Git log tree: goto commit action
authorirengrig <Irina.Chernushina@jetbrains.com>
Fri, 30 Apr 2010 15:58:53 +0000 (19:58 +0400)
committerirengrig <Irina.Chernushina@jetbrains.com>
Fri, 30 Apr 2010 15:58:53 +0000 (19:58 +0400)
plugins/git4idea/src/git4idea/changes/GitChangeUtils.java
plugins/git4idea/src/git4idea/history/GitHistoryUtils.java
plugins/git4idea/src/git4idea/history/browser/GitLogTree.java
plugins/git4idea/src/git4idea/history/browser/GitTreeController.java
plugins/git4idea/src/git4idea/history/browser/GitTreeViewI.java
plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java
plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java
plugins/git4idea/src/git4idea/history/browser/ManageGitTreeView.java
plugins/git4idea/src/git4idea/history/browser/RepositoryCommonData.java

index 7ac25f16209762d39e38f06b4581e910b06afcd9..8f2afd99c3564395e26a25dbbc2ff830d13008f3 100644 (file)
@@ -17,6 +17,7 @@ package git4idea.changes;
 
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.FileStatus;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.Change;
@@ -32,7 +33,9 @@ import git4idea.GitUtil;
 import git4idea.commands.GitCommand;
 import git4idea.commands.GitSimpleHandler;
 import git4idea.commands.StringScanner;
+import git4idea.history.browser.SHAHash;
 import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.util.*;
@@ -279,6 +282,38 @@ public class GitChangeUtils {
     }
   }
 
+  @Nullable
+  public static SHAHash commitExists(final Project project, final VirtualFile root, final String anyReference) {
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
+    h.setNoSSH(true);
+    h.setSilent(true);
+    h.addParameters("--max-count=1", "--pretty=%H", "--encoding=UTF-8", "\"" + anyReference + "\"", "--");
+    try {
+      final String output = h.run().trim();
+      if (StringUtil.isEmptyOrSpaces(output)) return null;
+      return new SHAHash(output);
+    }
+    catch (VcsException e) {
+      return null;
+    }
+  }
+
+  @Nullable
+  public static SHAHash commitExistsByComment(final Project project, final VirtualFile root, final String anyReference) {
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
+    h.setNoSSH(true);
+    h.setSilent(true);
+    final String grepParam = "--grep=" + StringUtil.escapeQuotes(anyReference);
+    h.addParameters("--max-count=1", "--pretty=%H", "--all", "--encoding=UTF-8", grepParam, "--");
+    try {
+      final String output = h.run().trim();
+      if (StringUtil.isEmptyOrSpaces(output)) return null;
+      return new SHAHash(output);
+    }
+    catch (VcsException e) {
+      return null;
+    }
+  }
 
   /**
    * Parse changelist
index b032a077915759d9755a1e357f07ca59a7adfc6c..d36ab5435b94347fb5a697ecbe973c317cb688f8 100644 (file)
@@ -18,6 +18,7 @@ package git4idea.history;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
@@ -279,6 +280,32 @@ public class GitHistoryUtils {
     return rc;
   }
 
+  public static List<Pair<SHAHash, Date>> onlyHashesHistory(Project project, FilePath path, final String... parameters) throws VcsException {
+    // adjust path using change manager
+    path = getLastCommitName(project, path);
+    final VirtualFile root = GitUtil.getGitRoot(path);
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
+    h.setNoSSH(true);
+    h.setStdoutSuppressed(true);
+    h.addParameters(parameters);
+    h.addParameters("--name-only", "--pretty=format:%x03%H%x00%ct%x00", "--encoding=UTF-8");
+    h.endOptions();
+    h.addRelativePaths(path);
+    String output = h.run();
+    final List<Pair<SHAHash, Date>> rc = new ArrayList<Pair<SHAHash, Date>>();
+    StringTokenizer tk = new StringTokenizer(output, "\u0003", false);
+
+    while (tk.hasMoreTokens()) {
+      final String line = tk.nextToken();
+      final StringTokenizer tk2 = new StringTokenizer(line, "\u0000\n", false);
+        final String hash = tk2.nextToken("\u0000\n");
+        final String dateString = tk2.nextToken("\u0000");
+        final Date date = GitUtil.parseTimestamp(dateString);
+        rc.add(new Pair<SHAHash, Date>(new SHAHash(hash), date));
+    }
+    return rc;
+  }
+
   public static List<GitCommit> historyWithLinks(Project project, FilePath path, final Set<String> allBranchesSet, final String... parameters) throws VcsException {
     // adjust path using change manager
     path = getLastCommitName(project, path);
@@ -349,34 +376,6 @@ public class GitHistoryUtils {
                              committerEmail, tags, branches));
       //}
     }
-    /*StringTokenizer tk2 = new StringTokenizer(output, "\u0000\n", false);
-    String prefix = root.getPath() + "/";
-    while (tk.hasMoreTokens()) {
-      final String hash = tk.nextToken("\u0000\n");
-      final Date date = GitUtil.parseTimestamp(tk.nextToken("\u0000"));
-      final String authorName = tk.nextToken("\u0000");
-      final String committerName = tk.nextToken("\u0000");
-      // parent hashes
-      final String parents = tk.nextToken("\u0000");
-      final String[] parentsSplit = parents.split(" "); // todo if parent = 000000
-      final Set<SHAHash> parentsHashes = new HashSet<SHAHash>();
-      for (String s : parentsSplit) {
-        parentsHashes.add(new SHAHash(s));
-      }
-      // decorate
-      final String decorate = tk.nextToken("\u0000");
-      final int startSymb = decorate.indexOf("(");
-      int idxFrom = startSymb == -1 ? 0 : startSymb;
-      final int endSymb = decorate.indexOf(")");
-      int idxTo = endSymb == -1 ? 0 : endSymb;
-      final String refs = decorate.substring(idxFrom, idxTo);
-      final String[] refNames = refs.split(", ");
-
-      final String message = tk.nextToken("\u0000").trim();
-      tk.nextToken("\u0000\u0001\u0000");
-      //final FilePath revisionPath = VcsUtil.getFilePathForDeletedFile(prefix + GitUtil.unescapePath(tk.nextToken("\u0000\n")), false);
-      rc.add(new GitCommit(new SHAHash(hash), authorName, committerName, date, message, parentsHashes, Arrays.asList(refNames)));
-    }*/
     return rc;
   }
 
