Git: split clone into atomic operations.
authorKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Thu, 14 Apr 2011 08:48:00 +0000 (12:48 +0400)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Thu, 14 Apr 2011 08:48:00 +0000 (12:48 +0400)
Split clone to mkdir, init, add remote, fetch and checkout.
Add notifications in the case of error.

Reason: extract the remote FETCH operation, which will be handled by JGit for HTTP in futher changes.

plugins/git4idea/src/git4idea/Git.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitInit.java
plugins/git4idea/src/git4idea/checkout/GitCheckoutProvider.java
plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java
plugins/github/src/org/jetbrains/plugins/github/GithubCheckoutProvider.java

diff --git a/plugins/git4idea/src/git4idea/Git.java b/plugins/git4idea/src/git4idea/Git.java
new file mode 100644 (file)
index 0000000..9d1c501
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.GitCommand;
+import git4idea.commands.GitHandlerUtil;
+import git4idea.commands.GitLineHandler;
+
+/**
+ * Low level layer of Git commands.
+ *
+ * @author Kirill Likhodedov
+ */
+public class Git {
+
+  /**
+   * Calls 'git init' on the specified directory.
+   */
+  public static void init(Project project, VirtualFile root) throws VcsException {
+    GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT);
+    h.setNoSSH(true);
+    GitHandlerUtil.runInCurrentThread(h, null);
+    if (!h.errors().isEmpty()) {
+      throw h.errors().get(0);
+    }
+  }
+
+}
index b234fb22d74093bd37b41e4c49ce388f5ff8a594..ae31838a3d88cca4122248b00c4f34e25c7c5670 100644 (file)
@@ -28,12 +28,11 @@ import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
 import com.intellij.openapi.vcs.VcsDirectoryMapping;
+import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.Git;
 import git4idea.GitUtil;
 import git4idea.GitVcs;
-import git4idea.commands.GitCommand;
-import git4idea.commands.GitHandlerUtil;
-import git4idea.commands.GitLineHandler;
 import git4idea.i18n.GitBundle;
 import git4idea.ui.GitUIUtil;
 import org.jetbrains.annotations.NotNull;
@@ -73,13 +72,14 @@ public class GitInit extends DumbAwareAction {
         return;
       }
     }
