ea924590f4dbc72471b13c2d5029ad55c18377e5
[idea/community.git] / plugins / git4idea / src / git4idea / GitVcs.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package git4idea;
3
4 import com.intellij.idea.ActionsBundle;
5 import com.intellij.openapi.Disposable;
6 import com.intellij.openapi.components.ServiceManager;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.options.Configurable;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.ProgressManager;
11 import com.intellij.openapi.progress.Task;
12 import com.intellij.openapi.progress.util.BackgroundTaskUtil;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.ui.Messages;
15 import com.intellij.openapi.util.Disposer;
16 import com.intellij.openapi.vcs.*;
17 import com.intellij.openapi.vcs.changes.CommitExecutor;
18 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
19 import com.intellij.openapi.vcs.diff.DiffProvider;
20 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
21 import com.intellij.openapi.vcs.merge.MergeProvider;
22 import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
23 import com.intellij.openapi.vcs.roots.VcsRootDetector;
24 import com.intellij.openapi.vcs.update.UpdateEnvironment;
25 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.util.ui.UIUtil;
28 import com.intellij.util.ui.VcsSynchronousProgressWrapper;
29 import com.intellij.vcs.AnnotationProviderEx;
30 import com.intellij.vcs.log.VcsUserRegistry;
31 import git4idea.annotate.GitAnnotationProvider;
32 import git4idea.annotate.GitRepositoryForAnnotationsListener;
33 import git4idea.branch.GitBranchIncomingOutgoingManager;
34 import git4idea.changes.GitCommittedChangeListProvider;
35 import git4idea.changes.GitOutgoingChangesProvider;
36 import git4idea.checkin.GitCheckinEnvironment;
37 import git4idea.checkin.GitCommitAndPushExecutor;
38 import git4idea.checkout.GitCheckoutProvider;
39 import git4idea.config.GitExecutableManager;
40 import git4idea.config.GitExecutableProblemsNotifier;
41 import git4idea.config.GitExecutableValidator;
42 import git4idea.config.GitVersion;
43 import git4idea.diff.GitDiffProvider;
44 import git4idea.history.GitHistoryProvider;
45 import git4idea.i18n.GitBundle;
46 import git4idea.index.GitStageManagerKt;
47 import git4idea.merge.GitMergeProvider;
48 import git4idea.repo.GitRepository;
49 import git4idea.repo.GitRepositoryManager;
50 import git4idea.rollback.GitRollbackEnvironment;
51 import git4idea.roots.GitIntegrationEnabler;
52 import git4idea.status.GitChangeProvider;
53 import git4idea.update.GitUpdateEnvironment;
54 import git4idea.util.GitVcsConsoleWriter;
55 import git4idea.vfs.GitVFSListener;
56 import org.jetbrains.annotations.*;
57
58 import java.util.*;
59 import java.util.concurrent.locks.ReadWriteLock;
60 import java.util.concurrent.locks.ReentrantReadWriteLock;
61
62 /**
63  * Git VCS implementation
64  */
65 public final class GitVcs extends AbstractVcs {
66   public static final @NonNls String NAME = "Git";
67   public static final @NonNls String ID = "git";
68
69   private static final Logger LOG = Logger.getInstance(GitVcs.class.getName());
70   private static final VcsKey ourKey = createKey(NAME);
71
72   private Disposable myDisposable;
73   private GitVFSListener myVFSListener; // a VFS listener that tracks file addition, deletion, and renaming.
74
75   private final ReadWriteLock myCommandLock = new ReentrantReadWriteLock(true); // The command read/write lock
76
77   @NotNull
78   public static GitVcs getInstance(@NotNull Project project) {
79     GitVcs gitVcs = (GitVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(NAME);
80     ProgressManager.checkCanceled();
81     return Objects.requireNonNull(gitVcs);
82   }
83
84   public GitVcs(@NotNull Project project) {
85     super(project, NAME);
86   }
87
88   public ReadWriteLock getCommandLock() {
89     return myCommandLock;
90   }
91
92   /**
93    * Run task in background using the common queue (per project)
94    *
95    * @param task the task to run
96    */
97   public static void runInBackground(Task.Backgroundable task) {
98     task.queue();
99   }
100
101   @Override
102   public CommittedChangesProvider getCommittedChangesProvider() {
103     return myProject.getService(GitCommittedChangeListProvider.class);
104   }
105
106   @Override
107   public String getRevisionPattern() {
108     // return the full commit hash pattern, possibly other revision formats should be supported as well
109     return "[0-9a-fA-F]+";
110   }
111
112   @Override
113   @Nullable
114   public CheckinEnvironment getCheckinEnvironment() {
115     if (myProject.isDefault()) return null;
116     return myProject.getService(GitCheckinEnvironment.class);
117   }
118
119   @NotNull
120   @Override
121   public MergeProvider getMergeProvider() {
122     return GitMergeProvider.detect(myProject);
123   }
124
125   @Override
126   @NotNull
127   public RollbackEnvironment getRollbackEnvironment() {
128     return myProject.getService(GitRollbackEnvironment.class);
129   }
130
131   @Override
132   @NotNull
133   public GitHistoryProvider getVcsHistoryProvider() {
134     return myProject.getService(GitHistoryProvider.class);
135   }
136
137   @Override
138   public GitHistoryProvider getVcsBlockHistoryProvider() {
139     return myProject.getService(GitHistoryProvider.class);
140   }
141
142   @Override
143   @NotNull
144   public String getDisplayName() {
145     return NAME;
146   }
147
148   @Override
149   @Nullable
150   public UpdateEnvironment getUpdateEnvironment() {
151     return myProject.getService(GitUpdateEnvironment.class);
152   }
153
154   @Override
155   @NotNull
156   public AnnotationProviderEx getAnnotationProvider() {
157     return myProject.getService(GitAnnotationProvider.class);
158   }
159
160   @Override
161   @NotNull
162   public DiffProvider getDiffProvider() {
163     return myProject.getService(GitDiffProvider.class);
164   }
165
166   @Override
167   @Nullable
168   public VcsRevisionNumber parseRevisionNumber(@Nullable String revision, @Nullable FilePath path) throws VcsException {
169     if (revision == null || revision.length() == 0) return null;
170     if (revision.length() > 40) {    // date & revision-id encoded string
171       String dateString = revision.substring(0, revision.indexOf("["));
172       String rev = revision.substring(revision.indexOf("[") + 1, 40);
173       Date d = new Date(Date.parse(dateString));
174       return new GitRevisionNumber(rev, d);
175     }
176     if (path != null) {
177       try {
178         VirtualFile root = GitUtil.getRootForFile(myProject, path);
179         return GitRevisionNumber.resolve(myProject, root, revision);
180       }
181       catch (VcsException e) {
182         LOG.info("Unexpected problem with resolving the git revision number: ", e);
183         throw e;
184       }
185     }
186     return new GitRevisionNumber(revision);
187   }
188
189   @Override
190   @Nullable
191   public VcsRevisionNumber parseRevisionNumber(@Nullable String revision) throws VcsException {
192     return parseRevisionNumber(revision, null);
193   }
194
195   @Override
196   public boolean isVersionedDirectory(VirtualFile dir) {
197     return dir.isDirectory() && GitUtil.isUnderGit(dir);
198   }
199
200   @Override
201   protected void activate() {
202     myDisposable = Disposer.newDisposable();
203
204     BackgroundTaskUtil.executeOnPooledThread(myDisposable, ()
205       -> GitExecutableManager.getInstance().testGitExecutableVersionValid(myProject));
206
207     if (myVFSListener == null) {
208       myVFSListener = GitVFSListener.createInstance(this);
209     }
210     ServiceManager.getService(myProject, VcsUserRegistry.class); // make sure to read the registry before opening commit dialog
211
212     GitRepositoryForAnnotationsListener.registerListener(myProject, myDisposable);
213
214     GitUserRegistry.getInstance(myProject).activate();
215     GitBranchIncomingOutgoingManager.getInstance(myProject).activate();
216   }
217
218   @Override
219   protected void deactivate() {
220     if (myVFSListener != null) {
221       Disposer.dispose(myVFSListener);
222       myVFSListener = null;
223     }
224     if (myDisposable != null) {
225       Disposer.dispose(myDisposable);
226       myDisposable = null;
227     }
228   }
229
230   @Override
231   public Configurable getConfigurable() {
232     return null;
233   }
234
235   @Override
236   @Nullable
237   public GitChangeProvider getChangeProvider() {
238     if (myProject.isDefault()) return null;
239     return myProject.getService(GitChangeProvider.class);
240   }
241
242   /**
243    * Show errors as popup and as messages in vcs view.
244    *
245    * @param list   a list of errors
246    * @param action an action
247    */
248   public void showErrors(@NotNull List<? extends VcsException> list, @NotNull @Nls String action) {
249     if (list.size() > 0) {
250       StringBuilder buffer = new StringBuilder();
251       buffer.append("\n");
252       buffer.append(GitBundle.message("error.list.title", action));
253       for (final VcsException exception : list) {
254         buffer.append("\n");
255         buffer.append(exception.getMessage());
256       }
257       final String msg = buffer.toString();
258       UIUtil.invokeLaterIfNeeded(() -> Messages.showErrorDialog(myProject, msg, GitBundle.getString("error.dialog.title")));
259     }
260   }
261
262   /**
263    * @return the version number of Git, which is used by IDEA. Or {@link GitVersion#NULL} if version info is unavailable yet.
264    */
265   @NotNull
266   public GitVersion getVersion() {
267     return GitExecutableManager.getInstance().getVersion(myProject);
268   }
269
270   /**
271    * Shows a command line message in the Version Control Console
272    * @deprecated use {@link GitVcsConsoleWriter}
273    */
274   @Deprecated
275   public void showCommandLine(@Nls String cmdLine) {
276     GitVcsConsoleWriter.getInstance(myProject).showCommandLine(cmdLine);
277   }
278
279   @Override
280   public boolean allowsNestedRoots() {
281     return true;
282   }
283
284   public static VcsKey getKey() {
285     return ourKey;
286   }
287
288   @Override
289   public VcsType getType() {
290     return VcsType.distributed;
291   }
292
293   @Override
294   protected VcsOutgoingChangesProvider<CommittedChangeList> getOutgoingProviderImpl() {
295     return myProject.getService(GitOutgoingChangesProvider.class);
296   }
297
298   @Override
299   public RemoteDifferenceStrategy getRemoteDifferenceStrategy() {
300     return RemoteDifferenceStrategy.ASK_TREE_PROVIDER;
301   }
302
303   @Override
304   public List<CommitExecutor> getCommitExecutors() {
305     if (myProject.isDefault()) return Collections.emptyList();
306     return Collections.singletonList(myProject.getService(GitCommitAndPushExecutor.class));
307   }
308
309
310   /**
311    * @deprecated Use {@link GitExecutableManager#identifyVersion(String)} and {@link GitExecutableProblemsNotifier}.
312    */
313   @Deprecated
314   @NotNull
315   public GitExecutableValidator getExecutableValidator() {
316     return new GitExecutableValidator(myProject);
317   }
318
319   @Override
320   public boolean fileListenerIsSynchronous() {
321     return false;
322   }
323
324   @Override
325   @CalledInAwt
326   public void enableIntegration() {
327     new Task.Backgroundable(myProject, GitBundle.message("progress.title.enabling.git"), true) {
328       @Override
329       public void run(@NotNull ProgressIndicator indicator) {
330         Collection<VcsRoot> roots = ServiceManager.getService(myProject, VcsRootDetector.class).detect();
331         new GitIntegrationEnabler(GitVcs.this).enable(roots);
332       }
333     }.queue();
334   }
335
336   @Override
337   public CheckoutProvider getCheckoutProvider() {
338     return new GitCheckoutProvider();
339   }
340
341   @Nullable
342   @Override
343   public CommittedChangeList loadRevisions(@NotNull VirtualFile vf, @NotNull VcsRevisionNumber number) {
344     GitRepository repository = GitRepositoryManager.getInstance(myProject).getRepositoryForFile(vf);
345     if (repository == null) return null;
346
347     return VcsSynchronousProgressWrapper.compute(
348       () -> GitCommittedChangeListProvider.getCommittedChangeList(myProject, repository.getRoot(), (GitRevisionNumber)number),
349       getProject(), "Load Revision Contents");
350   }
351
352   @Override
353   public boolean arePartialChangelistsSupported() {
354     return !GitStageManagerKt.stageRegistryOption().asBoolean() ||
355            !GitStageManagerKt.stageLineStatusTrackerRegistryOption().asBoolean();
356   }
357
358   @TestOnly
359   public GitVFSListener getVFSListener() {
360     return myVFSListener;
361   }
362
363   @Override
364   public boolean needsCaseSensitiveDirtyScope() {
365     return true;
366   }
367
368   @Override
369   public boolean isWithCustomMenu() {
370     return true;
371   }
372
373   @Override
374   public @Nullable String getCompareWithTheSameVersionActionName() {
375     return ActionsBundle.message("action.Diff.ShowDiff.text");
376   }
377 }