index b8026ab9763f27ddb71b085230baf33dbfd0ffb2..b6a5af095ad5628fd91f75716aa25e99090d0393 100644 (file)
@@ -24,6 +24,7 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Splitter;
+import com.intellij.openapi.ui.popup.*;
 import com.intellij.openapi.util.*;
 import com.intellij.openapi.vcs.ObjectsConvertor;
 import com.intellij.openapi.vcs.changes.Change;
@@ -49,6 +50,7 @@ import javax.swing.event.ListSelectionListener;
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeModel;
 import java.awt.*;
+import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.text.DateFormat;
 import java.util.*;
@@ -152,13 +154,26 @@ public class GitLogTree implements GitTreeViewI {
     }.callMe();
   }
 
-  public void refreshView(@NotNull final List<GitCommit> commitsToShow, final TravelTicket ticket) {
+  public void refreshView(@NotNull final List<GitCommit> commitsToShow, final TravelTicket ticket, final SHAHash jumpTarget) {
     new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
       public void run() {
         final boolean wasSelected = myCommitsList.getSelectedIndices().length == 0;
         myCommitsList.setListData(ArrayUtil.toObjectArray(commitsToShow));
-        if ((! commitsToShow.isEmpty()) && (wasSelected || (! Comparing.equal(myTicket, ticket)))) {
-          myCommitsList.setSelectedIndex(0);
+        if (jumpTarget != null) {
+          for (int i = 0; i < commitsToShow.size(); i++) {
+            final GitCommit commit = commitsToShow.get(i);
+            if (commit.getHash().equals(jumpTarget)) {
+              myCommitsList.setSelectedIndex(i);
+              break;
+            }
+          }
+          if (myCommitsList.getSelectedIndices().length == 0) {
+            myCommitsList.setSelectedIndex(0);
+          }
+        } else {
+          if ((! commitsToShow.isEmpty()) && (wasSelected || (! Comparing.equal(myTicket, ticket)))) {
+            myCommitsList.setSelectedIndex(0);
+          }
         }
         myTicket = ticket;
         myCommitsList.revalidate();
@@ -218,6 +233,9 @@ public class GitLogTree implements GitTreeViewI {
     final MyCherryPick cp = new MyCherryPick();
     group.add(cp);
     cp.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)), myPanel);
+    final MyGoto gotoCommit = new MyGoto();
+    group.add(gotoCommit);
+    gotoCommit.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)), myPanel);
     group.add(new MyRefreshAction());
 
     final ActionManager am = ActionManager.getInstance();
