VCS: asynch load of committed changes when do "Browse changes"
authorirengrig <Irina.Chernushina@jetbrains.com>
Fri, 12 Feb 2010 13:24:43 +0000 (12 16:24 +0300)
committerirengrig <Irina.Chernushina@jetbrains.com>
Fri, 12 Feb 2010 13:24:43 +0000 (12 16:24 +0300)
13 files changed:
platform/lang-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesModuleGroupingPolicy.java
platform/platform-api/src/com/intellij/ide/util/treeView/TreeState.java
platform/vcs-api/src/com/intellij/openapi/vcs/changes/committed/ChangeListFilteringStrategy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/ColumnFilteringStrategy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesTreeBrowser.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CompositeChangeListFilteringStrategy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RefreshCommittedAction.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/StructureFilteringStrategy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesGroupingPolicy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java
plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java
plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java

index ed0fc07..7cde5c9 100644 (file)
@@ -54,6 +54,10 @@ public class ChangesModuleGroupingPolicy implements ChangesGroupingPolicy {
     return null;
   }
 
+  public void clear() {
+    myModuleCache.clear();
+  }
+
   private ChangesBrowserNode getNodeForModule(Module module, ChangesBrowserNode root) {
     ChangesBrowserNode node = myModuleCache.get(module);
     if (node == null) {
index 6299c27..c7195ef 100644 (file)
@@ -78,10 +78,12 @@ public class TreeState implements JDOMExternalizable {
 
   private final List<List<PathElement>> myExpandedPaths;
   private final List<List<PathElement>> mySelectedPaths;
+  private boolean myScrollToSelection;
 
   private TreeState(List<List<PathElement>> expandedPaths, final List<List<PathElement>> selectedPaths) {
     myExpandedPaths = expandedPaths;
     mySelectedPaths = selectedPaths;
+    myScrollToSelection = true;
   }
 
   public TreeState() {
@@ -256,11 +258,12 @@ public class TreeState implements JDOMExternalizable {
     }
   }
 
+  // todo
   private void applySelected(final JTree tree, final DefaultMutableTreeNode node) {
     TreeUtil.unselect(tree, node);
     List<TreePath> selectionPaths = new ArrayList<TreePath>();
     for (List<PathElement> pathElements : mySelectedPaths) {
-      applySelectedTo(pathElements, tree.getModel().getRoot(), tree, selectionPaths);
+      applySelectedTo(pathElements, tree.getModel().getRoot(), tree, selectionPaths, myScrollToSelection);
     }
 
     if (selectionPaths.size() > 1) {
@@ -344,7 +347,7 @@ public class TreeState implements JDOMExternalizable {
   private static void applySelectedTo(final List<PathElement> path,
                                       Object root,
                                       JTree tree,
-                                      final List<TreePath> outSelectionPaths) {
+                                      final List<TreePath> outSelectionPaths, final boolean scrollToSelection) {
 
     for (int i = 1; i < path.size(); i++) {
       if (!(root instanceof DefaultMutableTreeNode)) return;
@@ -355,7 +358,11 @@ public class TreeState implements JDOMExternalizable {
     if (!(root instanceof DefaultMutableTreeNode)) return;
 
     final TreePath pathInNewTree = new TreePath(((DefaultMutableTreeNode) root).getPath());
-    TreeUtil.selectPath(tree, pathInNewTree);
+    if (scrollToSelection) {
+      TreeUtil.selectPath(tree, pathInNewTree);
+    } else {
+      tree.setSelectionPath(pathInNewTree);
+    }
     outSelectionPaths.add(pathInNewTree);
   }
 
@@ -419,5 +426,8 @@ public class TreeState implements JDOMExternalizable {
     }
   }
 
+  public void setScrollToSelection(boolean scrollToSelection) {
+    myScrollToSelection = scrollToSelection;
+  }
 }
 
index 9349da6..430b4cc 100644 (file)
@@ -33,6 +33,10 @@ public interface ChangeListFilteringStrategy {
   void addChangeListener(ChangeListener listener);
   void removeChangeListener(ChangeListener listener);
 
+  @Nullable
+  void resetFilterBase();
+  void appendFilterBase(List<CommittedChangeList> changeLists);
+
   @NotNull
   List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists);
 
@@ -55,6 +59,13 @@ public interface ChangeListFilteringStrategy {
     public void removeChangeListener(ChangeListener listener) {
     }
 
+    @Nullable
+    public void resetFilterBase() {
+    }
+
+    public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    }
+
     @NotNull
     public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
       return changeLists;
index f97df91..5b8c409 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.ui.ColoredListCellRenderer;
 import com.intellij.ui.SimpleTextAttributes;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.Convertor;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -32,10 +33,8 @@ import javax.swing.event.ChangeListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 import java.awt.*;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.*;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -46,12 +45,18 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri
   private final CopyOnWriteArrayList<ChangeListener> myListeners = ContainerUtil.createEmptyCOWList();
   private final ChangeListColumn myColumn;
   private final Class<? extends CommittedChangesProvider> myProviderClass;
+  private final MyListModel myModel;
+  private final CommittedChangeListToStringConvertor ourConvertorInstance = new CommittedChangeListToStringConvertor();
+
+  private Object[] myPrefferedSelection;
 
   public ColumnFilteringStrategy(final ChangeListColumn column,
                                  final Class<? extends CommittedChangesProvider> providerClass) {
     setLayout(new BorderLayout());
+    myModel = new MyListModel();
     myValueList = new JList();
     add(new JScrollPane(myValueList));
+    myValueList.setModel(myModel);
     myValueList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
       public void valueChanged(final ListSelectionEvent e) {
         for(ChangeListener listener: myListeners) {
@@ -91,30 +96,8 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri
   }
 
   public void setFilterBase(List<CommittedChangeList> changeLists) {
-    final Object oldSelection = myValueList.getSelectedValue();
-    final Collection<String> values = new TreeSet<String>();
-    for(CommittedChangeList changeList: changeLists) {
-      if (myProviderClass == null || myProviderClass.isInstance(changeList.getVcs().getCommittedChangesProvider())) {
-        //noinspection unchecked
-        values.add(myColumn.getValue(ReceivedChangeList.unwrap(changeList)).toString());
-      }
-    }
-    final String[] valueArray = ArrayUtil.toStringArray(values);
-    myValueList.setModel(new AbstractListModel() {
-      public int getSize() {
-        return valueArray.length+1;
-      }
-
-      public Object getElementAt(final int index) {
-        if (index == 0) {
-          return VcsBundle.message("committed.changes.filter.all");
-        }
-        return valueArray [index-1];
-      }
-    });
-    if (oldSelection != null) {
-      myValueList.setSelectedValue(oldSelection, false);
-    }
+    myPrefferedSelection = null;
+    appendFilterBase(changeLists);
   }
 
   public void addChangeListener(final ChangeListener listener) {
@@ -125,6 +108,36 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri
     myListeners.remove(listener);
   }
 
+  public void resetFilterBase() {
+    myPrefferedSelection = myValueList.getSelectedValues();
+    myValueList.clearSelection();
+    myModel.clear();
+    myValueList.revalidate();
+    myValueList.repaint();
+  }
+
+  public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    final Object[] oldSelection = myModel.isEmpty() ? myPrefferedSelection : myValueList.getSelectedValues();
+
+    myModel.addNext(changeLists, ourConvertorInstance);
+    if (oldSelection != null) {
+      for (Object o : oldSelection) {
+        myValueList.setSelectedValue(o, false);
+      }
+    }
+    myValueList.revalidate();
+    myValueList.repaint();
+  }
+
+  private class CommittedChangeListToStringConvertor implements Convertor<CommittedChangeList, String> {
+    public String convert(CommittedChangeList o) {
+      if (myProviderClass == null || myProviderClass.isInstance(o.getVcs().getCommittedChangesProvider())) {
+        return myColumn.getValue(ReceivedChangeList.unwrap(o)).toString();
+      }
+      return null;
+    }
+  }
+
   @NotNull
   public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
     final Object[] selection = myValueList.getSelectedValues();
@@ -145,4 +158,43 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri
     }
     return result;
   }
+
+  private static class MyListModel extends AbstractListModel {
+    private volatile String[] myValues;
+
+    private MyListModel() {
+      myValues = ArrayUtil.EMPTY_STRING_ARRAY;
+    }
+
+    public<T> void addNext(final Collection<T> values, final Convertor<T, String> convertor) {
+      final TreeSet<String> set = new TreeSet<String>(Arrays.asList(myValues));
+      for (T value : values) {
+        final String converted = convertor.convert(value);
+        if (converted != null) {
+          // also works as filter
+          set.add(converted);
+        }
+      }
+      myValues = ArrayUtil.toStringArray(set);
+    }
+
+    public int getSize() {
+      return myValues.length + 1;
+    }
+
+    public boolean isEmpty() {
+      return myValues.length == 0;
+    }
+
+    public Object getElementAt(int index) {
+      if (index == 0) {
+        return VcsBundle.message("committed.changes.filter.all");
+      }
+      return myValues[index-1];
+    }
+
+    public void clear() {
+      myValues = ArrayUtil.EMPTY_STRING_ARRAY;
+    }
+  }
 }
index 6bf55c7..57f3aa1 100644 (file)
@@ -24,22 +24,30 @@ package com.intellij.openapi.vcs.changes.committed;
 
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.*;
+import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
 import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.FilterComponent;
+import com.intellij.util.AsynchConsumer;
+import com.intellij.util.BufferedListConsumer;
 import com.intellij.util.Consumer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import java.awt.*;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,10 +62,12 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
   private ChangeBrowserSettings mySettings;
   private final RepositoryLocation myLocation;
   private int myMaxCount = 0;
-  private final FilterComponent myFilterComponent = new MyFilterComponent();
+  private final MyFilterComponent myFilterComponent = new MyFilterComponent();
   private List<CommittedChangeList> myChangesFromProvider;
   private final JLabel myErrorLabel = new JLabel();
   private final List<Runnable> myShouldBeCalledOnDispose;
+  private volatile boolean myDisposed;
+  private volatile boolean myInLoad;
 
   public CommittedChangesPanel(Project project, final CommittedChangesProvider provider, final ChangeBrowserSettings settings,
                                @Nullable final RepositoryLocation location, @Nullable ActionGroup extraActions) {
@@ -94,6 +104,7 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
     
     final AnAction anAction = ActionManager.getInstance().getAction("CommittedChanges.Refresh");
     anAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), this);
+    myBrowser.addFilter(myFilterComponent);
   }
 
   public RepositoryLocation getRepositoryLocation() {
@@ -121,26 +132,54 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
   }
 
   private void refreshChangesFromLocation() {
-    final Ref<VcsException> refEx = new Ref<VcsException>();
-    final Ref<List<CommittedChangeList>> changes = new Ref<List<CommittedChangeList>>();
-    boolean completed = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
-      public void run() {
+    myBrowser.reset();
+
+    myInLoad = true;
+    myBrowser.setLoading(true);
+    ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Loading changes", true, BackgroundFromStartOption.getInstance()) {
+      @Override
+      public void run(@NotNull final ProgressIndicator indicator) {
         try {
-          changes.set(myProvider.getCommittedChanges(mySettings, myLocation, myMaxCount));
+          final AsynchConsumer<List<CommittedChangeList>> appender = new AsynchConsumer<List<CommittedChangeList>>() {
+            public void finished() {
+            }
+
+            public void consume(final List<CommittedChangeList> list) {
+              new AbstractCalledLater(myProject, ModalityState.stateForComponent(myBrowser)) {
+                public void run() {
+                  myBrowser.append(list);
+                }
+              }.callMe();
+            }
+          };
+          final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(30, appender);
+
+          myProvider.loadCommittedChanges(mySettings, myLocation, myMaxCount, new AsynchConsumer<CommittedChangeList>() {
+            public void finished() {
+              bufferedListConsumer.flush();
+            }
+            public void consume(CommittedChangeList committedChangeList) {
+              if (myDisposed) {
+                indicator.cancel();
+              }
+              ProgressManager.checkCanceled();
+              bufferedListConsumer.consumeOne(committedChangeList);
+            }
+          });
         }
-        catch (VcsException ex) {
-          refEx.set(ex);
+        catch (final VcsException e) {
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            public void run() {
+              LOG.info(e);
+              Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(e.getMessages(), "\n"), "Committed Changes");
+            }
+          });
+        } finally {
+          myInLoad = false;
+          myBrowser.setLoading(false);
         }
       }
-    }, "Loading changes", true, myProject);
-    if (!refEx.isNull()) {
-      LOG.info(refEx.get());
-      Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(refEx.get().getMessages(), "\n"), "Committed Changes");
-    }
-    else if (completed) {
-      myChangesFromProvider = changes.get();
-      updateFilteredModel(false);
-    }
+    });
   }
 
   private void refreshChangesFromCache(final boolean cacheOnly) {
@@ -166,6 +205,34 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
                                  });
   }
 
