3 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
4 * with the License. You may obtain a copy of the License at:
6 * http://www.apache.org/licenses/LICENSE-2.0
8 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
9 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
10 * the specific language governing permissions and limitations under the License.
12 * Copyright 2007 Decentrix Inc
13 * Copyright 2007 Aspiro AS
14 * Copyright 2008 MQSoftware
15 * Authors: gevession, Erlend Simonsen & Mark Scott
17 * This code was originally derived from the MKS & Mercurial IDEA VCS plugins
20 import com
.intellij
.execution
.ui
.ConsoleViewContentType
;
21 import com
.intellij
.openapi
.diagnostic
.Logger
;
22 import com
.intellij
.openapi
.diff
.impl
.patch
.formove
.FilePathComparator
;
23 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
24 import com
.intellij
.openapi
.options
.Configurable
;
25 import com
.intellij
.openapi
.project
.Project
;
26 import com
.intellij
.openapi
.ui
.Messages
;
27 import com
.intellij
.openapi
.util
.Disposer
;
28 import com
.intellij
.openapi
.vcs
.*;
29 import com
.intellij
.openapi
.vcs
.changes
.ChangeProvider
;
30 import com
.intellij
.openapi
.vcs
.checkin
.CheckinEnvironment
;
31 import com
.intellij
.openapi
.vcs
.diff
.DiffProvider
;
32 import com
.intellij
.openapi
.vcs
.diff
.RevisionSelector
;
33 import com
.intellij
.openapi
.vcs
.history
.VcsHistoryProvider
;
34 import com
.intellij
.openapi
.vcs
.history
.VcsRevisionNumber
;
35 import com
.intellij
.openapi
.vcs
.merge
.MergeProvider
;
36 import com
.intellij
.openapi
.vcs
.rollback
.RollbackEnvironment
;
37 import com
.intellij
.openapi
.vcs
.update
.UpdateEnvironment
;
38 import com
.intellij
.openapi
.vfs
.VfsUtil
;
39 import com
.intellij
.openapi
.vfs
.VirtualFile
;
40 import com
.intellij
.util
.EventDispatcher
;
41 import git4idea
.annotate
.GitAnnotationProvider
;
42 import git4idea
.changes
.GitChangeProvider
;
43 import git4idea
.changes
.GitCommittedChangeListProvider
;
44 import git4idea
.changes
.GitOutgoingChangesProvider
;
45 import git4idea
.checkin
.GitCheckinEnvironment
;
46 import git4idea
.commands
.GitHandler
;
47 import git4idea
.commands
.GitSimpleHandler
;
48 import git4idea
.config
.GitVcsConfigurable
;
49 import git4idea
.config
.GitVcsSettings
;
50 import git4idea
.config
.GitVersion
;
51 import git4idea
.diff
.GitDiffProvider
;
52 import git4idea
.diff
.GitTreeDiffProvider
;
53 import git4idea
.history
.GitHistoryProvider
;
54 import git4idea
.i18n
.GitBundle
;
55 import git4idea
.merge
.GitMergeProvider
;
56 import git4idea
.rollback
.GitRollbackEnvironment
;
57 import git4idea
.update
.GitUpdateEnvironment
;
58 import git4idea
.vfs
.*;
59 import org
.jetbrains
.annotations
.NonNls
;
60 import org
.jetbrains
.annotations
.NotNull
;
61 import org
.jetbrains
.annotations
.Nullable
;
64 import java
.util
.Collections
;
65 import java
.util
.Date
;
66 import java
.util
.List
;
69 * Git VCS implementation
71 public class GitVcs
extends AbstractVcs
{
75 private static final Logger log
= Logger
.getInstance(GitVcs
.class.getName());
79 @NonNls public static final String NAME
= "Git";
80 private static final VcsKey ourKey
= createKey(NAME
);
84 private final ChangeProvider myChangeProvider
;
88 private final CheckinEnvironment myCheckinEnvironment
;
92 private final RollbackEnvironment myRollbackEnvironment
;
96 private final GitUpdateEnvironment myUpdateEnvironment
;
98 * annotate file support
100 private final GitAnnotationProvider myAnnotationProvider
;
104 private final DiffProvider myDiffProvider
;
108 private final VcsHistoryProvider myHistoryProvider
;
110 * cached instance of vcs manager for the project
112 private final ProjectLevelVcsManager myVcsManager
;
114 * project vcs settings
116 private final GitVcsSettings mySettings
;
118 * configuration support
120 private final Configurable myConfigurable
;
122 * selector for revisions
124 private final RevisionSelector myRevSelector
;
128 private final GitMergeProvider myMergeProvider
;
130 * a VFS listener that tracks file addition, deletion, and renaming.
132 private GitVFSListener myVFSListener
;
134 * The currently detected git version or null.
136 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private GitVersion myVersion
;
138 * Checking the version lock (used to prevent infinite recursion)
140 private final Object myCheckingVersion
= new Object();
142 * The path to executable at the time of version check
144 private String myVersionCheckExcecutable
= "";
146 * The changelist provider
148 private GitCommittedChangeListProvider myCommittedChangeListProvider
;
150 * The tracker that checks validity of git roots
152 private GitRootTracker myRootTracker
;
154 * The dispatcher object for root events
156 private EventDispatcher
<GitRootsListener
> myRootListeners
= EventDispatcher
.create(GitRootsListener
.class);
158 * The dispatcher object for git configuration events
160 private EventDispatcher
<GitConfigListener
> myConfigListeners
= EventDispatcher
.create(GitConfigListener
.class);
162 * Tracker for ignored files
164 private GitIgnoreTracker myGitIgnoreTracker
;
166 * Configuration file tracker
168 private GitConfigTracker myConfigTracker
;
170 private final TreeDiffProvider myTreeDiffProvider
;
172 public static GitVcs
getInstance(@NotNull Project project
) {
173 return (GitVcs
)ProjectLevelVcsManager
.getInstance(project
).findVcsByName(NAME
);
176 public GitVcs(@NotNull Project project
,
177 @NotNull final GitChangeProvider gitChangeProvider
,
178 @NotNull final GitCheckinEnvironment gitCheckinEnvironment
,
179 @NotNull final ProjectLevelVcsManager gitVcsManager
,
180 @NotNull final GitAnnotationProvider gitAnnotationProvider
,
181 @NotNull final GitDiffProvider gitDiffProvider
,
182 @NotNull final GitHistoryProvider gitHistoryProvider
,
183 @NotNull final GitRollbackEnvironment gitRollbackEnvironment
,
184 @NotNull final GitVcsSettings gitSettings
) {
185 super(project
, NAME
);
186 myVcsManager
= gitVcsManager
;
187 mySettings
= gitSettings
;
188 myChangeProvider
= gitChangeProvider
;
189 myCheckinEnvironment
= gitCheckinEnvironment
;
190 myAnnotationProvider
= gitAnnotationProvider
;
191 myDiffProvider
= gitDiffProvider
;
192 myHistoryProvider
= gitHistoryProvider
;
193 myRollbackEnvironment
= gitRollbackEnvironment
;
194 myRevSelector
= new GitRevisionSelector();
195 myConfigurable
= new GitVcsConfigurable(mySettings
, myProject
);
196 myUpdateEnvironment
= new GitUpdateEnvironment(myProject
, this, mySettings
);
197 myMergeProvider
= new GitMergeProvider(myProject
);
198 myCommittedChangeListProvider
= new GitCommittedChangeListProvider(myProject
);
199 myOutgoingChangesProvider
= new GitOutgoingChangesProvider(myProject
);
200 myTreeDiffProvider
= new GitTreeDiffProvider(myProject
);
204 * Add listener for git roots
206 * @param listener the listener to add
208 public void addGitConfigListener(GitConfigListener listener
) {
209 myConfigListeners
.addListener(listener
);
213 * Remove listener for git roots
215 * @param listener the listener to remove
217 public void removeGitConfigListener(GitConfigListener listener
) {
218 myConfigListeners
.removeListener(listener
);
223 * Add listener for git roots
225 * @param listener the listener to add
227 public void addGitRootsListener(GitRootsListener listener
) {
228 myRootListeners
.addListener(listener
);
232 * Remove listener for git roots
234 * @param listener the listener to remove
236 public void removeGitRootsListener(GitRootsListener listener
) {
237 myRootListeners
.removeListener(listener
);
244 public CommittedChangesProvider
getCommittedChangesProvider() {
245 // TODO Temporary disabled: return myCommittedChangeListProvider;
253 public String
getRevisionPattern() {
254 // return the full commit hash pattern, possibly other revision formats should be supported as well
255 return "[0-9a-fA-F]{40}";
263 public CheckinEnvironment
getCheckinEnvironment() {
264 return myCheckinEnvironment
;
272 public MergeProvider
getMergeProvider() {
273 return myMergeProvider
;
281 public RollbackEnvironment
getRollbackEnvironment() {
282 return myRollbackEnvironment
;
290 public VcsHistoryProvider
getVcsHistoryProvider() {
291 return myHistoryProvider
;
299 public String
getDisplayName() {
308 public UpdateEnvironment
getUpdateEnvironment() {
309 return myUpdateEnvironment
;
317 public GitAnnotationProvider
getAnnotationProvider() {
318 return myAnnotationProvider
;
326 public DiffProvider
getDiffProvider() {
327 return myDiffProvider
;
335 public RevisionSelector
getRevisionSelector() {
336 return myRevSelector
;
342 @SuppressWarnings({"deprecation"})
345 public VcsRevisionNumber
parseRevisionNumber(String revision
, FilePath path
) {
346 if (revision
== null || revision
.length() == 0) return null;
347 if (revision
.length() > 40) { // date & revision-id encoded string
348 String dateString
= revision
.substring(0, revision
.indexOf("["));
349 String rev
= revision
.substring(revision
.indexOf("[") + 1, 40);
350 Date d
= new Date(Date
.parse(dateString
));
351 return new GitRevisionNumber(rev
, d
);
355 VirtualFile root
= GitUtil
.getGitRoot(path
);
356 return GitRevisionNumber
.resolve(myProject
, root
, revision
);
358 catch (VcsException e
) {
359 log
.error("Unexpected problem with resolving the git revision number: ", e
);
362 return new GitRevisionNumber(revision
);
369 @SuppressWarnings({"deprecation"})
372 public VcsRevisionNumber
parseRevisionNumber(String revision
) {
373 return parseRevisionNumber(revision
, null);
380 public boolean isVersionedDirectory(VirtualFile dir
) {
381 return dir
.isDirectory() && GitUtil
.gitRootOrNull(dir
) != null;
388 protected void start() throws VcsException
{
395 protected void shutdown() throws VcsException
{
402 protected void activate() {
403 if (!myProject
.isDefault() && myRootTracker
== null) {
404 myRootTracker
= new GitRootTracker(this, myProject
, myRootListeners
.getMulticaster());
406 if (myVFSListener
== null) {
407 myVFSListener
= new GitVFSListener(myProject
, this);
409 if (myConfigTracker
== null) {
410 myConfigTracker
= new GitConfigTracker(myProject
, this, myConfigListeners
.getMulticaster());
412 if (myGitIgnoreTracker
== null) {
413 myGitIgnoreTracker
= new GitIgnoreTracker(myProject
, this);
421 protected void deactivate() {
422 if (myRootTracker
!= null) {
423 myRootTracker
.dispose();
424 myRootTracker
= null;
426 if (myVFSListener
!= null) {
427 Disposer
.dispose(myVFSListener
);
428 myVFSListener
= null;
430 if (myGitIgnoreTracker
!= null) {
431 myGitIgnoreTracker
.dispose();
432 myGitIgnoreTracker
= null;
434 if (myConfigTracker
!= null) {
435 myConfigTracker
.dispose();
436 myConfigTracker
= null;
445 public synchronized Configurable
getConfigurable() {
446 return myConfigurable
;
453 public ChangeProvider
getChangeProvider() {
454 return myChangeProvider
;
458 * Show errors as popup and as messages in vcs view.
460 * @param list a list of errors
461 * @param action an action
463 public void showErrors(@NotNull List
<VcsException
> list
, @NotNull String action
) {
464 if (list
.size() > 0) {
465 StringBuffer buffer
= new StringBuffer();
467 buffer
.append(GitBundle
.message("error.list.title", action
));
468 for (final VcsException exception
: list
) {
470 buffer
.append(exception
.getMessage());
472 String msg
= buffer
.toString();
473 Messages
.showErrorDialog(myProject
, msg
, GitBundle
.getString("error.dialog.title"));
478 * Show a plain message in vcs view
480 * @param message a message to show
482 public void showMessages(@NotNull String message
) {
483 if (message
.length() == 0) return;
484 showMessage(message
, ConsoleViewContentType
.NORMAL_OUTPUT
.getAttributes());
488 * @return vcs settings for the current project
491 public GitVcsSettings
getSettings() {
496 * Show message in the VCS view
498 * @param message a message to show
499 * @param style a style to use
501 private void showMessage(@NotNull String message
, final TextAttributes style
) {
502 myVcsManager
.addMessageToConsoleWindow(message
, style
);
506 * Check version and report problem
508 public void checkVersion() {
509 final String executable
= mySettings
.GIT_EXECUTABLE
;
510 synchronized (myCheckingVersion
) {
511 if (myVersion
!= null && myVersionCheckExcecutable
.equals(executable
)) {
514 myVersionCheckExcecutable
= executable
;
515 // this assignment is done to prevent recursive version check
516 myVersion
= GitVersion
.INVALID
;
517 final String version
;
519 version
= version(myProject
).trim();
521 catch (VcsException e
) {
522 String reason
= (e
.getCause() != null ? e
.getCause() : e
).getMessage();
523 if (!myProject
.isDefault()) {
524 showMessage(GitBundle
.message("vcs.unable.to.run.git", executable
, reason
), ConsoleViewContentType
.SYSTEM_OUTPUT
.getAttributes());
528 myVersion
= GitVersion
.parse(version
);
529 if (!GitVersion
.parse(version
).isSupported() && !myProject
.isDefault()) {
530 showMessage(GitBundle
.message("vcs.unsupported.version", version
, GitVersion
.MIN
),
531 ConsoleViewContentType
.SYSTEM_OUTPUT
.getAttributes());
537 * @return the configured version of git
539 public GitVersion
version() {
545 * Get the version of configured git
547 * @param project the project
548 * @return a version of configured git
549 * @throws VcsException an error if there is a problem with running git
551 public static String
version(Project project
) throws VcsException
{
553 GitSimpleHandler h
= new GitSimpleHandler(project
, new File("."), GitHandler
.VERSION
);
563 * @param cmdLine a command line text
565 public void showCommandLine(final String cmdLine
) {
566 showMessage(cmdLine
, ConsoleViewContentType
.SYSTEM_OUTPUT
.getAttributes());
572 * @param line a line to show
574 public void showErrorMessages(final String line
) {
575 showMessage(line
, ConsoleViewContentType
.ERROR_OUTPUT
.getAttributes());
582 public boolean allowsNestedRoots() {
590 public List
<VirtualFile
> filterUniqueRoots(List
<VirtualFile
> in
) {
591 Collections
.sort(in
, FilePathComparator
.getInstance());
593 for (int i
= 1; i
< in
.size(); i
++) {
594 final VirtualFile child
= in
.get(i
);
595 final VirtualFile childRoot
= GitUtil
.gitRootOrNull(child
);
596 if (childRoot
== null) {
597 // non-git file actually, skip it
600 for (int j
= i
- 1; j
>= 0; --j
) {
601 final VirtualFile parent
= in
.get(j
);
602 // the method check both that parent is an ancestor of the child and that they share common git root
603 if (VfsUtil
.isAncestor(parent
, child
, false) && VfsUtil
.isAncestor(childRoot
, parent
, false)) {
605 //noinspection AssignmentToForLoopParameter
618 public RootsConvertor
getCustomConvertor() {
619 return GitRootConverter
.INSTANCE
;
622 public static VcsKey
getKey() {
627 public VcsType
getType() {
628 return VcsType
.distibuted
;
631 private final GitOutgoingChangesProvider myOutgoingChangesProvider
;
633 protected VcsOutgoingChangesProvider
getOutgoingProviderImpl() {
634 return myOutgoingChangesProvider
;
638 public RemoteDifferenceStrategy
getRemoteDifferenceStrategy() {
639 return RemoteDifferenceStrategy
.ASK_TREE_PROVIDER
;
643 protected TreeDiffProvider
getTreeDiffProviderImpl() {
644 return myTreeDiffProvider
;