@@ -523,6 +541,65 @@ public class GitLogTree implements GitTreeViewI {
     }
   }
 
+  // hash, reference name, or comment mask
+  private class MyGoto extends AnAction {
+    private MyGoto() {
+      super("Goto", "Type commit hash, or reference, or regexp for commit message", IconLoader.getIcon("/general/run.png"));
+    }
+
+    @Override
+    public void actionPerformed(final AnActionEvent e) {
+      final JTextField field = new JTextField(30);
+
+      final String[] gotoString = new String[1];
+      final JBPopup[] popup = new JBPopup[1];
+      field.addKeyListener(new KeyAdapter() {
+        @Override
+        public void keyPressed(KeyEvent e) {
+          if (KeyEvent.VK_ENTER == e.getKeyCode()) {
+            gotoString[0] = field.getText();
+            if (popup[0] != null) {
+              popup[0].cancel();
+            }
+          }
+        }
+      });
+      final JPanel panel = new JPanel(new BorderLayout());
+      panel.add(field, BorderLayout.CENTER);
+      final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, field);
+      popup[0] = builder.setTitle("Goto")
+        .setResizable(true)
+        .setFocusable(true)
+        .setMovable(true)
+        .setModalContext(true)
+        .setAdText("Commit hash, or reference, or regexp for commit message")
+        .setDimensionServiceKey(myProject, "Git.Log.Tree.Goto", true)
+        .setCancelOnClickOutside(true).setCancelCallback(new Computable<Boolean>() {
+          public Boolean compute() {
+            if (gotoString[0] != null) {
+              tryFind(gotoString[0]);
+            }
+            return Boolean.TRUE;
+          }
+        })
+        .addListener(new JBPopupListener() {
+          public void beforeShown(LightweightWindowEvent event) {
+            IdeFocusManager.findInstanceByContext(e.getDataContext()).requestFocus(field, true);
+          }
+          public void onClosed(LightweightWindowEvent event) {
+          }
+        })
+        .createPopup();
+      UIUtil.installPopupMenuColorAndFonts(popup[0].getContent());
+      UIUtil.installPopupMenuBorder(popup[0].getContent());
+      popup[0].showInBestPositionFor(e.getDataContext());
+    }
+
+    private void tryFind(final String s) {
+      myController.navigateTo(s);
+    }
+  }
+
   private class MyCherryPick extends AnAction {
     private final Set<SHAHash> myIdsInProgress;
 
index 688db684f34d4f14472993fcacf1e02289274e91..f98393e213eec3a6e3de7d8768254aebc7c48c64 100644 (file)
@@ -66,6 +66,8 @@ class GitTreeController implements ManageGitTreeView {
   private Alarm myAlarm;
   private Portion myFiltered;
 
+  private SHAHash myJumpTarget;
+
   private final SLRUCache<SHAHash, CommittedChangeList> myListsCache = new SLRUCache<SHAHash, CommittedChangeList>(128, 64) {
     @NotNull
     @Override
@@ -78,6 +80,7 @@ class GitTreeController implements ManageGitTreeView {
       }
     }
   };
+  private Runnable myRefresher;
 
   GitTreeController(final Project project, final VirtualFile root, final GitTreeViewI treeView) {
     myProject = project;
@@ -92,19 +95,20 @@ class GitTreeController implements ManageGitTreeView {
     myBranches = new AtomicReference<List<String>>(Collections.<String>emptyList());
     myUsers = new AtomicReference<List<String>>(Collections.<String>emptyList());
 
-    // todo !!!!! dispose
     myAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD, project);
-    myFilterRequestsMerger = new RequestsMerger(new Runnable() {
+    myRefresher = new Runnable() {
       public void run() {
         try {
           if (myFilterHolder.isDirty()) {
+            final SHAHash target = myJumpTarget;
+            myJumpTarget = null;
             final Portion filtered = loadPortion(myFilterHolder.getStartingPoints(), myFilterHolder.getCurrentPoint(), null,
                                                  myFilterHolder.getFilters(), PageSizes.LOAD_SIZE);
             if (filtered == null) return;
 
             final List<GitCommit> commitList = filtered.getXFrom(0, PageSizes.VISIBLE_PAGE_SIZE);
             myFiltered = filtered;
-            myTreeView.refreshView(commitList, new TravelTicket(filtered.isStartFound(), filtered.getLast().getDate()));
+            myTreeView.refreshView(commitList, new TravelTicket(filtered.isStartFound(), filtered.getLast().getDate()), target);
 
             myFilterHolder.setDirty(false);
           }
@@ -121,7 +125,8 @@ class GitTreeController implements ManageGitTreeView {
           myProject.getMessageBus().syncPublisher(GitProjectLogManager.CHECK_CURRENT_BRANCH).consume(myRoot);
         }
       }
-    }, new Consumer<Runnable>() {
+    };
+    myFilterRequestsMerger = new RequestsMerger(myRefresher, new Consumer<Runnable>() {
       public void consume(Runnable runnable) {
         myTreeView.refreshStarted();
         myAlarm.addRequest(runnable, 50);
@@ -186,6 +191,29 @@ class GitTreeController implements ManageGitTreeView {
     }
   }
 
+  private List<Pair<SHAHash, Date>> loadLine(final Collection<String> startingPoints, final Date beforePoint, final Date afterPoint,
+                              final Collection<ChangesFilter.Filter> filtersIn, int maxCnt) {
+    final Collection<ChangesFilter.Filter> filters = new LinkedList<ChangesFilter.Filter>(filtersIn);
+    if (beforePoint != null) {
+      filters.add(new ChangesFilter.BeforeDate(new Date(beforePoint.getTime() - 1)));
+    }
+    if (afterPoint != null) {
+      filters.add(new ChangesFilter.AfterDate(afterPoint));
+    }
+
+    try {
+      return myAccess.loadCommitHashes(startingPoints, Collections.<String>emptyList(), filters, maxCnt);
+    }
+    catch (VcsException e) {
+      myTreeView.acceptError(e.getMessage());
+      return null;
+    }
+  }
+
+  public SHAHash commitExists(String reference) {
+    return GitChangeUtils.commitExists(myProject, myRoot, reference);
+  }
+
   private String getStatusMessage() {
     // todo
     return "Showing";
@@ -272,6 +300,47 @@ class GitTreeController implements ManageGitTreeView {
     myFilterRequestsMerger.request();
   }
 
+  public void navigateTo(@NotNull String reference) {
+    SHAHash hash = GitChangeUtils.commitExists(myProject, myRoot, reference);
+    if (hash == null) {
+      hash = GitChangeUtils.commitExistsByComment(myProject, myRoot, reference);
+    }
+    if (hash == null) {
+      ChangesViewBalloonProblemNotifier.showMe(myProject, "Nothing found for: \"" + reference + "\"", MessageType.WARNING);
+    } else {
+      final SHAHash finalHash = hash;
+      myAlarm.addRequest(new Runnable() {
+        public void run() {
+          // start from beginning
+          final List<Date> wayList = new LinkedList<Date>();
+
+          while (true) {
+            final Date startFrom = wayList.isEmpty() ? null : wayList.get(wayList.size() - 1);
+            final List<Pair<SHAHash, Date>> pairs =
+              loadLine(myFilterHolder.getStartingPoints(), startFrom, null, myFilterHolder.getFilters(), PageSizes.LOAD_SIZE);
+            if (pairs.isEmpty()) return;
+            for (Pair<SHAHash, Date> pair : pairs) {
+              if (finalHash.equals(pair.getFirst())) {
+                // select this page
+                while (myFilterHolder.getCurrentPoint() != null) {
+                  myFilterHolder.popContinuationPoint();
+                }
+                for (Date date : wayList) {
+                  myFilterHolder.addContinuationPoint(date);
+                }
+                myJumpTarget = finalHash;
+                myFilterHolder.setDirty(true);
+                myRefresher.run();
+                return;
+              }
+            }
+            wayList.add(pairs.get(pairs.size() - 1).getSecond());
+          }
+        }
+      }, 10);
+    }
+  }
+
   public void refresh() {
     myFilterHolder.setDirty(true);
     myFilterRequestsMerger.request();
index f4db71050a8f336135a1cd32d6786793c85869bd..4bced54ce18d594402815985b2c286285622f56f 100644 (file)
@@ -23,7 +23,7 @@ import java.util.Set;
 
 public interface GitTreeViewI {
   void controllerReady();
-  void refreshView(@NotNull final List<GitCommit> commitsToShow, final TravelTicket ticket);
+  void refreshView(@NotNull final List<GitCommit> commitsToShow, final TravelTicket ticket, SHAHash jumpTarget);
   void showStatusMessage(@NotNull final String message);
 
   void refreshStarted();
index 43e5f1bb2e5797ad967aae375e4e7ee9c957bd47..481ed4f959b92a9bb23f86dde8d77cb383868b4b 100644 (file)
  */
 package git4idea.history.browser;
 
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 public interface LowLevelAccess {
   GitCommit getCommitByHash(final SHAHash hash);
-  // todo define signature
+
+  List<Pair<SHAHash,Date>> loadCommitHashes(final @NotNull Collection<String> startingPoints,
+                                                   @NotNull final Collection<String> endPoints,
+                                                   @NotNull final Collection<ChangesFilter.Filter> filters,
+                                                   int useMaxCnt) throws VcsException;
+  
   void loadCommits(final Collection<String> startingPoints, final Collection<String> endPoints, final Collection<ChangesFilter.Filter> filters,
                    final Consumer<GitCommit> consumer, final Collection<String> branches, int useMaxCnt) throws VcsException;
 
index bb4918567791a693e9889bb37e3e153538ff8885..1d71098f037a053a7125384e719a53125d71936f 100644 (file)
@@ -17,6 +17,7 @@ package git4idea.history.browser;
 
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.vcs.FilePathImpl;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -27,10 +28,7 @@ import git4idea.commands.GitFileUtils;
 import git4idea.history.GitHistoryUtils;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
 
 public class LowLevelAccessImpl implements LowLevelAccess {
   private final static Logger LOG = Logger.getInstance("#git4idea.history.browser.LowLevelAccessImpl");
@@ -46,6 +44,34 @@ public class LowLevelAccessImpl implements LowLevelAccess {
     return null;  //To change body of implemented methods use File | Settings | File Templates.
   }
 
+  public List<Pair<SHAHash,Date>> loadCommitHashes(final @NotNull Collection<String> startingPoints,
+                                                   @NotNull final Collection<String> endPoints,
+                                                   @NotNull final Collection<ChangesFilter.Filter> filters,
+                                                   int useMaxCnt) throws VcsException {
+    final List<String> parameters = new LinkedList<String>();
+    if (useMaxCnt > 0) {
+      parameters.add("--max-count=" + useMaxCnt);
+    }
+
+    for (ChangesFilter.Filter filter : filters) {
+      filter.applyToCommandLine(parameters);
+    }
+
+    if (! startingPoints.isEmpty()) {
+      for (String startingPoint : startingPoints) {
+        parameters.add(startingPoint);
+      }
+    } else {
+      parameters.add("--all");
+    }
+
+    for (String endPoint : endPoints) {
+      parameters.add("^" + endPoint);
+    }
+
+    return GitHistoryUtils.onlyHashesHistory(myProject, new FilePathImpl(myRoot), parameters.toArray(new String[parameters.size()]));
+  }
+
   public void loadCommits(final @NotNull Collection<String> startingPoints, @NotNull final Collection<String> endPoints,
                           @NotNull final Collection<ChangesFilter.Filter> filters,
                           @NotNull final Consumer<GitCommit> consumer, final Collection<String> branches, int useMaxCnt)
index 35f5aacee339a10e1579184f5aeb4933c7a9cc20..7481db90ff95be8847b7e406619ad5ef84e0c524 100644 (file)
  */
 package git4idea.history.browser;
 
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
 interface ManageGitTreeView extends RepositoryCommonData {
   void init();
+
   boolean hasNext(@Nullable final TravelTicket ticket);
   boolean hasPrevious(@Nullable final TravelTicket ticket);
   void next(@Nullable final TravelTicket ticket);
   void previous(@Nullable final TravelTicket ticket);
+  void navigateTo(@NotNull final String reference);
 
   void refresh();
 
index 25d3ad41d78fe5255c312bb6ae57d54d7844bfa6..3306aded8552f61fb4fda9b6ca012b3819ecb4d6 100644 (file)
@@ -23,4 +23,6 @@ public interface RepositoryCommonData {
   List<String> getAllBranchesOrdered();
   List<String> getKnownUsers();
   void cherryPick(final Collection<SHAHash> hash);
+  // todo remove
+  SHAHash commitExists(String reference);
 }