+  private static class FilterHelper {
+    private final String[] myParts;
+
+    FilterHelper(final String filterString) {
+      myParts = filterString.split(" ");
+      for(int i = 0; i < myParts.length; ++ i) {
+        myParts [i] = myParts [i].toLowerCase();
+      }
+    }
+
+    public boolean filter(@NotNull final CommittedChangeList cl) {
+      return changeListMatches(cl, myParts);
+    }
+
+    private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) {
+      for(String word: filterWords) {
+        final String comment = changeList.getComment();
+        final String committer = changeList.getCommitterName();
+        if ((comment != null && comment.toLowerCase().indexOf(word) >= 0) ||
+            (committer != null && committer.toLowerCase().indexOf(word) >= 0) ||
+            Long.toString(changeList.getNumber()).indexOf(word) >= 0) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
   private void updateFilteredModel(final boolean keepFilter) {
     if (myChangesFromProvider == null) {
       return;
@@ -175,13 +242,10 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
       myBrowser.setItems(myChangesFromProvider, keepFilter, CommittedChangesBrowserUseCase.COMMITTED);
     }
     else {
-      final String[] strings = myFilterComponent.getFilter().split(" ");
-      for(int i=0; i<strings.length; i++) {
-        strings [i] = strings [i].toLowerCase();
-      }
+      final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter());
       List<CommittedChangeList> filteredChanges = new ArrayList<CommittedChangeList>();
       for(CommittedChangeList changeList: myChangesFromProvider) {
-        if (changeListMatches(changeList, strings)) {
+        if (filterHelper.filter(changeList)) {
           filteredChanges.add(changeList);
         }
       }
@@ -189,19 +253,6 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
     }
   }
 
-  private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) {
-    for(String word: filterWords) {
-      final String comment = changeList.getComment();
-      final String committer = changeList.getCommitterName();
-      if ((comment != null && comment.toLowerCase().indexOf(word) >= 0) ||
-          (committer != null && committer.toLowerCase().indexOf(word) >= 0) ||
-          Long.toString(changeList.getNumber()).indexOf(word) >= 0) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public void setChangesFilter() {
     CommittedChangesFilterDialog filterDialog = new CommittedChangesFilterDialog(myProject, myProvider.createFilterUI(true), mySettings);
     filterDialog.show();
@@ -222,19 +273,51 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
     for (Runnable runnable : myShouldBeCalledOnDispose) {
       runnable.run();
     }
+    myDisposed = true;
   }
 
   public void setErrorText(String text) {
     myErrorLabel.setText(text);
   }
 
-  private class MyFilterComponent extends FilterComponent {
+  private class MyFilterComponent extends FilterComponent implements ChangeListFilteringStrategy {
+    private final List<ChangeListener> myList;
+
     public MyFilterComponent() {
       super("COMMITTED_CHANGES_FILTER_HISTORY", 20);
+      myList = new ArrayList<ChangeListener>();
     }
 
     public void filter() {
-      updateFilteredModel(true);
+      for (ChangeListener changeListener : myList) {
+        changeListener.stateChanged(new ChangeEvent(this));
+      }
+    }
+    public JComponent getFilterUI() {
+      return null;
+    }
+    public void setFilterBase(List<CommittedChangeList> changeLists) {
+    }
+    public void addChangeListener(ChangeListener listener) {
+      myList.add(listener);
+    }
+    public void removeChangeListener(ChangeListener listener) {
+      myList.remove(listener);
+    }
+    public void resetFilterBase() {
+    }
+    public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    }
+    @NotNull
+    public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
+      final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter());
+      final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>();
+      for (CommittedChangeList list : changeLists) {
+        if (filterHelper.filter(list)) {
+          result.add(list);
+        }
+      }
+      return result;
     }
   }
 
@@ -244,4 +327,8 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
       notification.execute(project, root, myChangesFromProvider);
     }
   }
