2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package git4idea.commands;
18 import com.intellij.concurrency.JobScheduler;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.ProgressManager;
24 import com.intellij.openapi.progress.Task;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Computable;
27 import com.intellij.openapi.util.Disposer;
28 import com.intellij.openapi.util.Key;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vcs.VcsException;
31 import git4idea.GitVcs;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
35 import java.util.concurrent.ScheduledFuture;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import java.util.concurrent.atomic.AtomicReference;
41 * All Git commands are cancellable when called via {@link GitHandler}. <br/>
42 * To execute the command synchronously, call {@link GitHandler#runInCurrentThread(Runnable)}
43 * or better {@link Git#runCommand(Computable)}.<br/>
44 * To execute in the background or under a modal progress, use the standard {@link Task}. <br/>
45 * To watch the progress, call {@link GitStandardProgressAnalyzer#createListener(ProgressIndicator)}.
47 * @deprecated To remove in IDEA 2017.
50 public class GitTask {
52 private static final Logger LOG = Logger.getInstance(GitTask.class);
54 private final Project myProject;
55 private final GitHandler myHandler;
56 private final String myTitle;
57 private GitProgressAnalyzer myProgressAnalyzer;
58 private ProgressIndicator myProgressIndicator;
60 public GitTask(Project project, GitHandler handler, String title) {
67 * Executes this task synchronously, with a modal progress dialog.
68 * @return Result of the task execution.
70 @SuppressWarnings("unused")
71 public GitTaskResult executeModal() {
76 * Executes this task asynchronously, in background. Calls the resultHandler when finished.
77 * @param resultHandler callback called after the task has finished or was cancelled by user or automatically.
79 public void executeAsync(final GitTaskResultHandler resultHandler) {
80 execute(false, false, resultHandler);
83 public void executeInBackground(boolean sync, final GitTaskResultHandler resultHandler) {
84 execute(sync, false, resultHandler);
87 // this is always sync
89 public GitTaskResult execute(boolean modal) {
90 final AtomicReference<GitTaskResult> result = new AtomicReference<>(GitTaskResult.INITIAL);
91 execute(true, modal, new GitTaskResultHandlerAdapter() {
93 protected void run(GitTaskResult res) {
101 * The most general execution method.
102 * @param sync Set to {@code true} to make the calling thread wait for the task execution.
103 * @param modal If {@code true}, the task will be modal with a modal progress dialog. If false, the task will be executed in
104 * background. {@code modal} implies {@code sync}, i.e. if modal then sync doesn't matter: you'll wait anyway.
105 * @param resultHandler Handle the result.
106 * @see #execute(boolean)
108 public void execute(boolean sync, boolean modal, final GitTaskResultHandler resultHandler) {
109 final Object LOCK = new Object();
110 final AtomicBoolean completed = new AtomicBoolean();
113 final ModalTask task = new ModalTask(myProject, myHandler, myTitle) {
114 @Override public void onSuccess() {
115 commonOnSuccess(LOCK, resultHandler);
118 @Override public void onCancel() {
119 commonOnCancel(LOCK, resultHandler);
122 @Override public void onThrowable(@NotNull Throwable error) {
123 super.onThrowable(error);
124 commonOnCancel(LOCK, resultHandler);
128 ApplicationManager.getApplication().invokeAndWait(() -> ProgressManager.getInstance().run(task));
130 final BackgroundableTask task = new BackgroundableTask(myProject, myHandler, myTitle) {
131 @Override public void onSuccess() {
132 commonOnSuccess(LOCK, resultHandler);
135 @Override public void onCancel() {
136 commonOnCancel(LOCK, resultHandler);
140 if (myProgressIndicator == null) {
141 GitVcs.runInBackground(task);
148 while (!completed.get()) {
150 synchronized (LOCK) {
153 } catch (InterruptedException e) {
160 private void commonOnSuccess(final Object LOCK, final GitTaskResultHandler resultHandler) {
161 GitTaskResult res = !myHandler.errors().isEmpty() ? GitTaskResult.GIT_ERROR : GitTaskResult.OK;
162 resultHandler.run(res);
163 synchronized (LOCK) {
168 private void commonOnCancel(final Object LOCK, final GitTaskResultHandler resultHandler) {
169 resultHandler.run(GitTaskResult.CANCELLED);
170 synchronized (LOCK) {
175 private void addListeners(final TaskExecution task, final ProgressIndicator indicator) {
176 if (indicator != null) {
177 indicator.setIndeterminate(myProgressAnalyzer == null);
179 // When receives an error line, adds a VcsException to the GitHandler.
180 final GitLineHandlerListener listener = new GitLineHandlerListener() {
182 public void processTerminated(int exitCode) {
183 if (exitCode != 0 && !myHandler.isIgnoredErrorCode(exitCode)) {
184 if (myHandler.errors().isEmpty()) {
185 myHandler.addError(new VcsException(myHandler.getLastOutput()));
191 public void startFailed(Throwable exception) {
192 myHandler.addError(new VcsException("Git start failed: " + exception.getMessage(), exception));
196 public void onLineAvailable(String line, Key outputType) {
197 if (GitHandlerUtil.isErrorLine(line.trim())) {
198 myHandler.addError(new VcsException(line));
199 } else if (!StringUtil.isEmptyOrSpaces(line)) {
200 myHandler.addLastOutput(line);
202 if (indicator != null) {
203 indicator.setText2(line);
205 if (myProgressAnalyzer != null && indicator != null) {
206 final double fraction = myProgressAnalyzer.analyzeProgress(line);
208 indicator.setFraction(fraction);
214 if (myHandler instanceof GitLineHandler) {
215 ((GitLineHandler)myHandler).addLineListener(listener);
217 myHandler.addListener(listener);
220 // disposes the timer
221 myHandler.addListener(new GitHandlerListener() {
223 public void processTerminated(int exitCode) {
228 public void startFailed(Throwable exception) {
234 public void setProgressAnalyzer(GitProgressAnalyzer progressAnalyzer) {
235 myProgressAnalyzer = progressAnalyzer;
238 public void setProgressIndicator(ProgressIndicator progressIndicator) {
239 myProgressIndicator = progressIndicator;
243 * We're using this interface here to work with Task, because standard {@link Task#run(com.intellij.openapi.progress.ProgressIndicator)}
244 * is busy with timers.
246 private interface TaskExecution {
247 void execute(ProgressIndicator indicator);
251 // To add to {@link com.intellij.openapi.progress.BackgroundTaskQueue} a task must be {@link Task.Backgroundable},
252 // so we can't have a single class representing a task: we have BackgroundableTask and ModalTask.
253 // To minimize code duplication we use GitTaskDelegate.
255 private abstract class BackgroundableTask extends Task.Backgroundable implements TaskExecution {
256 private final GitTaskDelegate myDelegate;
258 public BackgroundableTask(@Nullable final Project project, @NotNull GitHandler handler, @NotNull final String processTitle) {
259 super(project, processTitle, true);
260 myDelegate = new GitTaskDelegate(myProject, handler, this);
264 public final void run(@NotNull ProgressIndicator indicator) {
265 myDelegate.run(indicator);
268 public final void runAlone() {
269 if (ApplicationManager.getApplication().isDispatchThread()) {
270 ApplicationManager.getApplication().executeOnPooledThread(() -> justRun());
276 private void justRun() {
277 String oldTitle = myProgressIndicator.getText();
278 myProgressIndicator.setText(myTitle);
279 myDelegate.run(myProgressIndicator);
280 myProgressIndicator.setText(oldTitle);
281 if (myProgressIndicator.isCanceled()) {
289 public void execute(ProgressIndicator indicator) {
290 addListeners(this, indicator);
291 GitHandlerUtil.runInCurrentThread(myHandler, indicator, false, myTitle);
295 public void dispose() {
296 Disposer.dispose(myDelegate);
300 private abstract class ModalTask extends Task.Modal implements TaskExecution {
301 private final GitTaskDelegate myDelegate;
303 public ModalTask(@Nullable final Project project, @NotNull GitHandler handler, @NotNull final String processTitle) {
304 super(project, processTitle, true);
305 myDelegate = new GitTaskDelegate(myProject, handler, this);
309 public final void run(@NotNull ProgressIndicator indicator) {
310 myDelegate.run(indicator);
314 public void execute(ProgressIndicator indicator) {
315 addListeners(this, indicator);
316 GitHandlerUtil.runInCurrentThread(myHandler, indicator, false, myTitle);
320 public void dispose() {
321 Disposer.dispose(myDelegate);
326 * Does the work which is common for BackgroundableTask and ModalTask.
327 * Actually - starts a timer which checks if current progress indicator is cancelled.
328 * If yes, kills the GitHandler.
330 private static class GitTaskDelegate implements Disposable {
331 private final GitHandler myHandler;
332 private ProgressIndicator myIndicator;
333 private final TaskExecution myTask;
334 private ScheduledFuture<?> myTimer;
335 private final Project myProject;
337 public GitTaskDelegate(Project project, GitHandler handler, TaskExecution task) {
341 Disposer.register(myProject, this);
344 public void run(ProgressIndicator indicator) {
345 myIndicator = indicator;
346 myTimer = JobScheduler.getScheduler().scheduleWithFixedDelay(
348 if (myIndicator != null && myIndicator.isCanceled()) {
350 if (myHandler != null) {
351 myHandler.destroyProcess();
355 Disposer.dispose(this);
358 }, 0, 200, TimeUnit.MILLISECONDS);
359 myTask.execute(indicator);
363 public void dispose() {
364 if (myTimer != null) {
365 myTimer.cancel(false);