git update & push active branches: save all documents, prohibit save/sync on frame...
authorKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Wed, 9 Mar 2011 17:31:17 +0000 (20:31 +0300)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Thu, 10 Mar 2011 16:09:10 +0000 (19:09 +0300)
Cleanup, logging.

plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java
plugins/git4idea/src/git4idea/ui/GitUIUtil.java
plugins/git4idea/src/git4idea/update/GitUpdateProcess.java

index 5230fa4335d5c903a4e62d484363ad31bd808dbc..24aea2fefd3e22459087111b1c662926d3a38a41 100644 (file)
  */
 package git4idea.checkin;
 
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationDisplayType;
+import com.intellij.ide.GeneralSettings;
 import com.intellij.notification.NotificationType;
-import com.intellij.notification.Notifications;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.progress.EmptyProgressIndicator;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
@@ -38,7 +37,6 @@ import com.intellij.ui.CheckboxTree;
 import com.intellij.ui.CheckedTreeNode;
 import com.intellij.ui.ColoredTreeCellRenderer;
 import com.intellij.ui.SimpleTextAttributes;
-import com.intellij.util.Function;
 import com.intellij.util.text.DateFormatUtil;
 import com.intellij.util.ui.UIUtil;
 import com.intellij.util.ui.tree.TreeUtil;
@@ -72,8 +70,7 @@ import java.util.*;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
-import static git4idea.ui.GitUIUtil.notifyError;
-import static git4idea.ui.GitUIUtil.notifyImportantError;
+import static git4idea.ui.GitUIUtil.*;
 
 /**
  * The dialog that allows pushing active branches.
@@ -97,6 +94,8 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
   private JRadioButton myShelveRadioButton;
   private GitVcs myVcs;
   private static final Logger LOG = Logger.getInstance(GitPushActiveBranchesDialog.class.getName());
+  private final GeneralSettings myGeneralSettings;
+  private final ProjectManagerEx myProjectManager;
 
   /**
    * A modification of Runnable with the roots-parameter.
@@ -117,6 +116,8 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
     myVcs = GitVcs.getInstance(project);
     myProject = project;
     myVcsRoots = vcsRoots;
+    myGeneralSettings = GeneralSettings.getInstance();
+    myProjectManager = ProjectManagerEx.getInstanceEx();
 
     updateTree(roots, null);
     updateUI();
@@ -255,7 +256,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
 
           final List<Root> roots = loadRoots(myProject, myVcsRoots, exceptions, true); // fetch
           if (!exceptions.isEmpty()) {
-            notifyException("Failed to fetch", exceptions);
+            notifyMessage(myProject, "Failed to fetch", null, NotificationType.ERROR, true, exceptions);
             return;
           }
           updateTree(roots, rebaseInfo.uncheckedCommits);
@@ -263,34 +264,17 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
           if (isRebaseNeeded()) {
             executeRebase(exceptions, rebaseInfo);
             if (!exceptions.isEmpty()) {
-              notifyException("Failed to rebase", exceptions);
+              notifyMessage(myProject, "Failed to rebase", null, NotificationType.ERROR, true, exceptions);
               return;
             }
             GitUtil.refreshFiles(myProject, rebaseInfo.roots);
           }
         }
-        notifyException("Failed to push", pushExceptions);
+        notifyMessage(myProject, "Failed to push", "Update project and push again", NotificationType.ERROR, true, pushExceptions);
       }
     });
   }
 
-  /**
-   * Notifies about errors during background rebase & push tasks.
-   */
-  private void notifyException(String title, Collection<VcsException> exceptions) {
-    String content = StringUtil.join(exceptions, new Function<VcsException, String>() {
-      @Override public String fun(VcsException e) {
-        return e.getLocalizedMessage();
-      }
-    }, "<br/>");
-    if (StringUtil.isEmptyOrSpaces(content)) {
-      content = title;
-    }
-    LOG.info(title + " || " + content);
-    Notifications.Bus.notify(new Notification(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, content, NotificationType.ERROR),
-                             NotificationDisplayType.STICKY_BALLOON, myProject);
-  }
-
   /**
    * Pushes selected commits synchronously in foreground.
    */
@@ -445,7 +429,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
       rebaseInfo = collectRebaseInfo();
       return reorderCommitsIfNeeded(rebaseInfo);
     } else {
-      GitUIUtil.notifyMessage(myProject, "Commits weren't pushed", "Rebase failed.", NotificationType.WARNING, true, null);
+      notifyMessage(myProject, "Commits weren't pushed", "Rebase failed.", NotificationType.WARNING, true, null);
       return false;
     }
 