+
+  public boolean isInLoad() {
+    return myInLoad;
+  }
 }
index 0f831e7..9be7b60 100644 (file)
@@ -9,6 +9,7 @@ import com.intellij.ide.DefaultTreeExpander;
 import com.intellij.ide.TreeExpander;
 import com.intellij.ide.actions.ContextHelpAction;
 import com.intellij.ide.ui.SplitterProportionsDataImpl;
+import com.intellij.ide.util.treeView.TreeState;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.application.ApplicationManager;
@@ -40,6 +41,7 @@ import com.intellij.ui.treeStructure.actions.ExpandAllAction;
 import com.intellij.util.messages.MessageBusConnection;
 import com.intellij.util.messages.Topic;
 import com.intellij.util.ui.TreeWithEmptyText;
+import com.intellij.util.ui.UIUtil;
 import com.intellij.util.ui.tree.TreeUtil;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -92,6 +94,12 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
 
   private final WiseSplitter myInnerSplitter;
   private MessageBusConnection myConnection;
+  private List<CommittedChangeList> myFilteredChangeLists;
+  private Object[] mySelectedFiltering;
+  private JLabel myLoadingLabel;
+  private int[] mySelectionRows;
+  private TreeState myState;
+  private JPanel myLoadingPanel;
 
   public CommittedChangesTreeBrowser(final Project project, final List<CommittedChangeList> changeLists) {
     super(new BorderLayout());
@@ -121,7 +129,16 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
     myToolbarPanel = new JPanel(new BorderLayout());
     myLeftPanel.add(myToolbarPanel, BorderLayout.NORTH);
     myFilterSplitter = new Splitter(false, 0.5f);
+
+    myLoadingPanel = new JPanel(new BorderLayout());
+    myLoadingPanel.setBackground(UIUtil.getToolTipBackground());
+    myLoadingLabel = new JLabel("Loading...");
+
+    myLoadingPanel.add(myLoadingLabel, BorderLayout.CENTER);
+
+    myToolbarPanel.add(myLoadingPanel, BorderLayout.CENTER);
     myFilterSplitter.setSecondComponent(new JScrollPane(myChangesTree));
+    myLoadingPanel.setVisible(false);
     myLeftPanel.add(myFilterSplitter, BorderLayout.CENTER);
     final Splitter splitter = new Splitter(false, 0.7f);
     splitter.setFirstComponent(myLeftPanel);
@@ -163,6 +180,11 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
     });
   }
 
