13c2d84c13c075ed1cca37f07dfe3ec8a1d49aa4
[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.setStdoutSuppressed(false);
356     h.addParameters("-m", currentName, newName);
357     return run(h);
358   }
359
360   @Override
361   @NotNull
362   public GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target,
363                                 @NotNull GitLineHandlerListener... listeners) {
364     return reset(repository, mode.getArgument(), target, listeners);
365   }
366
367   @Override
368   @NotNull
369   public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) {
370     return reset(repository, "--merge", revision);
371   }
372
373   @NotNull
374   private static GitCommandResult reset(@NotNull GitRepository repository, @NotNull String argument, @Nullable String target,
375                                         @NotNull GitLineHandlerListener... listeners) {
376     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET);
377     handler.addParameters(argument);
378     if (target != null) {
379       handler.addParameters(target);
380     }
381     addListeners(handler, listeners);
382     return run(handler);
383   }
384
385   /**
386    * Returns the last (tip) commit on the given branch.<br/>
387    * {@code git rev-list -1 <branchName>}
388    */
389   @NotNull
390   @Override
391   public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
392     final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST);
393     h.addParameters("-1");
394     h.addParameters(branchName);
395     return run(h);
396   }
397
398   @Override
399   @NotNull
400   public GitCommandResult push(@NotNull GitRepository repository,
401                                @NotNull String remote,
402                                @Nullable String url,
403                                @NotNull String spec,
404                                boolean updateTracking,
405                                @NotNull GitLineHandlerListener... listeners) {
406     return doPush(repository, remote, singleton(url), spec, false, updateTracking, null, listeners);
407   }
408
409   @Override
410   @NotNull
411   public GitCommandResult push(@NotNull GitRepository repository,
412                                @NotNull GitRemote remote,
413                                @NotNull String spec,
414                                boolean force,
415                                boolean updateTracking,
416                                @Nullable String tagMode,
417                                GitLineHandlerListener... listeners) {
418     return doPush(repository, remote.getName(), remote.getPushUrls(), spec, force, updateTracking, tagMode, listeners);
419   }
420
421   @NotNull
422   private GitCommandResult doPush(@NotNull final GitRepository repository,
423                                   @NotNull final String remoteName,
424                                   @NotNull final Collection<String> remoteUrls,
425                                   @NotNull final String spec,
426                                   final boolean force,
427                                   final boolean updateTracking,
428                                   @Nullable final String tagMode,
429                                   @NotNull final GitLineHandlerListener... listeners) {
430     return runCommand(new Computable<GitLineHandler>() {
431       @Override
432       public GitLineHandler compute() {
433         final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.PUSH);
434         h.setUrls(remoteUrls);
435         h.setSilent(false);
436         h.setStdoutSuppressed(false);
437         addListeners(h, listeners);
438         h.addProgressParameter();
439         h.addParameters("--porcelain");
440         h.addParameters(remoteName);
441         h.addParameters(spec);
442         if (updateTracking) {
443           h.addParameters("--set-upstream");
444         }
445         if (force) {
446           h.addParameters("--force");
447         }
448         if (tagMode != null) {
449           h.addParameters(tagMode);
450         }
451         return h;
452       }
453     });
454   }
455
456   @NotNull
457   @Override
458   public GitCommandResult show(@NotNull GitRepository repository, @NotNull String... params) {
459     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.SHOW);
460     handler.addParameters(params);
461     return run(handler);
462   }
463
464   @Override
465   @NotNull
466   public GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
467                                      @NotNull GitLineHandlerListener... listeners) {
468     final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHERRY_PICK);
469     handler.addParameters("-x");
470     if (!autoCommit) {
471       handler.addParameters("-n");
472     }
473     handler.addParameters(hash);
474     addListeners(handler, listeners);
475     handler.setSilent(false);
476     handler.setStdoutSuppressed(false);
477     return run(handler);
478   }
479
480   @NotNull
481   @Override
482   public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
483     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.LS_FILES);
484     h.addParameters("--unmerged");
485     h.setSilent(true);
486     return run(h);
487   }
488
489   /**
490    * Fetch remote branch
491    * {@code git fetch <remote> <params>}
492    */
493   @Override
494   @NotNull
495   public GitCommandResult fetch(@NotNull final GitRepository repository,
496                                 @NotNull final GitRemote remote,
497                                 @NotNull final List<GitLineHandlerListener> listeners,
498                                 final String... params) {
499     return runCommand(new Computable<GitLineHandler>() {
500       @Override
501       public GitLineHandler compute() {
502         final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.FETCH);
503         h.setSilent(false);
504         h.setStdoutSuppressed(false);
505         h.setUrls(remote.getUrls());
506         h.addParameters(remote.getName());
507         h.addParameters(params);
508         h.addProgressParameter();
509         GitVcs vcs = GitVcs.getInstance(repository.getProject());
510         if (vcs != null && GitVersionSpecialty.SUPPORTS_FETCH_PRUNE.existsIn(vcs.getVersion())) {
511           h.addParameters("--prune");
512         }
513         addListeners(h, listeners);
514         return h;
515       }
516     });
517   }
518
519   @NotNull
520   @Override
521   public GitCommandResult addRemote(@NotNull GitRepository repository, @NotNull String name, @NotNull String url) {
522     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
523     h.addParameters("add", name, url);
524     return run(h);
525   }
526
527   @NotNull
528   @Override
529   public GitCommandResult removeRemote(@NotNull GitRepository repository, @NotNull GitRemote remote) {
530     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
531     h.addParameters("remove", remote.getName());
532     return run(h);
533   }
534
535   @NotNull
536   @Override
537   public GitCommandResult renameRemote(@NotNull GitRepository repository, @NotNull String oldName, @NotNull String newName) {
538     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
539     h.addParameters("rename", oldName, newName);
540     return run(h);
541   }
542
543   @NotNull
544   @Override
545   public GitCommandResult setRemoteUrl(@NotNull GitRepository repository, @NotNull String remoteName, @NotNull String newUrl) {
546     GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
547     h.addParameters("set-url", remoteName, newUrl);
548     return run(h);
549   }
550
551   @NotNull
552   @Override
553   public GitCommandResult lsRemote(@NotNull final Project project,
554                                    @NotNull final File workingDir,
555                                    @NotNull final String url) {
556     return doLsRemote(project, workingDir, url, singleton(url));
557   }
558
559   @NotNull
560   @Override
561   public GitCommandResult lsRemote(@NotNull Project project,
562                                    @NotNull VirtualFile workingDir,
563                                    @NotNull GitRemote remote,
564                                    String... additionalParameters) {
565     return doLsRemote(project, VfsUtilCore.virtualToIoFile(workingDir), remote.getName(), remote.getUrls(), additionalParameters);
566   }
567
568   @NotNull
569   @Override
570   public GitCommandResult remotePrune(@NotNull final GitRepository repository, @NotNull final GitRemote remote) {
571     return run(new Computable<GitLineHandler>() {
572       @Override
573       public GitLineHandler compute() {
574         GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE.writeLockingCommand());
575         h.setStdoutSuppressed(false);
576         h.addParameters("prune");
577         h.addParameters(remote.getName());
578         h.setUrls(remote.getUrls());
579         return h;
580       }
581     });
582   }
583
584   @NotNull
585   @Override
586   public GitCommandResult rebase(@NotNull GitRepository repository,
587                                  @NotNull GitRebaseParams parameters,
588                                  @NotNull GitLineHandlerListener... listeners) {
589     Project project = repository.getProject();
590     VirtualFile root = repository.getRoot();
591     GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
592     handler.addParameters(parameters.asCommandLineArguments());
593     addListeners(handler, listeners);
594     return parameters.isInteractive() ? runWithEditor(project, root, handler, true) : run(handler);
595   }
596
597   @NotNull
598   @Override
599   public GitCommandResult rebaseAbort(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
600     GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REBASE);
601     handler.addParameters("--abort");
602     addListeners(handler, listeners);
603     return run(handler);
604   }
605
606   @NotNull
607   @Override
608   public GitCommandResult rebaseContinue(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
609     return rebaseResume(repository, GitRebaseResumeMode.CONTINUE, listeners);
610   }
611
612   @NotNull
613   @Override
614   public GitCommandResult rebaseSkip(@NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) {
615     return rebaseResume(repository, GitRebaseResumeMode.SKIP, listeners);
616   }
617
618   @NotNull
619   private GitCommandResult rebaseResume(@NotNull GitRepository repository,
620                                         @NotNull GitRebaseResumeMode rebaseMode,
621                                         @NotNull GitLineHandlerListener[] listeners) {
622     Project project = repository.getProject();
623     VirtualFile root = repository.getRoot();
624     GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
625     handler.addParameters(rebaseMode.asCommandLineArgument());
626     addListeners(handler, listeners);
627     return runWithEditor(project, root, handler, false);
628   }
629
630   @NotNull
631   private GitCommandResult runWithEditor(@NotNull Project project,
632                                          @NotNull VirtualFile root,
633                                          @NotNull GitLineHandler handler,
634                                          boolean commitListAware) {
635     GitInteractiveRebaseEditorHandler editor = configureEditor(project, root, handler, commitListAware);
636     try {
637       GitCommandResult result = run(handler);
638       return editor.wasEditorCancelled() ? toCancelledResult(result) : result;
639     }
640     finally {
641       editor.close();
642     }
643   }
644
645   @NotNull
646   private static GitCommandResult toCancelledResult(@NotNull GitCommandResult result) {
647     int exitCode = result.getExitCode() == 0 ? 1 : result.getExitCode();
648     return new GitCommandResult(false, exitCode, result.getErrorOutput(), result.getOutput(), result.getException()) {
649       @Override
650       public boolean cancelled() {
651         return true;
652       }
653     };
654   }
655
656   @VisibleForTesting
657   @NotNull
658   protected GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project,
659                                                               @NotNull VirtualFile root,
660                                                               @NotNull GitLineHandler handler,
661                                                               boolean commitListAware) {
662     GitRebaseEditorService service = GitRebaseEditorService.getInstance();
663     GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, handler);
664     if (!commitListAware) {
665       editor.setRebaseEditorShown();
666     }
667     service.configureHandler(handler, editor.getHandlerNo());
668     return editor;
669   }
670
671   @NotNull
672   private static GitCommandResult doLsRemote(@NotNull final Project project,
673                                              @NotNull final File workingDir,
674                                              @NotNull final String remoteId,
675                                              @NotNull final Collection<String> authenticationUrls,
676                                              final String... additionalParameters) {
677     return run(new Computable<GitLineHandler>() {
678       @Override
679       public GitLineHandler compute() {
680         GitLineHandler h = new GitLineHandler(project, workingDir, GitCommand.LS_REMOTE);
681         h.addParameters(additionalParameters);
682         h.addParameters(remoteId);
683         h.setUrls(authenticationUrls);
684         return h;
685       }
686     });
687   }
688
689   private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) {
690     addListeners(handler, Arrays.asList(listeners));
691   }
692
693   private static void addListeners(@NotNull GitLineHandler handler, @NotNull List<GitLineHandlerListener> listeners) {
694     for (GitLineHandlerListener listener : listeners) {
695       handler.addLineListener(listener);
696     }
697   }
698
699   @NotNull
700   private static GitCommandResult run(@NotNull Computable<GitLineHandler> handlerConstructor) {
701     final List<String> errorOutput = new ArrayList<>();
702     final List<String> output = new ArrayList<>();
703     final AtomicInteger exitCode = new AtomicInteger();
704     final AtomicBoolean startFailed = new AtomicBoolean();
705     final AtomicReference<Throwable> exception = new AtomicReference<>();
706
707     int authAttempt = 0;
708     boolean authFailed;
709     boolean success;
710     do {
711       errorOutput.clear();
712       output.clear();
713       exitCode.set(0);
714       startFailed.set(false);
715       exception.set(null);
716
717       GitLineHandler handler = handlerConstructor.compute();
718       handler.addLineListener(new GitLineHandlerListener() {
719         @Override public void onLineAvailable(String line, Key outputType) {
720           if (looksLikeError(line)) {
721             errorOutput.add(line);
722           } else {
723             output.add(line);
724           }
725         }
726
727         @Override public void processTerminated(int code) {
728           exitCode.set(code);
729         }
730
731         @Override public void startFailed(Throwable t) {
732           startFailed.set(true);
733           errorOutput.add("Failed to start Git process");
734           exception.set(t);
735         }
736       });
737
738       handler.runInCurrentThread(null);
739       authFailed = handler.hasHttpAuthFailed();
740       success = !startFailed.get() && (handler.isIgnoredErrorCode(exitCode.get()) || exitCode.get() == 0);
741     }
742     while (authFailed && authAttempt++ < 2);
743     return new GitCommandResult(success, exitCode.get(), errorOutput, output, null);
744   }
745
746   /**
747    * Runs the given {@link GitLineHandler} in the current thread and returns the {@link GitCommandResult}.
748    */
749   @NotNull
750   private static GitCommandResult run(@NotNull GitLineHandler handler) {
751     return run(new Computable.PredefinedValueComputable<>(handler));
752   }
753
754   @Override
755   @NotNull
756   public GitCommandResult runCommand(@NotNull Computable<GitLineHandler> handlerConstructor) {
757     return run(handlerConstructor);
758   }
759
760   @NotNull
761   @Override
762   public GitCommandResult runCommand(@NotNull final GitLineHandler handler) {
763     return runCommand(new Computable<GitLineHandler>() {
764       @Override
765       public GitLineHandler compute() {
766         return handler;
767       }
768     });
769   }
770
771   @NotNull
772   private static GitCommandResult runAll(@NotNull List<Computable<GitCommandResult>> commands) {
773     if (commands.isEmpty()) {
774       LOG.error("List of commands should not be empty", new Exception());
775       return GitCommandResult.error("Internal error");
776     }
777     GitCommandResult compoundResult = null;
778     for (Computable<GitCommandResult> command : commands) {
779       compoundResult = GitCommandResult.merge(compoundResult, command.compute());
780     }
781     return ObjectUtils.assertNotNull(compoundResult);
782   }
783
784   private static boolean looksLikeError(@NotNull final String text) {
785     return ContainerUtil.exists(ERROR_INDICATORS, new Condition<String>() {
786       @Override
787       public boolean value(@NotNull String indicator) {
788         return StringUtil.startsWithIgnoreCase(text.trim(), indicator);
789       }
790     });
791   }
792
793   // could be upper-cased, so should check case-insensitively
794   public static final String[] ERROR_INDICATORS = {
795     "error:", "remote: error", "fatal:",
796     "Cannot", "Could not", "Interactive rebase already started", "refusing to pull", "cannot rebase:", "conflict",
797     "unable"
798   };
799 }