f8d2760a029aca0e0ebc6b01d7e6886784662a96
[idea/community.git] / plugins / git4idea / src / git4idea / branch / GitBranches.java
1 /*
2  * Copyright 2000-2010 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.branch;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.components.ServiceManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.progress.ProgressIndicator;
22 import com.intellij.openapi.progress.Task;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.vcs.AbstractVcs;
25 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
26 import com.intellij.openapi.vcs.VcsException;
27 import com.intellij.openapi.vcs.VcsRoot;
28 import com.intellij.openapi.vcs.changes.ChangeListManager;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.util.EventDispatcher;
31 import git4idea.GitBranch;
32 import git4idea.GitVcs;
33 import git4idea.vfs.GitReferenceListener;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.Map;
41 import java.util.concurrent.atomic.AtomicBoolean;
42
43 /**
44  * Container and tracker of git branches information.
45  * Listens to branch change and updates information here.
46  * Subscribe a {@link GitBranchesListener} to get notified for current branch and other branch configuration changes.
47  * @author Kirill Likhodedov
48  */
49 public class GitBranches implements GitReferenceListener {
50   private static final Logger LOG = Logger.getInstance(GitBranches.class.getName());
51   private final Project myProject;
52   private final ProjectLevelVcsManager myVcsManager;
53
54   private final EventDispatcher<GitBranchesListener> myListeners = EventDispatcher.create(GitBranchesListener.class);
55   private Map<VirtualFile, GitBranch> myCurrentBranches = new HashMap<VirtualFile, GitBranch>();
56   private final Object myCurrentBranchesLock = new Object();
57   private ChangeListManager myChangeListManager;
58   private GitVcs myVcs;
59   private final AtomicBoolean mySoleUseControl;
60
61   public GitBranches(Project project, ChangeListManager changeListManager, ProjectLevelVcsManager vcsManager) {
62     myProject = project;
63     myChangeListManager = changeListManager;
64     myVcsManager = vcsManager;
65     mySoleUseControl = new AtomicBoolean(false);
66   }
67
68   public static GitBranches getInstance(Project project) {
69     return ServiceManager.getService(project, GitBranches.class);
70   }
71
72   @Override
73   public void referencesChanged(VirtualFile root) {
74     updateBranchesInfo(root);
75   }
76
77   public void activate(GitVcs vcs) {
78     myVcs = vcs;
79     myVcs.addGitReferenceListener(this);
80   }
81
82   public void deactivate() {
83     if (myVcs != null) {
84       myVcs.removeGitReferenceListener(this);
85     }
86   }
87
88   /**
89    * Returns branch that is active (current) in the repository in which the given file resides.
90    * @param file file to determine branch.
91    * @return current branch or null if the file is null, not under git vcs, unversioned, or branch information is not available for it.
92    */
93   @Nullable
94   public GitBranch getCurrentBranch(VirtualFile file) {
95     if (file == null) { return null; }
96     final AbstractVcs vcs = myVcsManager.getVcsFor(file);
97     if (vcs == null || !(vcs instanceof GitVcs)) { return null; }
98     final VirtualFile vcsRoot = myVcsManager.getVcsRootFor(file);
99     if (vcsRoot == null) { return null; }
100
101     synchronized (myCurrentBranchesLock) {
102       return myCurrentBranches.get(vcsRoot);
103     }
104   }
105
106   public void addListener(@NotNull GitBranchesListener listener, @NotNull Disposable parentDisposable) {
107     myListeners.addListener(listener,parentDisposable);
108   }
109
110   /**
111    * Updates branch information for the given root.
112    * If root is null, updates branch information for all Git roots in the project.
113    * @see #fullyUpdateBranchesInfo(java.util.Collection)
114    */
115   private void updateBranchesInfo(final VirtualFile root) {
116     if (root == null) { // all roots may be affected
117       Collection<VirtualFile> roots = new ArrayList<VirtualFile>(1);
118       for (VcsRoot vcsRoot : myVcsManager.getAllVcsRoots()) {
119         if (vcsRoot.vcs != null && vcsRoot.vcs instanceof GitVcs && vcsRoot.path != null) {
120           roots.add(vcsRoot.path);
121         }
122       }
123       fullyUpdateBranchesInfo(roots);
124       return;
125     }
126
127     final Task.Backgroundable task = new Task.Backgroundable(myProject, "Git: refresh current branch") {
128       @Override public void run(@NotNull ProgressIndicator indicator) {
129         assert ! mySoleUseControl.get();
130         mySoleUseControl.set(true);
131         try {
132           GitBranch currentBranch = GitBranch.current(myProject, root);
133           synchronized (myCurrentBranchesLock) {
134             myCurrentBranches.put(root, currentBranch);
135           }
136           notifyListeners();
137         } catch (VcsException e) {
138           LOG.info("Exception while trying to get current branch for root " + root, e);
139           // doing nothing - null will be set to myCurrentBranchName
140         } finally {
141           mySoleUseControl.set(false);
142         }
143       }
144     };
145     GitVcs.runInBackground(task);
146   }
147
148   private void fullyUpdateBranchesInfo(final Collection<VirtualFile> roots) {
149     if (roots == null) { return; }
150     final Task.Backgroundable task = new Task.Backgroundable(myProject, "Git: refresh current branches") {
151       @Override public void run(@NotNull ProgressIndicator indicator) {
152         assert ! mySoleUseControl.get();
153         mySoleUseControl.set(true);
154         try {
155         Map<VirtualFile, GitBranch> currentBranches = new HashMap<VirtualFile, GitBranch>();
156         for (VirtualFile root : roots) {
157           try {
158             GitBranch currentBranch = GitBranch.current(myProject, root);
159             currentBranches.put(root, currentBranch);
160             notifyListeners();
161           } catch (VcsException e) {
162             LOG.info("Exception while trying to get current branch for root " + root, e);
163             // doing nothing - null will be set to myCurrentBranchName
164           }
165         }
166         synchronized (myCurrentBranchesLock) {
167           myCurrentBranches = currentBranches;
168         }
169         } finally {
170           mySoleUseControl.set(false);
171         }
172       }
173     };
174     GitVcs.runInBackground(task);
175   }
176
177   private void notifyListeners() {
178     myListeners.getMulticaster().branchConfigurationChanged();
179   }
180
181 }