Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / HgVcs.java
1 // Copyright 2008-2010 Victor Iacoban
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software distributed under
10 // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 // either express or implied. See the License for the specific language governing permissions and
12 // limitations under the License.
13 package org.zmlx.hg4idea;
14
15 import com.intellij.concurrency.JobScheduler;
16 import com.intellij.execution.ui.ConsoleViewContentType;
17 import com.intellij.openapi.application.ApplicationManager;
18 import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator;
19 import com.intellij.openapi.editor.markup.TextAttributes;
20 import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
21 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
22 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
23 import com.intellij.openapi.fileTypes.FileTypeManager;
24 import com.intellij.openapi.options.Configurable;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Disposer;
27 import com.intellij.openapi.util.IconLoader;
28 import com.intellij.openapi.util.SystemInfo;
29 import com.intellij.openapi.vcs.AbstractVcs;
30 import com.intellij.openapi.vcs.CommittedChangesProvider;
31 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
32 import com.intellij.openapi.vcs.VcsException;
33 import com.intellij.openapi.vcs.annotate.AnnotationProvider;
34 import com.intellij.openapi.vcs.changes.ChangeProvider;
35 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
36 import com.intellij.openapi.vcs.diff.DiffProvider;
37 import com.intellij.openapi.vcs.history.VcsHistoryProvider;
38 import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
39 import com.intellij.openapi.vcs.update.UpdateEnvironment;
40 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
41 import com.intellij.openapi.vfs.VfsUtil;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.openapi.vfs.VirtualFileListener;
44 import com.intellij.openapi.vfs.VirtualFileManager;
45 import com.intellij.openapi.wm.StatusBar;
46 import com.intellij.openapi.wm.WindowManager;
47 import com.intellij.util.containers.ComparatorDelegate;
48 import com.intellij.util.containers.Convertor;
49 import com.intellij.util.messages.MessageBusConnection;
50 import com.intellij.util.messages.Topic;
51 import org.jetbrains.annotations.NotNull;
52 import org.zmlx.hg4idea.provider.*;
53 import org.zmlx.hg4idea.provider.annotate.HgAnnotationProvider;
54 import org.zmlx.hg4idea.provider.commit.HgCheckinEnvironment;
55 import org.zmlx.hg4idea.provider.update.HgIntegrateEnvironment;
56 import org.zmlx.hg4idea.provider.update.HgUpdateEnvironment;
57 import org.zmlx.hg4idea.ui.HgChangesetStatus;
58 import org.zmlx.hg4idea.ui.HgCurrentBranchStatus;
59
60 import javax.swing.*;
61 import java.io.File;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.concurrent.ScheduledFuture;
65 import java.util.concurrent.TimeUnit;
66
67 public class HgVcs extends AbstractVcs<CommittedChangeList> {
68
69   public static final Topic<HgUpdater> BRANCH_TOPIC =
70     new Topic<HgUpdater>("hg4idea.branch", HgUpdater.class);
71
72   public static final Topic<HgUpdater> INCOMING_TOPIC =
73     new Topic<HgUpdater>("hg4idea.incoming", HgUpdater.class);
74
75   public static final Topic<HgUpdater> OUTGOING_TOPIC =
76     new Topic<HgUpdater>("hg4idea.outgoing", HgUpdater.class);
77
78   public static final Icon MERCURIAL_ICON = IconLoader.getIcon("/images/mercurial.png");
79
80   private static final Icon INCOMING_ICON = IconLoader.getIcon("/actions/moveDown.png");
81   private static final Icon OUTGOING_ICON = IconLoader.getIcon("/actions/moveUp.png");
82
83   public static final String VCS_NAME = "hg4idea";
84   public static final String DIRSTATE_FILE_PATH = ".hg/dirstate";
85   public static final String NOTIFICATION_GROUP_ID = "Mercurial";
86   public static final String HG_EXECUTABLE_FILE_NAME = (SystemInfo.isWindows ? "hg.exe" : "hg");
87
88   private static final String ORIG_FILE_PATTERN = "*.orig";
89
90   private final HgChangeProvider changeProvider;
91   private final HgProjectConfigurable configurable;
92   private final HgRollbackEnvironment rollbackEnvironment;
93   private final HgDiffProvider diffProvider;
94   private final HgHistoryProvider historyProvider;
95   private final HgCheckinEnvironment checkinEnvironment;
96   private final HgAnnotationProvider annotationProvider;
97   private final HgUpdateEnvironment updateEnvironment;
98   private final HgIntegrateEnvironment integrateEnvironment;
99   private final HgCachingCommitedChangesProvider commitedChangesProvider;
100   private final HgCurrentBranchStatus hgCurrentBranchStatus = new HgCurrentBranchStatus();
101   private final HgChangesetStatus incomingChangesStatus = new HgChangesetStatus(INCOMING_ICON);
102   private final HgChangesetStatus outgoingChangesStatus = new HgChangesetStatus(OUTGOING_ICON);
103   private MessageBusConnection messageBusConnection;
104   private ScheduledFuture<?> changesUpdaterScheduledFuture;
105   private final HgGlobalSettings globalSettings;
106   private final HgProjectSettings projectSettings;
107   private final ProjectLevelVcsManager myVcsManager;
108
109   private boolean started = false;
110   private HgVFSListener myVFSListener;
111   private VirtualFileListener myDirStateChangeListener;
112
113   public HgVcs(Project project,
114     HgGlobalSettings globalSettings, HgProjectSettings projectSettings,
115     ProjectLevelVcsManager vcsManager) {
116     super(project, VCS_NAME);
117     this.globalSettings = globalSettings;
118     this.projectSettings = projectSettings;
119     myVcsManager = vcsManager;
120     configurable = new HgProjectConfigurable(projectSettings);
121     changeProvider = new HgChangeProvider(project, getKeyInstanceMethod());
122     rollbackEnvironment = new HgRollbackEnvironment(project);
123     diffProvider = new HgDiffProvider(project);
124     historyProvider = new HgHistoryProvider(project);
125     checkinEnvironment = new HgCheckinEnvironment(project);
126     annotationProvider = new HgAnnotationProvider(project);
127     updateEnvironment = new HgUpdateEnvironment(project);
128     integrateEnvironment = new HgIntegrateEnvironment(project);
129     commitedChangesProvider = new HgCachingCommitedChangesProvider(project);
130     myDirStateChangeListener = new HgDirStateChangeListener(myProject);
131   }
132
133   public String getDisplayName() {
134     return configurable.getDisplayName();
135   }
136
137   public Configurable getConfigurable() {
138     return configurable;
139   }
140
141   @Override
142   public ChangeProvider getChangeProvider() {
143     if (!started) {
144       return null;
145     }
146
147     return changeProvider;
148   }
149
150   @Override
151   public RollbackEnvironment getRollbackEnvironment() {
152     if (!started) {
153       return null;
154     }
155
156     return rollbackEnvironment;
157   }
158
159   @Override
160   public DiffProvider getDiffProvider() {
161     if (!started) {
162       return null;
163     }
164
165     return diffProvider;
166   }
167
168   @Override
169   public VcsHistoryProvider getVcsHistoryProvider() {
170     if (!started) {
171       return null;
172     }
173
174     return historyProvider;
175   }
176
177   @Override
178   public VcsHistoryProvider getVcsBlockHistoryProvider() {
179     return getVcsHistoryProvider();
180   }
181
182   @Override
183   public CheckinEnvironment getCheckinEnvironment() {
184     if (!started) {
185       return null;
186     }
187
188     return checkinEnvironment;
189   }
190
191   @Override
192   public AnnotationProvider getAnnotationProvider() {
193     if (!started) {
194       return null;
195     }
196
197     return annotationProvider;
198   }
199
200   @Override
201   public UpdateEnvironment getUpdateEnvironment() {
202     if (!started) {
203       return null;
204     }
205
206     return updateEnvironment;
207   }
208
209   @Override
210   public UpdateEnvironment getIntegrateEnvironment() {
211     if (!started) {
212       return null;
213     }
214
215     return integrateEnvironment;
216   }
217
218   @Override
219   public CommittedChangesProvider getCommittedChangesProvider() {
220     if (!started) {
221       return null;
222     }
223     return null;
224 //    return commitedChangesProvider;
225   }
226
227   @Override
228   public boolean allowsNestedRoots() {
229     return true;
230   }
231
232   @Override
233   public <S> List<S> filterUniqueRoots(final List<S> in, final Convertor<S, VirtualFile> convertor) {
234     Collections.sort(in, new ComparatorDelegate<S, VirtualFile>(convertor, FilePathComparator.getInstance()));
235
236     for (int i = 1; i < in.size(); i++) {
237       final S sChild = in.get(i);
238       final VirtualFile child = convertor.convert(sChild);
239       final VirtualFile childRoot = HgUtil.getHgRootOrNull(myProject, child);
240       if (childRoot == null) {
241         continue;
242       }
243       for (int j = i - 1; j >= 0; --j) {
244         final S sParent = in.get(j);
245         final VirtualFile parent = convertor.convert(sParent);
246         // if the parent is an ancestor of the child and that they share common root, the child is removed
247         if (VfsUtil.isAncestor(parent, child, false) && VfsUtil.isAncestor(childRoot, parent, false)) {
248           in.remove(i);
249           //noinspection AssignmentToForLoopParameter
250           --i;
251           break;
252         }
253       }
254     }
255     return in;
256   }
257
258
259   @Override
260   public RootsConvertor getCustomConvertor() {
261     return HgRootsHandler.getInstance(myProject);
262   }
263
264     @Override
265   public boolean isVersionedDirectory(VirtualFile dir) {
266     return HgUtil.getNearestHgRoot(dir) != null;
267   }
268
269   public boolean isStarted() {
270     return started;
271   }
272
273   @Override
274   protected void shutdown() throws VcsException {
275     started = false;
276   }
277
278   @Override
279   public void activate() {
280     // validate hg executable
281     if (ApplicationManager.getApplication().isUnitTestMode()) {
282       started = true;
283     } else {
284       HgExecutableValidator validator = new HgExecutableValidator(myProject);
285       started = validator.check(globalSettings);
286     }
287     if (!started) {
288       return;
289     }
290
291     // status bar
292     StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
293     if (statusBar != null) {
294       statusBar.addWidget(hgCurrentBranchStatus, myProject);
295       statusBar.addWidget(incomingChangesStatus, myProject);
296       statusBar.addWidget(outgoingChangesStatus, myProject);
297     }
298
299     // updaters and listeners
300     final HgIncomingStatusUpdater incomingUpdater = new HgIncomingStatusUpdater(incomingChangesStatus, projectSettings);
301     final HgOutgoingStatusUpdater outgoingUpdater = new HgOutgoingStatusUpdater(outgoingChangesStatus, projectSettings);
302     changesUpdaterScheduledFuture = JobScheduler.getScheduler().scheduleWithFixedDelay(
303       new Runnable() {
304         public void run() {
305           incomingUpdater.update(myProject);
306           outgoingUpdater.update(myProject);
307         }
308       }, 0, HgGlobalSettings.getIncomingCheckIntervalSeconds(), TimeUnit.SECONDS);
309
310     messageBusConnection = myProject.getMessageBus().connect();
311     messageBusConnection.subscribe(INCOMING_TOPIC, incomingUpdater);
312     messageBusConnection.subscribe(OUTGOING_TOPIC, outgoingUpdater);
313     messageBusConnection.subscribe(BRANCH_TOPIC, new HgCurrentBranchStatusUpdater(hgCurrentBranchStatus));
314     messageBusConnection.subscribe(
315       FileEditorManagerListener.FILE_EDITOR_MANAGER,
316       new FileEditorManagerAdapter() {
317         @Override
318         public void selectionChanged(FileEditorManagerEvent event) {
319           Project project = event.getManager().getProject();
320           project.getMessageBus()
321             .asyncPublisher(BRANCH_TOPIC)
322             .update(project);
323         }
324       }
325     );
326
327     myVFSListener = new HgVFSListener(myProject, this);
328     VirtualFileManager.getInstance().addVirtualFileListener(myDirStateChangeListener);
329
330     // ignore temporary files
331     final String ignoredPattern = FileTypeManager.getInstance().getIgnoredFilesList();
332     if (!ignoredPattern.contains(ORIG_FILE_PATTERN)) {
333       final String newPattern = ignoredPattern + (ignoredPattern.endsWith(";") ? "" : ";") + ORIG_FILE_PATTERN;
334       HgUtil.runWriteActionLater(new Runnable() {
335         public void run() {
336           FileTypeManager.getInstance().setIgnoredFilesList(newPattern);
337         }
338       });
339     }
340   }
341
342   @Override
343   public void deactivate() {
344     if (!started) {
345       return;
346     }
347
348     StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
349     if (messageBusConnection != null) {
350       messageBusConnection.disconnect();
351     }
352     if (changesUpdaterScheduledFuture != null) {
353       changesUpdaterScheduledFuture.cancel(true);
354     }
355     if (statusBar != null) {
356       //statusBar.removeCustomIndicationComponent(incomingChangesStatus);
357       //statusBar.removeCustomIndicationComponent(outgoingChangesStatus);
358       //statusBar.removeCustomIndicationComponent(hgCurrentBranchStatus);
359     }
360
361     if (myVFSListener != null) {
362       Disposer.dispose(myVFSListener);
363       myVFSListener = null;
364     }
365
366     VirtualFileManager.getInstance().removeVirtualFileListener(myDirStateChangeListener);
367   }
368
369   public static HgVcs getInstance(Project project) {
370     return (HgVcs) ProjectLevelVcsManager.getInstance(project).findVcsByName(VCS_NAME);
371   }
372
373   private static String ourTestHgExecutablePath; // path to hg in test mode
374
375   /**
376    * Sets the path to hg executable used in the test mode.
377    */
378   public static void setTestHgExecutablePath(String path) {
379     ourTestHgExecutablePath = path;
380   }
381
382   /**
383    * Returns the hg executable file.
384    * If it is a test, returns the special value set in the test setup.
385    * If it is a normal app, returns the value from global settings.
386    */
387   public String getHgExecutable() {
388     if (ApplicationManager.getApplication().isUnitTestMode()) {
389       return (new File(ourTestHgExecutablePath, HG_EXECUTABLE_FILE_NAME)).getPath();
390     }
391     return globalSettings.getHgExecutable();
392   }
393
394   public HgGlobalSettings getGlobalSettings() {
395     return globalSettings;
396   }
397
398   public void showMessageInConsole(String message, final TextAttributes style) {
399     myVcsManager.addMessageToConsoleWindow(message, style);
400   }
401
402
403 }