2 * Copyright 2000-2008 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
.update
;
18 import com
.intellij
.openapi
.options
.Configurable
;
19 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
20 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
21 import com
.intellij
.openapi
.project
.Project
;
22 import com
.intellij
.openapi
.ui
.Messages
;
23 import com
.intellij
.openapi
.util
.Key
;
24 import com
.intellij
.openapi
.util
.Ref
;
25 import com
.intellij
.openapi
.vcs
.AbstractVcsHelper
;
26 import com
.intellij
.openapi
.vcs
.FilePath
;
27 import com
.intellij
.openapi
.vcs
.VcsException
;
28 import com
.intellij
.openapi
.vcs
.update
.SequentialUpdatesContext
;
29 import com
.intellij
.openapi
.vcs
.update
.UpdateEnvironment
;
30 import com
.intellij
.openapi
.vcs
.update
.UpdateSession
;
31 import com
.intellij
.openapi
.vcs
.update
.UpdatedFiles
;
32 import com
.intellij
.openapi
.vfs
.VirtualFile
;
33 import com
.intellij
.util
.ui
.UIUtil
;
34 import git4idea
.GitBranch
;
35 import git4idea
.GitRevisionNumber
;
36 import git4idea
.GitUtil
;
37 import git4idea
.GitVcs
;
38 import git4idea
.changes
.GitChangeUtils
;
39 import git4idea
.commands
.*;
40 import git4idea
.config
.GitVcsSettings
;
41 import git4idea
.i18n
.GitBundle
;
42 import git4idea
.merge
.MergeChangeCollector
;
43 import org
.jetbrains
.annotations
.NotNull
;
44 import org
.jetbrains
.annotations
.Nullable
;
46 import java
.util
.ArrayList
;
47 import java
.util
.Arrays
;
48 import java
.util
.Collection
;
49 import java
.util
.List
;
50 import java
.util
.concurrent
.atomic
.AtomicBoolean
;
53 * Git update environment implementation. The environment does just a simple
54 * {@code git pull -v} for each content root.
56 public class GitUpdateEnvironment
implements UpdateEnvironment
{
64 private final Project myProject
;
66 * The project settings
68 private final GitVcsSettings mySettings
;
71 * A constructor from settings
73 * @param project a project
75 public GitUpdateEnvironment(@NotNull Project project
, @NotNull GitVcs vcs
, GitVcsSettings settings
) {
78 mySettings
= settings
;
84 public void fillGroups(UpdatedFiles updatedFiles
) {
85 //unused, there are no custom categories yet
92 public UpdateSession
updateDirectories(@NotNull FilePath
[] filePaths
,
93 UpdatedFiles updatedFiles
,
94 ProgressIndicator progressIndicator
,
95 @NotNull Ref
<SequentialUpdatesContext
> sequentialUpdatesContextRef
)
96 throws ProcessCanceledException
{
97 List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
98 for (final VirtualFile root
: GitUtil
.gitRoots(Arrays
.asList(filePaths
))) {
100 // check if there is a remote for the branch
101 final GitBranch branch
= GitBranch
.current(myProject
, root
);
102 if (branch
== null) {
105 final String value
= branch
.getTrackedRemoteName(myProject
, root
);
106 if (value
== null || value
.length() == 0) {
109 boolean stashCreated
=
110 mySettings
.UPDATE_STASH
&& GitStashUtils
.saveStash(myProject
, root
, "Uncommitted changes before update operation");
112 // remember the current position
113 GitRevisionNumber before
= GitRevisionNumber
.resolve(myProject
, root
, "HEAD");
115 GitLineHandler h
= new GitLineHandler(myProject
, root
, GitHandler
.PULL
);
116 // ignore merge failure for the pull
117 h
.ignoreErrorCode(1);
118 switch (mySettings
.UPDATE_TYPE
) {
120 h
.addParameters("--rebase");
123 h
.addParameters("--no-rebase");
126 // use default for the branch
129 assert false : "Unknown update type: " + mySettings
.UPDATE_TYPE
;
131 h
.addParameters("--no-stat");
132 h
.addParameters("-v");
133 final Ref
<Boolean
> cancelled
= new Ref
<Boolean
>(false);
135 RebaseConflictDetector rebaseConflictDetector
= new RebaseConflictDetector();
136 h
.addLineListener(rebaseConflictDetector
);
138 GitHandlerUtil
.doSynchronouslyWithExceptions(h
, progressIndicator
);
141 if (!rebaseConflictDetector
.isRebaseConflict()) {
142 exceptions
.addAll(h
.errors());
146 final Ref
<Throwable
> ex
= new Ref
<Throwable
>();
147 UIUtil
.invokeAndWaitIfNeeded(new Runnable() {
150 List
<VirtualFile
> affectedFiles
= GitChangeUtils
.unmergedFiles(myProject
, root
);
151 while (affectedFiles
.size() != 0) {
152 AbstractVcsHelper
.getInstance(myProject
).showMergeDialog(affectedFiles
, myVcs
.getMergeProvider());
153 affectedFiles
= GitChangeUtils
.unmergedFiles(myProject
, root
);
154 if (affectedFiles
.size() != 0) {
155 int result
= Messages
.showYesNoDialog(myProject
, GitBundle
.message("update.rebase.unmerged", affectedFiles
.size(),
156 root
.getPresentableUrl()),
157 GitBundle
.getString("update.rebase.unmerged.title"), Messages
.getErrorIcon());
165 catch (Throwable t
) {
170 //noinspection ThrowableResultOfMethodCallIgnored
171 if (ex
.get() != null) {
172 //noinspection ThrowableResultOfMethodCallIgnored
173 throw GitUtil
.rethrowVcsException(ex
.get());
175 if (cancelled
.get()) {
178 doRebase(progressIndicator
, root
, rebaseConflictDetector
, "--continue");
179 final Ref
<Integer
> result
= new Ref
<Integer
>();
181 while (rebaseConflictDetector
.isNoChange()) {
182 UIUtil
.invokeAndWaitIfNeeded(new Runnable() {
184 int rc
= Messages
.showDialog(myProject
, GitBundle
.message("update.rebase.no.change", root
.getPresentableUrl()),
185 GitBundle
.getString("update.rebase.no.change.title"),
186 new String
[]{GitBundle
.getString("update.rebase.no.change.skip"),
187 GitBundle
.getString("update.rebase.no.change.retry"),
188 GitBundle
.getString("update.rebase.no.change.cancel")}, 0, Messages
.getErrorIcon());
192 switch (result
.get()) {
194 doRebase(progressIndicator
, root
, rebaseConflictDetector
, "--skip");
195 continue noChangeLoop
;
197 continue noChangeLoop
;
204 while (rebaseConflictDetector
.isRebaseConflict() && !cancelled
.get());
205 if (cancelled
.get()) {
206 //noinspection ThrowableInstanceNeverThrown
207 exceptions
.add(new VcsException("The update process was cancelled for " + root
.getPresentableUrl()));
208 doRebase(progressIndicator
, root
, rebaseConflictDetector
, "--abort");
212 if (!cancelled
.get()) {
213 // find out what have changed
214 MergeChangeCollector collector
= new MergeChangeCollector(myProject
, root
, before
, updatedFiles
);
215 collector
.collect(exceptions
);
221 GitStashUtils
.popLastStash(myProject
, root
);
225 catch (VcsException ex
) {
229 return new GitUpdateSession(exceptions
);
233 * Do rebase operation as part of update operator
235 * @param progressIndicator the progress indicator for the update
236 * @param root the vcs root
237 * @param rebaseConflictDetector the detector of conflicts in rebase operation
238 * @param action the rebase action to execute
240 private void doRebase(ProgressIndicator progressIndicator
,
242 RebaseConflictDetector rebaseConflictDetector
,
243 final String action
) {
244 GitLineHandler rh
= new GitLineHandler(myProject
, root
, GitHandler
.REBASE
);
245 // ignore failure for abort
246 rh
.ignoreErrorCode(1);
247 rh
.addParameters(action
);
248 rebaseConflictDetector
.reset();
249 rh
.addLineListener(rebaseConflictDetector
);
250 GitHandlerUtil
.doSynchronouslyWithExceptions(rh
, progressIndicator
);
256 public boolean validateOptions(Collection
<FilePath
> filePaths
) {
257 for (FilePath p
: filePaths
) {
258 if (!GitUtil
.isUnderGit(p
)) {
269 public Configurable
createConfigurable(Collection
<FilePath
> files
) {
270 return new GitUpdateConfigurable(mySettings
);
274 * The detector of conflict conditions for rebase operation
276 static class RebaseConflictDetector
extends GitLineHandlerAdapter
{
278 * The line that indicates that there is a rebase conflict.
280 private final static String REBASE_CONFLICT_INDICATOR
= "When you have resolved this problem run \"git rebase --continue\".";
282 * The line that indicates "no change" condition.
284 private static final String REBASE_NO_CHANGE_INDICATOR
= "No changes - did you forget to use 'git add'?";
286 * if true, the rebase conflict happened
288 AtomicBoolean rebaseConflict
= new AtomicBoolean(false);
290 * if true, the no changes were detected in the rebase operations
292 AtomicBoolean noChange
= new AtomicBoolean(false);
295 * Reset detector before new operation
297 public void reset() {
298 rebaseConflict
.set(false);
303 * @return true if "no change" condition was detected during the operation
305 public boolean isNoChange() {
306 return noChange
.get();
310 * @return true if conflict during rebase was detected
312 public boolean isRebaseConflict() {
313 return rebaseConflict
.get();
320 public void onLineAvailable(String line
, Key outputType
) {
321 if (line
.startsWith(REBASE_CONFLICT_INDICATOR
)) {
322 rebaseConflict
.set(true);
324 if (line
.startsWith(REBASE_NO_CHANGE_INDICATOR
)) {