+  public void addFilter(final ChangeListFilteringStrategy strategy) {
+    myFilteringStrategy.addStrategy("permanent", strategy);
+    strategy.addChangeListener(myFilterChangeListener);
+  }
+
   private void updateGrouping() {
     if (myGroupingStrategy.changedSinceApply()) {
       ApplicationManager.getApplication().invokeLater(new Runnable() {
@@ -173,8 +195,8 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
     }
   }
 
-  private TreeModel buildTreeModel() {
-    final List<CommittedChangeList> filteredChangeLists = myFilteringStrategy.filterChangeLists(myChangeLists);
+  private TreeModel buildTreeModel(final List<CommittedChangeList> filteredChangeLists) {
+    myFilteredChangeLists = filteredChangeLists;
     DefaultMutableTreeNode root = new DefaultMutableTreeNode();
     DefaultTreeModel model = new DefaultTreeModel(root);
     DefaultMutableTreeNode lastGroupNode = null;
@@ -236,7 +258,8 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
   }
 
   private void updateModel() {
-    myChangesTree.setModel(buildTreeModel());
+    final List<CommittedChangeList> filteredChangeLists = myFilteringStrategy.filterChangeLists(myChangeLists);
+    myChangesTree.setModel(buildTreeModel(filteredChangeLists));
     TreeUtil.expandAll(myChangesTree);
   }
 
@@ -421,6 +444,29 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
     }); 
   }
 