@@ -456,14 +440,28 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
       return true;
     }
 
-    ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
     ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
     if (progressIndicator == null) {
       progressIndicator = new EmptyProgressIndicator();
     }
     String stashMessage = "Uncommitted changes before rebase operation at " + DateFormatUtil.formatDateTime(Clock.getTime());
     GitChangesSaver saver = rebaseInfo.policy == GitVcsSettings.UpdateChangesPolicy.SHELVE ? new GitShelveChangesSaver(myProject, progressIndicator, stashMessage) : new GitStashChangesSaver(myProject, progressIndicator, stashMessage);
-    projectManager.blockReloadingProjectOnExternalChanges();
+
+    final boolean saveOnFrameDeactivation = myGeneralSettings.isSaveOnFrameDeactivation();
+    final boolean syncOnFrameDeactivation = myGeneralSettings.isSyncOnFrameActivation();
+    myProjectManager.blockReloadingProjectOnExternalChanges();
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override public void run() {
+        ApplicationManager.getApplication().runWriteAction(new Runnable() {
+          @Override public void run() {
+            FileDocumentManager.getInstance().saveAllDocuments();
+            myGeneralSettings.setSaveOnFrameDeactivation(false);
+            myGeneralSettings.setSyncOnFrameActivation(false);
+          }
+        });
+      }
+    });
+
     try {
       final Set<VirtualFile> rootsToReorder = rebaseInfo.reorderedCommits.keySet();
       saver.saveLocalChanges(rootsToReorder);
@@ -494,8 +492,8 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
           }
 
         } catch (VcsException e) {
-          GitUIUtil.notifyMessage(myProject, "Commits weren't pushed", "Failed to reorder commits", NotificationType.WARNING, true,
-                                  Collections.singleton(e));
+          notifyMessage(myProject, "Commits weren't pushed", "Failed to reorder commits", NotificationType.WARNING, true,
+                        Collections.singleton(e));
         } finally {
           try {
             saver.restoreLocalChanges();
@@ -511,7 +509,9 @@ public class GitPushActiveBranchesDialog extends DialogWrapper {
                   "Tried to save uncommitted changes in " + saver.getSaverName() + " before update, but failed with an error.<br/>" +
                   "Update was cancelled.", true, e);
     } finally {
-      projectManager.unblockReloadingProjectOnExternalChanges();
+      myProjectManager.unblockReloadingProjectOnExternalChanges();
+      myGeneralSettings.setSaveOnFrameDeactivation(saveOnFrameDeactivation);
+      myGeneralSettings.setSyncOnFrameActivation(syncOnFrameDeactivation);
     }
     return false;
   }