-    GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT);
-    h.setNoSSH(true);
-    GitHandlerUtil.doSynchronously(h, GitBundle.getString("initializing.title"), h.printableCommandLine());
-    if (!h.errors().isEmpty()) {
-      GitUIUtil.showOperationErrors(project, h.errors(), "git init");
+
+    try {
+      Git.init(project, root);
+    } catch (VcsException ex) {
+      GitUIUtil.showOperationErrors(project, Collections.singleton(ex), "git init");
       return;
     }
+
     if (project.isDefault()) return;
     int rc = Messages.showYesNoDialog(project, GitBundle.getString("init.add.root.message"), GitBundle.getString("init.add.root.title"),
                                       Messages.getQuestionIcon());
index ee5fe42d021766517f97b139b715be5268074269..269e0c843a62b7bc7954294c772aa214f340265d 100644 (file)
  */
 package git4idea.checkout;
 
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.CheckoutProvider;
+import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import git4idea.GitVcs;
+import com.intellij.vcsUtil.VcsUtil;
+import git4idea.Git;
 import git4idea.actions.BasicAction;
-import git4idea.commands.*;
-import git4idea.config.GitVersion;
+import git4idea.commands.GitCommand;
+import git4idea.commands.GitSimpleHandler;
 import git4idea.i18n.GitBundle;
 import git4idea.ui.GitUIUtil;
+import git4idea.update.GitFetcher;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Checkout provider for the Git
  */
 public class GitCheckoutProvider implements CheckoutProvider {
-  /**
-   * The version number since which "-v" options is supported.
-   */
-  // TODO check if they will actually support the switch in the released 1.6.0.5
-  private static final GitVersion VERBOSE_CLONE_SUPPORTED = new GitVersion(1, 6, 0, 5);
+
+  private static final Logger LOG = Logger.getInstance(GitCheckoutProvider.class);
+
+  public String getVcsName() {
+    return "_Git";
+  }
 
   public void doCheckout(@NotNull final Project project, @Nullable final Listener listener) {
     BasicAction.saveAll();
@@ -56,64 +64,110 @@ public class GitCheckoutProvider implements CheckoutProvider {
     final String sourceRepositoryURL = dialog.getSourceRepositoryURL();
     final String directoryName = dialog.getDirectoryName();
     final String parentDirectory = dialog.getParentDirectory();
-    checkout(project, listener, destinationParent, sourceRepositoryURL, directoryName, parentDirectory);
+    clone(project, listener, destinationParent, sourceRepositoryURL, directoryName, parentDirectory);
   }
 
-  public static void checkout(final Project project,
-                              final Listener listener,
-                              final VirtualFile destinationParent,
-                              final String sourceRepositoryURL,
-                              final String directoryName,
-                              final String parentDirectory) {
-    final GitLineHandler handler = getCloneHandler(project, sourceRepositoryURL, new File(parentDirectory), directoryName);
-    GitTask task = new GitTask(project, handler, GitBundle.message("cloning.repository", sourceRepositoryURL));
-    task.setProgressAnalyzer(new GitStandardProgressAnalyzer());
-    task.executeAsync(new GitTaskResultHandlerAdapter() {
+  public static void clone(final Project project,
+                           final Listener listener,
+                           final VirtualFile destinationParent,
+                           final String sourceRepositoryURL,
+                           final String directoryName,
+                           final String parentDirectory) {
+
+    final AtomicBoolean cloneResult = new AtomicBoolean();
+    new Task.Backgroundable(project, GitBundle.message("cloning.repository", sourceRepositoryURL)) {
       @Override
-      public void onSuccess() {
-          destinationParent.refresh(true, true, new Runnable() {
-            public void run() {
-              if (project.isOpen() && (!project.isDisposed()) && (!project.isDefault())) {
-                final VcsDirtyScopeManager mgr = VcsDirtyScopeManager.getInstance(project);
-                mgr.fileDirty(destinationParent);
-              }
-            }
-          });
-          listener.directoryCheckedOut(new File(parentDirectory, directoryName));
-          listener.checkoutCompleted();
+      public void run(@NotNull ProgressIndicator indicator) {
+        cloneResult.set(doClone(indicator, project, directoryName, parentDirectory, sourceRepositoryURL));
       }
 
       @Override
-      protected void onFailure() {
-        GitUIUtil.notifyGitErrors(project, "Couldn't clone", "Couldn't clone from " + sourceRepositoryURL, handler.errors());
+      public void onSuccess() {
+        if (!cloneResult.get()) {
+          return;
+        }
+
+        destinationParent.refresh(true, true, new Runnable() {
+          public void run() {
+            if (project.isOpen() && (!project.isDisposed()) && (!project.isDefault())) {
+              final VcsDirtyScopeManager mgr = VcsDirtyScopeManager.getInstance(project);
+              mgr.fileDirty(destinationParent);
+            }
+          }
+        });
+        listener.directoryCheckedOut(new File(parentDirectory, directoryName));
+        listener.checkoutCompleted();
       }
-    });
+    }.queue();
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  public String getVcsName() {
-    return "_Git";
+  private static boolean doClone(ProgressIndicator indicator, Project project, String directoryName, String parentDirectory, String sourceRepositoryURL) {
+    final VirtualFile root = mkdir(project, directoryName, parentDirectory);
+    if (root == null) { return false; }
+    if (!init(project, root)) { return false; }
+    if (!addRemote(project, root, sourceRepositoryURL)) { return false; }
+    if (!fetch(project, root, indicator)) { return false; }
+    return checkout(project, root);
   }
 
-  /**
-   * Prepare clone handler
-   *
-   * @param project    a project
-   * @param url        an url
-   * @param directory  a base directory
-   * @param name       a name to checkout
-   * @param originName origin name (ignored if null or empty string)
-   * @return a handler for clone operation
-   */
-  public static GitLineHandler getCloneHandler(Project project, final String url, final File directory, final String name) {
-    GitLineHandler handler = new GitLineHandler(project, directory, GitCommand.CLONE);
-    if (VERBOSE_CLONE_SUPPORTED.isOlderOrEqual(GitVcs.getInstance(project).getVersion())) {
-      handler.addParameters("-v");
+  private static @Nullable VirtualFile mkdir(Project project, String directoryName, String parentDirectory) {
+    final File dir = new File(parentDirectory, directoryName);
+    if (dir.exists()) {
+      GitUIUtil.notifyError(project, "Couldn't clone", "Directory <code>" + dir + "</code> already exists.");
+      return null;
+    }
+    if (!dir.mkdir()) {
+      GitUIUtil.notifyError(project, "Couldn't clone", "Can't create directory <code>" + dir + "</code>");
+      return null;
     }
-    handler.addParameters(url, name);
-    handler.addProgressParameter();
-    return handler;
+
+    return VcsUtil.getVirtualFileWithRefresh(dir);
   }
+
+  private static boolean init(Project project, VirtualFile root) {
+    try {
+      Git.init(project, root);
+    } catch (VcsException e) {
+      LOG.info("init ", e);
+      GitUIUtil.notifyError(project, "Couldn't clone", "Couldn't <code>git init</code> in <code>" + root.getPresentableUrl() + "</code>", true, e);
+      return false;
+    }
+    return true;
+  }
+
+  private static boolean addRemote(Project project, VirtualFile root, String remoteUrl) {
+    final GitSimpleHandler addRemoteHandler = new GitSimpleHandler(project, root, GitCommand.REMOTE);
+    addRemoteHandler.setNoSSH(true);
+    addRemoteHandler.addParameters("add", "origin", remoteUrl);
+    try {
+      addRemoteHandler.run();
+      return true;
+    }
+    catch (VcsException e) {
+      LOG.info("addRemote ", e);
+      GitUIUtil.notifyError(project, "Couldn't clone", "Couldn't add remote <code>" + remoteUrl + "</code>", true, e);
+      return false;
+    }
+  }
+
+  private static boolean fetch(Project project, VirtualFile root, ProgressIndicator indicator) {
+    return new GitFetcher(project, indicator).fetch(root);
+  }
+
+  private static boolean checkout(Project project, VirtualFile root) {
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.CHECKOUT);
+    h.setNoSSH(true);
+    h.addParameters("-b", "master", "origin/master");
+    try {
+      h.run();
+      return true;
+    }
+    catch (VcsException e) {
+      LOG.info("checkout ", e);
+      GitUIUtil.notifyError(project, "Clone not completed",
+                            "Couldn't checkout master branch. <br/>All changes were fetched to <code>" + root + "</code>.", true, e);
+      return false;
+    }
+  }
+
 }
index 5b16c138a6979cc9e8a2ba6c0357a752531feec0..3e9acd911347ffd01ba75d2bb12fbeedb298f486 100644 (file)
@@ -181,7 +181,7 @@ public class GitHandlerUtil {
    * @param handler         a handler to run
    * @param postStartAction an action that is executed
    */
-  static void runInCurrentThread(final GitHandler handler, @Nullable final Runnable postStartAction) {
+  public static void runInCurrentThread(final GitHandler handler, @Nullable final Runnable postStartAction) {
     handler.runInCurrentThread(postStartAction);
   }
 
index 4efad1a01bfd26ca03c97eeba09b85c5fc7be02e..8ec8f7b1edc47fdfc7537c878019ccb40f417090 100644 (file)
@@ -109,7 +109,7 @@ public class GithubCheckoutProvider implements CheckoutProvider {
     final String repositoryName = name;
     final String repositoryOwner = owner;
     final String checkoutUrl = host + repositoryOwner + "/" + repositoryName + ".git";
-    GitCheckoutProvider.checkout(project, listener, selectedPathFile, checkoutUrl, projectName, selectedPath);
+    GitCheckoutProvider.clone(project, listener, selectedPathFile, checkoutUrl, projectName, selectedPath);
   }
 
   @Override