+  // for appendable view
+  public void reset() {
+    myChangeLists.clear();
+    myFilteringStrategy.resetFilterBase();
+
+    myState = TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
+    updateModel();
+  }
+
+  public void append(final List<CommittedChangeList> list) {
+    final TreeState state = (myChangeLists.isEmpty() && myState != null) ? myState :
+      TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
+    state.setScrollToSelection(false);
+    myChangeLists.addAll(list);
+
+    myFilteringStrategy.appendFilterBase(list);
+
+    myChangesTree.setModel(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists)));
+    state.applyTo(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
+    TreeUtil.expandAll(myChangesTree);
+    myProject.getMessageBus().syncPublisher(ITEMS_RELOADED).itemsReloaded();
+  }
+
   public static class CommittedChangeListRenderer extends ColoredTreeCellRenderer {
     private final static DateFormat myDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
     private static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, Color.blue);
@@ -584,7 +630,7 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
 
   private class ChangesBrowserTree extends TreeWithEmptyText implements TypeSafeDataProvider {
     public ChangesBrowserTree() {
-      super(buildTreeModel());
+      super(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists)));
     }
 
     @Override
@@ -687,4 +733,14 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP
     void itemsReloaded();
     void emptyRefresh();
   }
+
+  public void setLoading(final boolean value) {
+    new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
+      public void run() {
+        myLoadingPanel.setVisible(value);
+        myToolbarPanel.revalidate();
+        myToolbarPanel.repaint();
+      }
+    }.callMe();
+  }
 }
index c4bab16..6f40a43 100644 (file)
@@ -56,6 +56,18 @@ public class CompositeChangeListFilteringStrategy implements ChangeListFiltering
     }
   }
 
