Display git rename branch command in the VCS Console
[idea/community.git] / plugins / git4idea / src / git4idea / commands / GitImpl.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package git4idea.commands;
17
18 import com.google.common.annotations.VisibleForTesting;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Computable;
22 import com.intellij.openapi.util.Condition;
23 import com.intellij.openapi.util.Key;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.vcs.VcsException;
26 import com.intellij.openapi.vfs.VfsUtilCore;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.util.Function;
29 import com.intellij.util.ObjectUtils;
30 import com.intellij.util.containers.ContainerUtil;
31 import com.intellij.vcsUtil.VcsFileUtil;
32 import git4idea.GitVcs;
33 import git4idea.branch.GitRebaseParams;
34 import git4idea.config.GitVersionSpecialty;
35 import git4idea.rebase.GitInteractiveRebaseEditorHandler;
36 import git4idea.rebase.GitRebaseEditorService;
37 import git4idea.rebase.GitRebaseResumeMode;
38 import git4idea.repo.GitRemote;
39 import git4idea.repo.GitRepository;
40 import git4idea.reset.GitResetMode;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import java.io.File;
45 import java.util.*;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 import java.util.concurrent.atomic.AtomicInteger;
48 import java.util.concurrent.atomic.AtomicReference;
49
50 import static java.util.Collections.singleton;
51
52 /**
53  * Easy-to-use wrapper of common native Git commands.
54  * Most of them return result as {@link GitCommandResult}.
55  *
56  * @author Kirill Likhodedov
57  */
58 @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale")
59 public class GitImpl implements Git {
60
61   private static final Logger LOG = Logger.getInstance(Git.class);
62
63   public GitImpl() {
64   }
65
66   /**
67    * Calls 'git init' on the specified directory.
68    */
69   @NotNull
70   @Override
71   public GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) {
72     GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT);
73     for (GitLineHandlerListener listener : listeners) {
74       h.addLineListener(listener);
75     }
76     h.setSilent(false);
77     h.setStdoutSuppressed(false);
78     return run(h);
79   }
80
81   /**
82    * <p>Queries Git for the unversioned files in the given paths. </p>
83    * <p>Ignored files are left ignored, i. e. no information is returned about them (thus this method may also be used as a
84    *    ignored files checker.</p>
85    *
86    * @param files files that are to be checked for the unversioned files among them.
87    *              <b>Pass <code>null</code> to query the whole repository.</b>
88    * @return Unversioned not ignored files from the given scope.
89    */
90   @Override
91   @NotNull
92   public Set<VirtualFile> untrackedFiles(@NotNull Project project, @NotNull VirtualFile root,
93                                          @Nullable Collection<VirtualFile> files) throws VcsException {
94     final Set<VirtualFile> untrackedFiles = new HashSet<>();
95
96     if (files == null) {
97       untrackedFiles.addAll(untrackedFilesNoChunk(project, root, null));
98     }
99     else {
100       for (List<String> relativePaths : VcsFileUtil.chunkFiles(root, files)) {
101         untrackedFiles.addAll(untrackedFilesNoChunk(project, root, relativePaths));
102       }
103     }
104
105     return untrackedFiles;
106   }
107
108   // relativePaths are guaranteed to fit into command line length limitations.
109   @Override
110   @NotNull
111   public Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project,
112                                                        @NotNull VirtualFile root,
113                                                        @Nullable List<String> relativePaths)
114     throws VcsException {
115     final Set<VirtualFile> untrackedFiles = new HashSet<>();
116     GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LS_FILES);
117     h.setSilent(true);
118     h.addParameters("--exclude-standard", "--others", "-z");
119     h.endOptions();
120     if (relativePaths != null) {
121       h.addParameters(relativePaths);
122     }
123
124     final String output = h.run();
125     if (StringUtil.isEmptyOrSpaces(output)) {
126       return untrackedFiles;
127     }
128
129     for (String relPath : output.split("\u0000")) {
130       VirtualFile f = root.findFileByRelativePath(relPath);
131       if (f == null) {
132         // files was created on disk, but VirtualFile hasn't yet been created,
133         // when the GitChangeProvider has already been requested about changes.
134         LOG.info(String.format("VirtualFile for path [%s] is null", relPath));
135       } else {
136         untrackedFiles.add(f);
137       }
138     }
139
140     return untrackedFiles;
141   }
142
143   @Override
144   @NotNull
145   public GitCommandResult clone(@NotNull final Project project, @NotNull final File parentDirectory, @NotNull final String url,
146                                 @NotNull final String clonedDirectoryName, @NotNull final GitLineHandlerListener... listeners) {
147     return run(new Computable<GitLineHandler>() {
148       @Override
149       public GitLineHandler compute() {
150         GitLineHandler handler = new GitLineHandler(project, parentDirectory, GitCommand.CLONE);
151         handler.setStdoutSuppressed(false);
152         handler.setUrl(url);
153         handler.addParameters("--progress");
154         handler.addParameters(url);
155         handler.endOptions();
156         handler.addParameters(clonedDirectoryName);
157         addListeners(handler, listeners);
158         return handler;
159       }
160     });
161   }
162
163   @NotNull
164   @Override
165   public GitCommandResult config(@NotNull GitRepository repository, String... params) {
166     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CONFIG);
167     h.addParameters(params);
168     return run(h);
169   }
170
171   @NotNull
172   @Override
173   public GitCommandResult diff(@NotNull GitRepository repository, @NotNull List<String> parameters, @NotNull String range) {
174     final GitLineHandler diff = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.DIFF);
175     diff.addParameters(parameters);
176     diff.addParameters(range);
177     diff.setStdoutSuppressed(true);
178     diff.setStderrSuppressed(true);
179     diff.setSilent(true);
180     return run(diff);
181   }
182
183   @NotNull
184   @Override
185   public GitCommandResult checkAttr(@NotNull final GitRepository repository,
186                                     @NotNull final Collection<String> attributes,
187                                     @NotNull Collection<VirtualFile> files) {
188     List<List<String>> listOfPaths = VcsFileUtil.chunkFiles(repository.getRoot(), files);
189     return runAll(ContainerUtil.map(listOfPaths, new Function<List<String>, Computable<GitCommandResult>>() {
190       @Override
191       public Computable<GitCommandResult> fun(final List<String> relativePaths) {
192         return new Computable<GitCommandResult>() {
193           @Override
194           public GitCommandResult compute() {
195             final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECK_ATTR);
196             h.addParameters(new ArrayList<>(attributes));
197             h.endOptions();
198             h.addParameters(relativePaths);
199             return run(h);
200           }
201         };
202       }
203     }));
204   }
205
206   @NotNull
207   @Override
208   public GitCommandResult stashSave(@NotNull GitRepository repository, @NotNull String message) {
209     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.STASH);
210     h.addParameters("save");
211     h.addParameters(message);
212     return run(h);
213   }
214
215   @NotNull
216   @Override
217   public GitCommandResult stashPop(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
218     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.STASH);
219     handler.addParameters("pop");
220     addListeners(handler, listeners);
221     return run(handler);
222   }
223
224   @Override
225   @NotNull
226   public GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge,
227                                 @Nullable List<String> additionalParams, @NotNull GitLineHandlerListener... listeners) {
228     final GitLineHandler mergeHandler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.MERGE);
229     mergeHandler.setSilent(false);
230     mergeHandler.addParameters(branchToMerge);
231     if (additionalParams != null) {
232       mergeHandler.addParameters(additionalParams);
233     }
234     for (GitLineHandlerListener listener : listeners) {
235       mergeHandler.addLineListener(listener);
236     }
237     return run(mergeHandler);
238   }
239
240
241   /**
242    * {@code git checkout &lt;reference&gt;} <br/>
243    * {@code git checkout -b &lt;newBranch&gt; &lt;reference&gt;}
244    */
245   @NotNull
246   @Override
247   public GitCommandResult checkout(@NotNull GitRepository repository,
248                                    @NotNull String reference,
249                                    @Nullable String newBranch,
250                                    boolean force,
251                                    boolean detach,
252                                    @NotNull GitLineHandlerListener... listeners) {
253     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECKOUT);
254     h.setSilent(false);
255     h.setStdoutSuppressed(false);
256     if (force) {
257       h.addParameters("--force");
258     }
259     if (newBranch == null) { // simply checkout
260       h.addParameters(detach ? reference + "^0" : reference); // we could use `--detach` here, but it is supported only since 1.7.5.
261     }
262     else { // checkout reference as new branch
263       h.addParameters("-b", newBranch, reference);
264     }
265     h.endOptions();
266     for (GitLineHandlerListener listener : listeners) {
267       h.addLineListener(listener);
268     }
269     return run(h);
270   }
271
272   /**
273    * {@code git checkout -b &lt;branchName&gt;}
274    */
275   @NotNull
276   @Override
277   public GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName,
278                                                    @Nullable GitLineHandlerListener listener) {
279     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECKOUT.readLockingCommand());
280     h.setSilent(false);
281     h.setStdoutSuppressed(false);
282     h.addParameters("-b");
283     h.addParameters(branchName);
284     if (listener != null) {
285       h.addLineListener(listener);
286     }
287     return run(h);
288   }
289
290   @NotNull
291   @Override
292   public GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName,
293                                        @Nullable GitLineHandlerListener listener, @NotNull String reference) {
294     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.TAG);
295     h.setSilent(false);
296     h.addParameters(tagName);
297     if (!reference.isEmpty()) {
298       h.addParameters(reference);
299     }
300     if (listener != null) {
301       h.addLineListener(listener);
302     }
303     return run(h);
304   }
305
306   /**
307    * {@code git branch -d <reference>} or {@code git branch -D <reference>}
308    */
309   @NotNull
310   @Override
311   public GitCommandResult branchDelete(@NotNull GitRepository repository,
312                                               @NotNull String branchName,
313                                               boolean force,
314                                               @NotNull GitLineHandlerListener... listeners) {
315     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
316     h.setSilent(false);
317     h.setStdoutSuppressed(false);
318     h.addParameters(force ? "-D" : "-d");
319     h.addParameters(branchName);
320     for (GitLineHandlerListener listener : listeners) {
321       h.addLineListener(listener);
322     }
323     return run(h);
324   }
325
326   /**
327    * Get branches containing the commit.
328    * {@code git branch --contains <commit>}
329    */
330   @Override
331   @NotNull
332   public GitCommandResult branchContains(@NotNull GitRepository repository, @NotNull String commit) {
333     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
334     h.addParameters("--contains", commit);
335     return run(h);
336   }
337
338   @Override
339   @NotNull
340   public GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName, @NotNull String startPoint) {
341     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
342     h.setStdoutSuppressed(false);
343     h.addParameters(branchName);
344     h.addParameters(startPoint);
345     return run(h);
346   }
347
348   @NotNull
349   @Override
350   public GitCommandResult renameBranch(@NotNull GitRepository repository,
351                                        @NotNull String currentName,
352                                        @NotNull String newName,
353                                        @NotNull GitLineHandlerListener... listeners) {
354     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
355     h.setSilent(false);
356     h.setStdoutSuppressed(false);
357     h.addParameters("-m", currentName, newName);
358     return run(h);
359   }
360
361   @Override
362   @NotNull
363   public GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target,
364                                 @NotNull GitLineHandlerListener... listeners) {
365     return reset(repository, mode.getArgument(), target, listeners);
366   }
367
368   @Override
369   @NotNull
370   public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) {
371     return reset(repository, "--merge", revision);
372   }
373
374   @NotNull
375   private static GitCommandResult reset(@NotNull GitRepository repository, @NotNull String argument, @Nullable String target,
376                                         @NotNull GitLineHandlerListener... listeners) {
377     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET);
378     handler.addParameters(argument);
379     if (target != null) {
380       handler.addParameters(target);
381     }
382     addListeners(handler, listeners);
383     return run(handler);
384   }
385
386   /**
387    * Returns the last (tip) commit on the given branch.<br/>
388    * {@code git rev-list -1 <branchName>}
389    */
390   @NotNull
391   @Override
392   public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
393     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST);
394     h.addParameters("-1");
395     h.addParameters(branchName);
396     return run(h);
397   }
398
399   @Override
400   @NotNull
401   public GitCommandResult push(@NotNull GitRepository repository,
402                                @NotNull String remote,
403                                @Nullable String url,
404                                @NotNull String spec,
405                                boolean updateTracking,
406                                @NotNull GitLineHandlerListener... listeners) {
407     return doPush(repository, remote, singleton(url), spec, false, updateTracking, null, listeners);
408   }
409
410   @Override
411   @NotNull
412   public GitCommandResult push(@NotNull GitRepository repository,
413                                @NotNull GitRemote remote,
414                                @NotNull String spec,
415                                boolean force,
416                                boolean updateTracking,
417                                @Nullable String tagMode,
418                                GitLineHandlerListener... listeners) {
419     return doPush(repository, remote.getName(), remote.getPushUrls(), spec, force, updateTracking, tagMode, listeners);
420   }
421
422   @NotNull
423   private GitCommandResult doPush(@NotNull final GitRepository repository,
424                                   @NotNull final String remoteName,
425                                   @NotNull final Collection<String> remoteUrls,
426                                   @NotNull final String spec,
427                                   final boolean force,
428                                   final boolean updateTracking,
429                                   @Nullable final String tagMode,
430                                   @NotNull final GitLineHandlerListener... listeners) {
431     return runCommand(new Computable<GitLineHandler>() {
432       @Override
433       public GitLineHandler compute() {
434         final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.PUSH);
435         h.setUrls(remoteUrls);
436         h.setSilent(false);
437         h.setStdoutSuppressed(false);
438         addListeners(h, listeners);
439         h.addProgressParameter();
440         h.addParameters("--porcelain");
441         h.addParameters(remoteName);
442         h.addParameters(spec);
443         if (updateTracking) {
444           h.addParameters("--set-upstream");
445         }
446         if (force) {
447           h.addParameters("--force");
448         }
449         if (tagMode != null) {
450           h.addParameters(tagMode);
451         }
452         return h;
453       }
454     });
455   }
456
457   @NotNull
458   @Override
459   public GitCommandResult show(@NotNull GitRepository repository, @NotNull String... params) {
460     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.SHOW);
461     handler.addParameters(params);
462     return run(handler);
463   }
464
465   @Override
466   @NotNull
467   public GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
468                                      @NotNull GitLineHandlerListener... listeners) {
469     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHERRY_PICK);
470     handler.addParameters("-x");
471     if (!autoCommit) {
472       handler.addParameters("-n");
473     }
474     handler.addParameters(hash);
475     addListeners(handler, listeners);
476     handler.setSilent(false);
477     handler.setStdoutSuppressed(false);
478     return run(handler);
479   }
480
481   @NotNull
482   @Override
483   public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
484     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.LS_FILES);
485     h.addParameters("--unmerged");
486     h.setSilent(true);
487     return run(h);
488   }
489
490   /**
491    * Fetch remote branch
492    * {@code git fetch <remote> <params>}
493    */
494   @Override
495   @NotNull
496   public GitCommandResult fetch(@NotNull final GitRepository repository,
497                                 @NotNull final GitRemote remote,
498                                 @NotNull final List<GitLineHandlerListener> listeners,
499                                 final String... params) {
500     return runCommand(new Computable<GitLineHandler>() {
501       @Override
502       public GitLineHandler compute() {
503         final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.FETCH);
504         h.setSilent(false);
505         h.setStdoutSuppressed(false);
506         h.setUrls(remote.getUrls());
507         h.addParameters(remote.getName());
508         h.addParameters(params);
509         h.addProgressParameter();
510         GitVcs vcs = GitVcs.getInstance(repository.getProject());
511         if (vcs != null && GitVersionSpecialty.SUPPORTS_FETCH_PRUNE.existsIn(vcs.getVersion())) {
512           h.addParameters("--prune");
513         }
514         addListeners(h, listeners);
515         return h;
516       }
517     });
518   }
519
520   @NotNull
521   @Override
522   public GitCommandResult addRemote(@NotNull GitRepository repository, @NotNull String name, @NotNull String url) {
523     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
524     h.addParameters("add", name, url);
525     return run(h);
526   }
527
528   @NotNull
529   @Override
530   public GitCommandResult removeRemote(@NotNull GitRepository repository, @NotNull GitRemote remote) {
531     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
532     h.addParameters("remove", remote.getName());
533     return run(h);
534   }
535
536   @NotNull
537   @Override
538   public GitCommandResult renameRemote(@NotNull GitRepository repository, @NotNull String oldName, @NotNull String newName) {
539     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
540     h.addParameters("rename", oldName, newName);
541     return run(h);
542   }
543
544   @NotNull
545   @Override
546   public GitCommandResult setRemoteUrl(@NotNull GitRepository repository, @NotNull String remoteName, @NotNull String newUrl) {
547     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
548     h.addParameters("set-url", remoteName, newUrl);
549     return run(h);
550   }
551
552   @NotNull
553   @Override
554   public GitCommandResult lsRemote(@NotNull final Project project,
555                                    @NotNull final File workingDir,
556                                    @NotNull final String url) {
557     return doLsRemote(project, workingDir, url, singleton(url));
558   }
559
560   @NotNull
561   @Override
562   public GitCommandResult lsRemote(@NotNull Project project,
563                                    @NotNull VirtualFile workingDir,
564                                    @NotNull GitRemote remote,
565                                    String... additionalParameters) {
566     return doLsRemote(project, VfsUtilCore.virtualToIoFile(workingDir), remote.getName(), remote.getUrls(), additionalParameters);
567   }
568
569   @NotNull
570   @Override
571   public GitCommandResult remotePrune(@NotNull final GitRepository repository, @NotNull final GitRemote remote) {
572     return run(new Computable<GitLineHandler>() {
573       @Override
574       public GitLineHandler compute() {
575         GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE.writeLockingCommand());
576         h.setStdoutSuppressed(false);
577         h.addParameters("prune");
578         h.addParameters(remote.getName());
579         h.setUrls(remote.getUrls());
580         return h;
581       }
582     });
583   }
584
585   @NotNull
586   @Override
587   public GitCommandResult rebase(@NotNull GitRepository repository,
588                                  @NotNull GitRebaseParams parameters,
589                                  @NotNull GitLineHandlerListener... listeners) {
590     Project project = repository.getProject();
591     VirtualFile root = repository.getRoot();
592     GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
593     handler.addParameters(parameters.asCommandLineArguments());
594     addListeners(handler, listeners);
595     return parameters.isInteractive() ? runWithEditor(project, root, handler, true) : run(handler);
596   }
597
598   @NotNull
599   @Override
600   public GitCommandResult rebaseAbort(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
601     GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REBASE);
602     handler.addParameters("--abort");
603     addListeners(handler, listeners);
604     return run(handler);
605   }
606
607   @NotNull
608   @Override
609   public GitCommandResult rebaseContinue(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
610     return rebaseResume(repository, GitRebaseResumeMode.CONTINUE, listeners);
611   }
612
613   @NotNull
614   @Override
615   public GitCommandResult rebaseSkip(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
616     return rebaseResume(repository, GitRebaseResumeMode.SKIP, listeners);
617   }
618
619   @NotNull
620   private GitCommandResult rebaseResume(@NotNull GitRepository repository,
621                                         @NotNull GitRebaseResumeMode rebaseMode,
622                                         @NotNull GitLineHandlerListener[] listeners) {
623     Project project = repository.getProject();
624     VirtualFile root = repository.getRoot();
625     GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
626     handler.addParameters(rebaseMode.asCommandLineArgument());
627     addListeners(handler, listeners);
628     return runWithEditor(project, root, handler, false);
629   }
630
631   @NotNull
632   private GitCommandResult runWithEditor(@NotNull Project project,
633                                          @NotNull VirtualFile root,
634                                          @NotNull GitLineHandler handler,
635                                          boolean commitListAware) {
636     GitInteractiveRebaseEditorHandler editor = configureEditor(project, root, handler, commitListAware);
637     try {
638       GitCommandResult result = run(handler);
639       return editor.wasEditorCancelled() ? toCancelledResult(result) : result;
640     }
641     finally {
642       editor.close();
643     }
644   }
645
646   @NotNull
647   private static GitCommandResult toCancelledResult(@NotNull GitCommandResult result) {
648     int exitCode = result.getExitCode() == 0 ? 1 : result.getExitCode();
649     return new GitCommandResult(false, exitCode, result.getErrorOutput(), result.getOutput(), result.getException()) {
650       @Override
651       public boolean cancelled() {
652         return true;
653       }
654     };
655   }
656
657   @VisibleForTesting
658   @NotNull
659   protected GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project,
660                                                               @NotNull VirtualFile root,
661                                                               @NotNull GitLineHandler handler,
662                                                               boolean commitListAware) {
663     GitRebaseEditorService service = GitRebaseEditorService.getInstance();
664     GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, handler);
665     if (!commitListAware) {
666       editor.setRebaseEditorShown();
667     }
668     service.configureHandler(handler, editor.getHandlerNo());
669     return editor;
670   }
671
672   @NotNull
673   private static GitCommandResult doLsRemote(@NotNull final Project project,
674                                              @NotNull final File workingDir,
675                                              @NotNull final String remoteId,
676                                              @NotNull final Collection<String> authenticationUrls,
677                                              final String... additionalParameters) {
678     return run(new Computable<GitLineHandler>() {
679       @Override
680       public GitLineHandler compute() {
681         GitLineHandler h = new GitLineHandler(project, workingDir, GitCommand.LS_REMOTE);
682         h.addParameters(additionalParameters);
683         h.addParameters(remoteId);
684         h.setUrls(authenticationUrls);
685         return h;
686       }
687     });
688   }
689
690   private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) {
691     addListeners(handler, Arrays.asList(listeners));
692   }
693
694   private static void addListeners(@NotNull GitLineHandler handler, @NotNull List<GitLineHandlerListener> listeners) {
695     for (GitLineHandlerListener listener : listeners) {
696       handler.addLineListener(listener);
697     }
698   }
699
700   @NotNull
701   private static GitCommandResult run(@NotNull Computable<GitLineHandler> handlerConstructor) {
702     final List<String> errorOutput = new ArrayList<>();
703     final List<String> output = new ArrayList<>();
704     final AtomicInteger exitCode = new AtomicInteger();
705     final AtomicBoolean startFailed = new AtomicBoolean();
706     final AtomicReference<Throwable> exception = new AtomicReference<>();
707
708     int authAttempt = 0;
709     boolean authFailed;
710     boolean success;
711     do {
712       errorOutput.clear();
713       output.clear();
714       exitCode.set(0);
715       startFailed.set(false);
716       exception.set(null);
717
718       GitLineHandler handler = handlerConstructor.compute();
719       handler.addLineListener(new GitLineHandlerListener() {
720         @Override public void onLineAvailable(String line, Key outputType) {
721           if (looksLikeError(line)) {
722             errorOutput.add(line);
723           } else {
724             output.add(line);
725           }
726         }
727
728         @Override public void processTerminated(int code) {
729           exitCode.set(code);
730         }
731
732         @Override public void startFailed(Throwable t) {
733           startFailed.set(true);
734           errorOutput.add("Failed to start Git process");
735           exception.set(t);
736         }
737       });
738
739       handler.runInCurrentThread(null);
740       authFailed = handler.hasHttpAuthFailed();
741       success = !startFailed.get() && (handler.isIgnoredErrorCode(exitCode.get()) || exitCode.get() == 0);
742     }
743     while (authFailed && authAttempt++ < 2);
744     return new GitCommandResult(success, exitCode.get(), errorOutput, output, null);
745   }
746
747   /**
748    * Runs the given {@link GitLineHandler} in the current thread and returns the {@link GitCommandResult}.
749    */
750   @NotNull
751   private static GitCommandResult run(@NotNull GitLineHandler handler) {
752     return run(new Computable.PredefinedValueComputable<>(handler));
753   }
754
755   @Override
756   @NotNull
757   public GitCommandResult runCommand(@NotNull Computable<GitLineHandler> handlerConstructor) {
758     return run(handlerConstructor);
759   }
760
761   @NotNull
762   @Override
763   public GitCommandResult runCommand(@NotNull final GitLineHandler handler) {
764     return runCommand(new Computable<GitLineHandler>() {
765       @Override
766       public GitLineHandler compute() {
767         return handler;
768       }
769     });
770   }
771
772   @NotNull
773   private static GitCommandResult runAll(@NotNull List<Computable<GitCommandResult>> commands) {
774     if (commands.isEmpty()) {
775       LOG.error("List of commands should not be empty", new Exception());
776       return GitCommandResult.error("Internal error");
777     }
778     GitCommandResult compoundResult = null;
779     for (Computable<GitCommandResult> command : commands) {
780       compoundResult = GitCommandResult.merge(compoundResult, command.compute());
781     }
782     return ObjectUtils.assertNotNull(compoundResult);
783   }
784
785   private static boolean looksLikeError(@NotNull final String text) {
786     return ContainerUtil.exists(ERROR_INDICATORS, new Condition<String>() {
787       @Override
788       public boolean value(@NotNull String indicator) {
789         return StringUtil.startsWithIgnoreCase(text.trim(), indicator);
790       }
791     });
792   }
793
794   // could be upper-cased, so should check case-insensitively
795   public static final String[] ERROR_INDICATORS = {
796     "error:", "remote: error", "fatal:",
797     "Cannot", "Could not", "Interactive rebase already started", "refusing to pull", "cannot rebase:", "conflict",
798     "unable"
799   };
800 }