index b9494a38f0e9f5a3f4b2d8eca7a266146119634c..52a7ad51784792066571ebe5f532da2d72dfbb47 100644 (file)
@@ -53,8 +53,11 @@ public class GitUIUtil {
    */
   private GitUIUtil() { }
 
-  public static void notifyMessages(Project project, String title, String description, NotificationType type, boolean important, @Nullable Collection<String> messages) {
-    String desc = description.replace("\n", "<br/>");
+  public static void notifyMessages(Project project, @Nullable String title, @Nullable String description, NotificationType type, boolean important, @Nullable Collection<String> messages) {
+    if (StringUtil.isEmptyOrSpaces(title)) {
+      title = description;
+    }
+    String desc = (description != null ? description.replace("\n", "<br/>") : "");
     if (messages != null && !messages.isEmpty()) {
       desc += "<hr/>" + StringUtil.join(messages, "<br/>");
     }
@@ -62,7 +65,7 @@ public class GitUIUtil {
     Notifications.Bus.notify(new Notification(id, title, desc, type), project);
   }
 
-  public static void notifyMessage(Project project, String title, String description, NotificationType type, boolean important, @Nullable Collection<VcsException> errors) {
+  public static void notifyMessage(Project project, @Nullable String title, @Nullable String description, NotificationType type, boolean important, @Nullable Collection<VcsException> errors) {
     Collection<String> errorMessages;
     if (errors == null) {
       errorMessages = null;
index dcb7b597ae9549ac9635cc6b77fafaf08ba049a4..c386bb81e3bfde147cc4ef76b4a48cadef1fae8c 100644 (file)
  */
 package git4idea.update;
 
+import com.intellij.ide.GeneralSettings;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ex.ProjectManagerEx;
@@ -24,6 +27,7 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.update.UpdatedFiles;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.text.DateFormatUtil;
+import com.intellij.util.ui.UIUtil;
 import git4idea.GitBranch;
 import git4idea.branch.GitBranchPair;
 import git4idea.merge.GitMergeConflictResolver;
@@ -54,6 +58,7 @@ public class GitUpdateProcess {
   private final GitChangesSaver mySaver;
 
   private final Map<VirtualFile, GitBranchPair> myTrackedBranches = new HashMap<VirtualFile, GitBranchPair>();
+  private GeneralSettings myGeneralSettings;
 
   public GitUpdateProcess(@NotNull Project project,
                           @NotNull ProgressIndicator progressIndicator,
@@ -66,6 +71,7 @@ public class GitUpdateProcess {
     myMerger = new GitMerger(myProject);
     mySaver = GitChangesSaver.getSaver(myProject, myProgressIndicator,
       "Uncommitted changes before update operation at " + DateFormatUtil.formatDateTime(Clock.getTime()));
+    myGeneralSettings = GeneralSettings.getInstance();
   }
 
   /**
@@ -76,35 +82,72 @@ public class GitUpdateProcess {
     return update(false);
   }
 
+  /**
+   * Perform update on all roots.
+   * 0. Blocks reloading project on external change, saving/syncing on frame deactivation.
+   * 1. Checks if update is possible (rebase/merge in progress, no tracked branches...) and provides merge dialog to solve problems.
+   * 2. Finds updaters to use (merge or rebase).
+   * 3. Preserves local changes if needed (not needed for merge sometimes).
+   * 4. Updates via 'git pull' or equivalent.
+   * 5. Restores local changes if update completed or failed with error. If update is incomplete, i.e. some unmerged files remain,
+   * local changes are not restored.
+   * @param forceRebase
+   * @return
+   */
   public boolean update(boolean forceRebase) {
-    LOG.info("update started");
+    LOG.info("update started|" + (forceRebase ? " force rebase" : ""));
 
+    final boolean saveOnFrameDeactivation = myGeneralSettings.isSaveOnFrameDeactivation();
+    final boolean syncOnFrameDeactivation = myGeneralSettings.isSyncOnFrameActivation();
     myProjectManager.blockReloadingProjectOnExternalChanges();
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override public void run() {
+        ApplicationManager.getApplication().runWriteAction(new Runnable() {
+          @Override public void run() {
+            FileDocumentManager.getInstance().saveAllDocuments();
+            myGeneralSettings.setSaveOnFrameDeactivation(false);
+            myGeneralSettings.setSyncOnFrameActivation(false);
+          }
+        });
+      }
+    });
+
     try {
       // check if update is possible
       if (checkRebaseInProgress() || checkMergeInProgress() || checkUnmergedFiles()) { return false; }
       if (!checkTrackedBranchesConfigured()) { return false; }
 
-      // define updaters for each root
-      Collection<VirtualFile> rootsToSave = new HashSet<VirtualFile>(1);
+      // define updaters for roots
+      final Map<VirtualFile, GitUpdater> updaters = new HashMap<VirtualFile, GitUpdater>();
       for (VirtualFile root : myRoots) {
-        final GitUpdater updater = forceRebase ? GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles) :
-          new GitRebaseUpdater(myProject, root, this, myProgressIndicator, myUpdatedFiles);
+        final GitUpdater updater = forceRebase
+                                   ? new GitRebaseUpdater(myProject, root, this, myProgressIndicator, myUpdatedFiles)
+                                   : GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles);
+        updaters.put(root, updater);
+        LOG.info("update| root=" + root + " ,updater=" + updater);
+      }
+
+      // save local changes if needed (update via merge may perform without saving).
+      final Collection<VirtualFile> rootsToSave = new HashSet<VirtualFile>(1);
+      for (Map.Entry<VirtualFile, GitUpdater> entry : updaters.entrySet()) {
+        VirtualFile root = entry.getKey();
+        GitUpdater updater = entry.getValue();
         if (updater.isSaveNeeded()) {
           rootsToSave.add(root);
+          LOG.info("update| root " + root + " needs save");
         }
       }
-
       mySaver.saveLocalChanges(rootsToSave);
 
       // update each root
       boolean incomplete = false;
       boolean success = true;
-      for (final VirtualFile root : myRoots) {
+      for (Map.Entry<VirtualFile, GitUpdater> entry : updaters.entrySet()) {
+        VirtualFile root = entry.getKey();
+        GitUpdater updater = entry.getValue();
         try {
-          final GitUpdater updater = forceRebase ? GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles) :
-            new GitRebaseUpdater(myProject, root, this, myProgressIndicator, myUpdatedFiles);
           GitUpdateResult res = updater.update();
+          LOG.info("updating root " + root + " finished: " + res);
           if (res == GitUpdateResult.INCOMPLETE) {
             incomplete = true;
           }
@@ -135,6 +178,8 @@ public class GitUpdateProcess {
                   "Update was cancelled.", true, e);
     } finally {
       myProjectManager.unblockReloadingProjectOnExternalChanges();
+      myGeneralSettings.setSaveOnFrameDeactivation(saveOnFrameDeactivation);
+      myGeneralSettings.setSyncOnFrameActivation(syncOnFrameDeactivation);
     }
     return false;
   }
@@ -157,6 +202,7 @@ public class GitUpdateProcess {
       try {
         final GitBranch branch = GitBranch.current(myProject, root);
         if (branch == null) {
+          LOG.info("checkTrackedBranchesConfigured current branch is null");
           notifyImportantError(myProject, "Can't update: no current branch",
                                "You are in 'detached HEAD' state, which means that you're not on any branch.<br/>" +
                                "Checkout a branch to make update possible.");
@@ -165,6 +211,7 @@ public class GitUpdateProcess {
         final GitBranch tracked = branch.tracked(myProject, root);
         if (tracked == null) {
           final String branchName = branch.getName();
+          LOG.info("checkTrackedBranchesConfigured tracked branch is null for current branch " + branch);
           notifyImportantError(myProject, "Can't update: no tracked branch",
                                "No tracked branch configured for branch " + branchName +
                                "<br/>To make your branch track a remote branch call, for example,<br/>" +
@@ -173,6 +220,7 @@ public class GitUpdateProcess {
         }
         myTrackedBranches.put(root, new GitBranchPair(branch, tracked));
       } catch (VcsException e) {
+        LOG.info("checkTrackedBranchesConfigured ", e);
         notifyImportantError(myProject, "Can't update: error identifying tracked branch", e.getLocalizedMessage());
         return false;
       }
@@ -189,13 +237,14 @@ public class GitUpdateProcess {
     if (mergingRoots.isEmpty()) {
       return false;
     }
+    LOG.info("checkMergeInProgress mergingRoots: " + mergingRoots);
 
     return !new GitMergeConflictResolver(myProject, false, "You have unfinished merge. These conflicts must be resolved before update.", "Can't update", "") {
       @Override protected boolean proceedAfterAllMerged() throws VcsException {
         myMerger.mergeCommit(mergingRoots);
         return true;
       }
-    }.mergeFiles(mergingRoots);
+    }.merge(mergingRoots);
   }
 
   /**
@@ -206,12 +255,13 @@ public class GitUpdateProcess {
     try {
       Collection<VirtualFile> unmergedFiles = GitMergeUtil.getUnmergedFiles(myProject, myRoots);
       if (!unmergedFiles.isEmpty()) {
+        LOG.info("checkUnmergedFiles unmergedFiles: " + unmergedFiles);
         return !new GitMergeConflictResolver(myProject, false, "Unmerged files detected. These conflicts must be resolved before update.", "Can't update", "") {
           @Override protected boolean proceedAfterAllMerged() throws VcsException {
             myMerger.mergeCommit(myRoots);
             return true;
           }
-        }.mergeFiles(myRoots);
+        }.merge(myRoots);
       }
     } catch (VcsException e) {
       LOG.info("areUnmergedFiles. Couldn't get unmerged files", e);
@@ -229,6 +279,7 @@ public class GitUpdateProcess {
     if (rebasingRoots.isEmpty()) {
       return false;
     }
+    LOG.info("checkRebaseInProgress rebasingRoots: " + rebasingRoots);
 
     return !new GitMergeConflictResolver(myProject, true, "You have unfinished rebase process. These conflicts must be resolved before update.", "Can't update",
                                          "Then you may <b>continue rebase</b>. <br/> You also may <b>abort rebase</b> to restore the original branch and stop rebasing.") {
@@ -239,7 +290,7 @@ public class GitUpdateProcess {
       @Override protected boolean proceedAfterAllMerged() {
         return rebaser.continueRebase(rebasingRoots);
       }
-    }.mergeFiles(rebasingRoots);
+    }.merge(rebasingRoots);
   }
 
 }