+  public void resetFilterBase() {
+    for (ChangeListFilteringStrategy delegate : myDelegates.values()) {
+      delegate.resetFilterBase();
+    }
+  }
+
+  public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    for (ChangeListFilteringStrategy delegate : myDelegates.values()) {
+      delegate.appendFilterBase(changeLists);
+    }
+  }
+
   @NotNull
   public List<CommittedChangeList> filterChangeLists(final List<CommittedChangeList> changeLists) {
     List<CommittedChangeList> result = new ArrayList<CommittedChangeList>(changeLists);
index c5906e5..59eae65 100644 (file)
@@ -18,8 +18,8 @@ package com.intellij.openapi.vcs.changes.committed;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.PlatformDataKeys;
-import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
 
 /**
@@ -30,6 +30,7 @@ public class RefreshCommittedAction extends AnAction implements DumbAware {
     Project project = e.getData(PlatformDataKeys.PROJECT);
     CommittedChangesPanel panel = ChangesViewContentManager.getInstance(project).getActiveComponent(CommittedChangesPanel.class);
     assert panel != null;
+    if (panel.isInLoad()) return;
     if (panel.getRepositoryLocation() != null) {
       panel.refreshChanges(false);
     }
@@ -42,7 +43,7 @@ public class RefreshCommittedAction extends AnAction implements DumbAware {
     Project project = e.getData(PlatformDataKeys.PROJECT);
     if (project != null) {
       CommittedChangesPanel panel = ChangesViewContentManager.getInstance(project).getActiveComponent(CommittedChangesPanel.class);
-      e.getPresentation().setEnabled(panel != null);
+      e.getPresentation().setEnabled(panel != null && (! panel.isInLoad()));
     }
     else {
       e.getPresentation().setEnabled(false);
index cf527ac..08e33a9 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.intellij.openapi.vcs.changes.committed;
 
+import com.intellij.ide.util.treeView.TreeState;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsBundle;
@@ -24,8 +25,8 @@ import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode;
 import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer;
 import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder;
 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
-import com.intellij.util.containers.ContainerUtil;
 import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -36,7 +37,6 @@ import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeModel;
-import javax.swing.tree.TreeNode;
 import java.awt.*;
 import java.util.*;
 import java.util.List;
@@ -71,7 +71,8 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy {
     if (myUI == null) {
       myUI = new MyUI();
     }
-    myUI.buildModel(changeLists);
+    myUI.reset();
+    myUI.append(changeLists);
   }
 
   public void addChangeListener(ChangeListener listener) {
@@ -82,6 +83,14 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy {
     myListeners.remove(listener);
   }
 
+  public void resetFilterBase() {
+    myUI.reset();
+  }
+
+  public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    myUI.append(changeLists);
+  }
+
   @NotNull
   public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
     if (mySelection.size() == 0) {
@@ -111,6 +120,8 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy {
   private class MyUI extends JPanel {
     private final Tree myStructureTree;
     private boolean myRendererInitialized;
+    private final TreeModelBuilder myBuilder;
+    private TreeState myState;
 
     public MyUI() {
       setLayout(new BorderLayout());
@@ -129,35 +140,40 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy {
         }
       });
       add(new JScrollPane(myStructureTree), BorderLayout.CENTER);
+      myBuilder = new TreeModelBuilder(myProject, false);
     }
 
-    public void buildModel(final List<CommittedChangeList> changeLists) {
-      final Set<FilePath> filePaths = new HashSet<FilePath>();
-      for(CommittedChangeList changeList: changeLists) {
-        for(Change change: changeList.getChanges()) {
-          filePaths.add(ChangesUtil.getFilePath(change));
-        }
-      }
-      final TreeModelBuilder builder = new TreeModelBuilder(myProject, false);
-      final DefaultTreeModel model = builder.buildModelFromFilePaths(filePaths);
-      deleteLeafNodes((DefaultMutableTreeNode) model.getRoot());
-      myStructureTree.setModel(model);
+    public void initRenderer() {
       if (!myRendererInitialized) {
         myRendererInitialized = true;
         myStructureTree.setCellRenderer(new ChangesBrowserNodeRenderer(myProject, false, false));
       }
     }
 
-    private void deleteLeafNodes(final DefaultMutableTreeNode node) {
-      for(int i=node.getChildCount()-1; i >= 0; i--) {
-        final TreeNode child = node.getChildAt(i);
-        if (child.isLeaf()) {
-          node.remove(i);
-        }
-        else {
-          deleteLeafNodes((DefaultMutableTreeNode) child);
+    public void reset() {
+      myState = TreeState.createOn(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot());
+      myStructureTree.setModel(myBuilder.clearAndGetModel());
+    }
+
+    public void append(final List<CommittedChangeList> changeLists) {
+      final TreeState localState = (myState != null) && myBuilder.isEmpty() ? myState : TreeState.createOn(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot());
+
+      final Set<FilePath> filePaths = new HashSet<FilePath>();
+      for(CommittedChangeList changeList: changeLists) {
+        for(Change change: changeList.getChanges()) {
+          final FilePath path = ChangesUtil.getFilePath(change);
+          if (path.getParentPath() != null) {
+            filePaths.add(path.getParentPath());
+          }
         }
       }
+
+      final DefaultTreeModel model = myBuilder.buildModelFromFilePaths(filePaths);
+      myStructureTree.setModel(model);
+      localState.applyTo(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot());
+      myStructureTree.revalidate();
+      myStructureTree.repaint();
+      initRenderer();
     }
   }
 }
index fc4477e..4002220 100644 (file)
@@ -20,4 +20,6 @@ import org.jetbrains.annotations.Nullable;
 public interface ChangesGroupingPolicy {
   @Nullable
   ChangesBrowserNode getParentNodeFor(final ChangesBrowserNode node, final ChangesBrowserNode rootNode);
+
+  void clear();
 }
\ No newline at end of file
index ef1b6e1..7908373 100644 (file)
@@ -44,21 +44,24 @@ public class TreeModelBuilder {
 
   private final Project myProject;
   private final boolean showFlatten;
-  private final DefaultTreeModel model;
+  private DefaultTreeModel model;
   private final ChangesBrowserNode root;
+  private boolean myPolicyInitialized;
+  private ChangesGroupingPolicy myPolicy;
+  private final HashMap<String, ChangesBrowserNode> myFoldersCache;
 
   public TreeModelBuilder(final Project project, final boolean showFlatten) {
     myProject = project;
     this.showFlatten = showFlatten;
     root = ChangesBrowserNode.create(myProject, ROOT_NODE_VALUE);
     model = new DefaultTreeModel(root);
+    myFoldersCache = new HashMap<String, ChangesBrowserNode>();
   }
 
   public DefaultTreeModel buildModel(final List<Change> changes, final ChangeNodeDecorator changeNodeDecorator) {
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (final Change change : changes) {
-      insertChangeNode(change, foldersCache, policy, root, new Computable<ChangesBrowserNode>() {
+      insertChangeNode(change, policy, root, new Computable<ChangesBrowserNode>() {
         public ChangesBrowserNode compute() {
           return new ChangesBrowserChangeNode(myProject, change, changeNodeDecorator);
         }
@@ -73,11 +76,14 @@ public class TreeModelBuilder {
 
   @Nullable
   private ChangesGroupingPolicy createGroupingPolicy() {
-    final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
-    if (factory != null) {
-      return factory.createGroupingPolicy(model);
+    if (! myPolicyInitialized) {
+      myPolicyInitialized = true;
+      final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
+      if (factory != null) {
+        myPolicy = factory.createGroupingPolicy(model);
+      }
     }
-    return null;
+    return myPolicy;
   }
 
   public DefaultTreeModel buildModelFromFiles(final List<VirtualFile> files) {
@@ -162,13 +168,12 @@ public class TreeModelBuilder {
       final ChangeListRemoteState listRemoteState = new ChangeListRemoteState(changes.size());
       ChangesBrowserNode listNode = new ChangesBrowserChangeListNode(myProject, list, listRemoteState);
       model.insertNodeInto(listNode, root, 0);
-      final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
       final ChangesGroupingPolicy policy = createGroupingPolicy();
       int i = 0;
       for (final Change change : changes) {
         final MyChangeNodeUnderChangeListDecorator decorator =
           new MyChangeNodeUnderChangeListDecorator(revisionsCache, new ChangeListRemoteState.Reporter(i, listRemoteState));
-        insertChangeNode(change, foldersCache, policy, listNode, new Computable<ChangesBrowserNode>() {
+        insertChangeNode(change, policy, listNode, new Computable<ChangesBrowserNode>() {
           public ChangesBrowserNode compute() {
             return new ChangesBrowserChangeNode(myProject, change, decorator);
           }
@@ -181,11 +186,10 @@ public class TreeModelBuilder {
 
   private void buildVirtualFiles(final Iterator<FilePath> iterator, @Nullable final Object tag) {
     final ChangesBrowserNode baseNode = createNode(tag);
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (; ; iterator.hasNext()) {
       final FilePath path = iterator.next();
-      insertChangeNode(path.getVirtualFile(), foldersCache, policy, baseNode, defaultNodeCreator(path.getVirtualFile()));
+      insertChangeNode(path.getVirtualFile(), policy, baseNode, defaultNodeCreator(path.getVirtualFile()));
     }
   }
 
@@ -207,37 +211,35 @@ public class TreeModelBuilder {
   }
 
   private void insertFilesIntoNode(List<VirtualFile> files, ChangesBrowserNode baseNode) {
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (VirtualFile file : files) {
-      insertChangeNode(file, foldersCache, policy, baseNode, defaultNodeCreator(file));
+      insertChangeNode(file, policy, baseNode, defaultNodeCreator(file));
     }
   }
 
   private void buildLocallyDeletedPaths(final Collection<LocallyDeletedChange> locallyDeletedChanges, final ChangesBrowserNode baseNode) {
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (LocallyDeletedChange change : locallyDeletedChanges) {
-      ChangesBrowserNode oldNode = foldersCache.get(change.getPresentableUrl());
+      ChangesBrowserNode oldNode = myFoldersCache.get(change.getPresentableUrl());
       if (oldNode == null) {
         final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, change);
-        final ChangesBrowserNode parent = getParentNodeFor(node, foldersCache, policy, baseNode);
+        final ChangesBrowserNode parent = getParentNodeFor(node, policy, baseNode);
         model.insertNodeInto(node, parent, parent.getChildCount());
-        foldersCache.put(change.getPresentableUrl(), node);
+        myFoldersCache.put(change.getPresentableUrl(), node);
       }
     }
   }
 
-  private void buildFilePaths(final Collection<FilePath> filePaths, final ChangesBrowserNode baseNode) {
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
+  public void buildFilePaths(final Collection<FilePath> filePaths, final ChangesBrowserNode baseNode) {
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (FilePath file : filePaths) {
       assert file != null;
-      ChangesBrowserNode oldNode = foldersCache.get(file);
+      ChangesBrowserNode oldNode = myFoldersCache.get(file.getIOFile().getAbsolutePath());
       if (oldNode == null) {
         final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, file);
-        model.insertNodeInto(node, getParentNodeFor(node, foldersCache, policy, baseNode), 0);
-        foldersCache.put(file.getIOFile().getAbsolutePath(), node);
+        final ChangesBrowserNode parentNode = getParentNodeFor(node, policy, baseNode);
+        model.insertNodeInto(node, parentNode, 0);
+        myFoldersCache.put(file.getIOFile().getAbsolutePath(), node);
       }
     }
   }
@@ -248,12 +250,11 @@ public class TreeModelBuilder {
     model.insertNodeInto(rootsHeadNode, root, root.getChildCount());
 
     for (VirtualFile vf : switchedRoots.keySet()) {
-      final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
       final ChangesGroupingPolicy policy = createGroupingPolicy();
       final ContentRevision cr = new CurrentContentRevision(new FilePathImpl(vf));
       final Change change = new Change(cr, cr, FileStatus.NOT_CHANGED);
       final String branchName = switchedRoots.get(vf);
-      insertChangeNode(vf, foldersCache, policy, rootsHeadNode, new Computable<ChangesBrowserNode>() {
+      insertChangeNode(vf, policy, rootsHeadNode, new Computable<ChangesBrowserNode>() {
         public ChangesBrowserNode compute() {
           return new ChangesBrowserChangeNode(myProject, change, new ChangeNodeDecorator() {
             public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) {
@@ -279,10 +280,9 @@ public class TreeModelBuilder {
         ChangesBrowserNode branchNode = ChangesBrowserNode.create(myProject, branchName);
         model.insertNodeInto(branchNode, baseNode, baseNode.getChildCount());
 
-        final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
         final ChangesGroupingPolicy policy = createGroupingPolicy();
         for (VirtualFile file : switchedFileList) {
-          insertChangeNode(file, foldersCache, policy, branchNode, defaultNodeCreator(file));
+          insertChangeNode(file, policy, branchNode, defaultNodeCreator(file));
         }
       }
     }
@@ -291,13 +291,12 @@ public class TreeModelBuilder {
   private void buildLogicallyLockedFiles(final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
     final ChangesBrowserNode baseNode = createNode(ChangesBrowserNode.LOGICALLY_LOCKED_TAG);
 
-    final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
     final ChangesGroupingPolicy policy = createGroupingPolicy();
     for (Map.Entry<VirtualFile, LogicalLock> entry : logicallyLockedFiles.entrySet()) {
       final VirtualFile file = entry.getKey();
       final LogicalLock lock = entry.getValue();
       final ChangesBrowserLogicallyLockedFile obj = new ChangesBrowserLogicallyLockedFile(myProject, file, lock);
-      insertChangeNode(obj, foldersCache, policy, baseNode,
+      insertChangeNode(obj, policy, baseNode,
                        defaultNodeCreator(obj));
     }
   }
@@ -310,11 +309,10 @@ public class TreeModelBuilder {
     };
   }
 
-  private void insertChangeNode(final Object change, final HashMap<String, ChangesBrowserNode> foldersCache,
-                                final ChangesGroupingPolicy policy,
+  private void insertChangeNode(final Object change, final ChangesGroupingPolicy policy,
                                 final ChangesBrowserNode listNode, final Computable<ChangesBrowserNode> nodeCreator) {
     final FilePath nodePath = getPathForObject(change);
-    ChangesBrowserNode oldNode = (nodePath == null) ? null : foldersCache.get(nodePath.getIOFile().getAbsolutePath());
+    ChangesBrowserNode oldNode = (nodePath == null) ? null : myFoldersCache.get(nodePath.getIOFile().getAbsolutePath());
     ChangesBrowserNode node = nodeCreator.compute();
     if (oldNode != null) {
       for(int i=oldNode.getChildCount()-1; i >= 0; i--) {
@@ -326,14 +324,14 @@ public class TreeModelBuilder {
       int index = model.getIndexOfChild(parent, oldNode);
       model.removeNodeFromParent(oldNode);
       model.insertNodeInto(node, parent, index);
-      foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
+      myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
     }
     else {
-      ChangesBrowserNode parentNode = getParentNodeFor(node, foldersCache, policy, listNode);
+      ChangesBrowserNode parentNode = getParentNodeFor(node, policy, listNode);
       model.insertNodeInto(node, parentNode, model.getChildCount(parentNode));
       // ?
       if (nodePath != null) {
-        foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
+        myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
       }
     }
   }
@@ -393,10 +391,7 @@ public class TreeModelBuilder {
     return null;
   }
 
-  private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node,
-                                Map<String, ChangesBrowserNode> folderNodesCache,
-                                @Nullable ChangesGroupingPolicy policy,
-                                ChangesBrowserNode rootNode) {
+  private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node, @Nullable ChangesGroupingPolicy policy, ChangesBrowserNode rootNode) {
     if (showFlatten) {
       return rootNode;
     }
@@ -415,14 +410,27 @@ public class TreeModelBuilder {
       return rootNode;
     }
 
-    ChangesBrowserNode parentNode = folderNodesCache.get(parentPath.getIOFile().getAbsolutePath());
+    final String parentKey = parentPath.getIOFile().getAbsolutePath();
+    ChangesBrowserNode parentNode = myFoldersCache.get(parentKey);
     if (parentNode == null) {
       parentNode = ChangesBrowserNode.create(myProject, parentPath);
-      ChangesBrowserNode grandPa = getParentNodeFor(parentNode, folderNodesCache, policy, rootNode);
+      ChangesBrowserNode grandPa = getParentNodeFor(parentNode, policy, rootNode);
       model.insertNodeInto(parentNode, grandPa, grandPa.getChildCount());
-      folderNodesCache.put(parentPath.getIOFile().getAbsolutePath(), parentNode);
+      myFoldersCache.put(parentKey, parentNode);
     }
 
     return parentNode;
   }
+
+  public DefaultTreeModel clearAndGetModel() {
+    root.removeAllChildren();
+    model = new DefaultTreeModel(root);
+    myFoldersCache.clear();
+    myPolicyInitialized = false;
+    return model;
+  }
+
+  public boolean isEmpty() {
+    return model.getChildCount(root) == 0;
+  }
 }
index 3fe68b2..0faf937 100644 (file)
@@ -156,10 +156,13 @@ public class CvsCommittedChangesProvider implements CachingCommittedChangesProvi
         dateFrom = calendar.getTime();
       }
       final ChangeBrowserSettings.Filter filter = settings.createFilter();
+      final Set<Date> controlSet = new HashSet<Date>();
       final CvsResult executionResult = runRLogOperation(connectionSettings, module, dateFrom, dateTo, new Consumer<LogInformationWrapper>() {
         public void consume(LogInformationWrapper wrapper) {
           final RevisionWrapper revisionWrapper = builder.revisionWrapperFromLog(wrapper);
           final CvsChangeList changeList = builder.addRevision(revisionWrapper);
+          if (controlSet.contains(changeList.getCommitDate())) return;
+          controlSet.add(changeList.getCommitDate());
           if (filter.accepts(changeList)) {
             consumer.consume(changeList);
           }
index 53f3194..d4c11c2 100644 (file)
@@ -852,6 +852,12 @@ public class RootsAndBranches implements CommittedChangeListDecorator {
       myListener = null;
     }
 
+    public void resetFilterBase() {
+    }
+
+    public void appendFilterBase(List<CommittedChangeList> changeLists) {
+    }
+
     @NotNull
     public List<CommittedChangeList> filterChangeLists(final List<CommittedChangeList> changeLists) {
       if ((! myFilterAlien.isSelected(null)) && (! myFilterNotMerged.isSelected(null)) && (! myFilterMerged.isSelected(null))) {