1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.theplugin.idea;
18
19 import com.atlassian.theplugin.commons.crucible.api.model.CommitType;
20 import com.atlassian.theplugin.commons.util.MiscUtil;
21 import com.intellij.openapi.actionSystem.AnActionEvent;
22 import com.intellij.openapi.actionSystem.DataContext;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
25 import com.intellij.openapi.progress.ProgressIndicator;
26 import com.intellij.openapi.progress.Task;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.Messages;
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.RepositoryLocation;
33 import com.intellij.openapi.vcs.VcsException;
34 import com.intellij.openapi.vcs.changes.ContentRevision;
35 import com.intellij.openapi.vcs.diff.DiffProvider;
36 import com.intellij.openapi.vcs.history.VcsFileRevision;
37 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
38 import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
39 import com.intellij.openapi.vfs.VfsUtil;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.vcsUtil.VcsUtil;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.util.List;
46 import java.util.Map;
47
48 public final class VcsIdeaHelper {
49
50 private static final int INITIAL_CAPACITY = 20;
51 private static Map<String, VirtualFile> fetchedFiles = MiscUtil.buildConcurrentHashMap(INITIAL_CAPACITY);
52
53 private VcsIdeaHelper() {
54 }
55
56 public static boolean isUnderVcsControl(DataContext context) {
57 return isUnderVcsControl(IdeaHelper.getCurrentProject(context));
58 }
59
60 public static boolean isUnderVcsControl(AnActionEvent action) {
61 return isUnderVcsControl(action.getDataContext());
62 }
63
64 public static boolean isUnderVcsControl(Project project) {
65 ProjectLevelVcsManager plm = ProjectLevelVcsManager.getInstance(project);
66
67 return (plm != null && plm.getAllActiveVcss().length > 0);
68 }
69
70
71 @Nullable
72 public static String getRepositoryUrlForFile(Project project, VirtualFile vFile) {
73 ProjectLevelVcsManager plm = ProjectLevelVcsManager.getInstance(project);
74 if (plm == null) {
75 return null;
76 }
77 AbstractVcs vcs = plm.getVcsFor(vFile);
78 if (vcs == null) {
79 return null;
80 }
81 CommittedChangesProvider<?, ?> provider = vcs.getCommittedChangesProvider();
82 if (provider == null) {
83 return null;
84 }
85 RepositoryLocation repositoryLocation
86 = provider.getLocationFor(VcsUtil.getFilePath(vFile.getPath()));
87 if (repositoryLocation == null) {
88 return null;
89 }
90 return repositoryLocation.toPresentableString();
91 }
92
93 public static List<VcsFileRevision> getFileHistory(Project project, VirtualFile vFile) throws VcsException {
94 ProjectLevelVcsManager vcsPLM = ProjectLevelVcsManager.getInstance(project);
95
96 if (vcsPLM != null) {
97 return vcsPLM.getVcsFor(vFile).getVcsHistoryProvider().createSessionFor(
98 VcsUtil.getFilePath(vFile.getPath())).getRevisionList();
99 } else {
100 throw new VcsException("File: " + vFile.getPath() + " is not under VCS.");
101 }
102 }
103
104 private static String getFileCacheKey(VirtualFile file, String revision) {
105 return revision + ":" + file.getPath();
106 }
107
108 private static VirtualFile getFileFromCache(VirtualFile virtualFile, String revision) {
109 String key = getFileCacheKey(virtualFile, revision);
110 if (fetchedFiles.containsKey(key)) {
111 return fetchedFiles.get(key);
112 } else {
113 return null;
114 }
115 }
116
117 private static void putFileInfoCache(VirtualFile file, VirtualFile virtualFile, String revision) {
118 String key = getFileCacheKey(virtualFile, revision);
119 fetchedFiles.put(key, file);
120 }
121
122 @Nullable
123 private static VirtualFile getVcsVirtualFile(Project project, VirtualFile virtualFile,
124 String revision) throws VcsException {
125 VirtualFile vcvf = getFileFromCache(virtualFile, revision);
126 if (vcvf != null) {
127 return vcvf;
128 } else {
129 AbstractVcs vcs = VcsUtil.getVcsFor(project, virtualFile);
130 if (vcs == null) {
131 return null;
132 }
133 VcsRevisionNumber vcsRevisionNumber = vcs.parseRevisionNumber(revision);
134 vcvf = getVcsVirtualFileImpl2(virtualFile, vcs, vcsRevisionNumber);
135 putFileInfoCache(vcvf, virtualFile, revision);
136 return vcvf;
137 }
138 }
139
140 @Nullable
141 private static VirtualFile getVcsVirtualFileImpl2(VirtualFile virtualFile, AbstractVcs vcs,
142 VcsRevisionNumber vcsRevisionNumber) throws VcsException {
143
144 DiffProvider diffProvider = vcs.getDiffProvider();
145 if (diffProvider == null) {
146 return null;
147 }
148 ContentRevision contentRevision = diffProvider.createFileContent(vcsRevisionNumber, virtualFile);
149 if (contentRevision == null) {
150 return null;
151 }
152
153
154 final String content = contentRevision.getContent();
155 if (content == null) {
156 return null;
157 }
158
159
160
161
162
163
164
165
166
167
168 return new VcsVirtualFile(contentRevision.getFile().getPath(), content.getBytes(),
169 vcsRevisionNumber.asString(), virtualFile.getFileSystem());
170 }
171
172
173
174
175
176
177
178
179
180
181
182 private static void fetchAndOpenFile(final Project project, final String revision, @NotNull final VirtualFile virtualFile,
183 final int line, final int column, @Nullable final OpenFileDescriptorAction action) {
184 VirtualFile file = getFileFromCache(virtualFile, revision);
185 if (file != null && action != null) {
186 OpenFileDescriptor fileDescriptor = new OpenFileDescriptor(project, file, line, column);
187 action.run(fileDescriptor);
188 return;
189 }
190
191 final String niceFileMessage = virtualFile.getName() + " (rev: " + revision + ") from VCS";
192 new FetchingFileTask(project, niceFileMessage, virtualFile, revision, line, column, action).queue();
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 private static void fetchAndOpenFileWithDiffs(final Project project, final String fromRevision, final String toRevision,
209 @NotNull final CommitType commitType, @NotNull final VirtualFile virtualFile,
210 final int line, final int column, @Nullable final OpenDiffAction action) {
211
212 VirtualFile referenceVirtualFile = getFileFromCache(virtualFile, fromRevision);
213 VirtualFile displayVirtualFile = getFileFromCache(virtualFile, toRevision);
214
215 if (referenceVirtualFile != null
216 && displayVirtualFile != null) {
217 OpenFileDescriptor displayDescriptor = new OpenFileDescriptor(project, displayVirtualFile, line, column);
218 action.run(displayDescriptor, referenceVirtualFile, commitType);
219 return;
220 }
221
222 final String niceFileMessage;
223 switch (commitType) {
224 case Added:
225 niceFileMessage = " " + virtualFile.getName() + " (rev: " + toRevision + ") from VCS";
226 break;
227 case Deleted:
228 niceFileMessage = " " + virtualFile.getName() + " (rev: " + fromRevision + ") from VCS";
229 break;
230 case Modified:
231 case Moved:
232 case Copied:
233 niceFileMessage = "s" + virtualFile.getName() + " (rev: " + fromRevision + ", " + toRevision + ") from VCS";
234 break;
235 case Unknown:
236 niceFileMessage = "s" + virtualFile.getName() + " (rev: " + fromRevision + ", " + toRevision + ") from VCS";
237 break;
238 default:
239 niceFileMessage = "s" + virtualFile.getName() + " (rev: " + fromRevision + ", " + toRevision + ") from VCS";
240 }
241
242 new FetchingTwoFilesTask(project, niceFileMessage, commitType, virtualFile, fromRevision,
243 toRevision, line, column, action).queue();
244 }
245
246
247
248
249
250
251
252
253
254
255
256 public static void openFile(final Project project, String filePath, @NotNull final String fileRevision,
257 final int line, final int col, @Nullable final OpenFileDescriptorAction action) {
258
259 VirtualFile baseDir = project.getBaseDir();
260 String baseUrl = getRepositoryUrlForFile(project, baseDir);
261
262 if (baseUrl != null && filePath.startsWith(baseUrl)) {
263 String relUrl = filePath.substring(baseUrl.length());
264 final VirtualFile vfl = VfsUtil.findRelativeFile(relUrl, baseDir);
265 ApplicationManager.getApplication().invokeLater(new Runnable() {
266 public void run() {
267 fetchAndOpenFile(project, fileRevision, vfl, line, col, action);
268 }
269 });
270 }
271
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287 public static void openFileWithDiffs(final Project project, String filePath, @NotNull final String fileRevision,
288 final String toRevision, @NotNull final CommitType commitType,
289 final int line, final int col, @Nullable final OpenDiffAction action) {
290
291 VirtualFile baseDir = project.getBaseDir();
292 String baseUrl = getRepositoryUrlForFile(project, baseDir);
293
294 if (baseUrl != null && filePath.startsWith(baseUrl)) {
295 String relUrl = filePath.substring(baseUrl.length());
296 final VirtualFile vfl = VfsUtil.findRelativeFile(relUrl, baseDir);
297 if (vfl != null) {
298
299 ApplicationManager.getApplication().invokeLater(new Runnable() {
300 public void run() {
301 fetchAndOpenFileWithDiffs(project, fileRevision, toRevision, commitType, vfl, line, col, action);
302 }
303 });
304 } else {
305 ApplicationManager.getApplication().invokeLater(new Runnable() {
306 public void run() {
307 switch (commitType) {
308 case Deleted:
309 Messages.showErrorDialog(project,
310 "Your project does not contain requested file. Please update to revision "
311 + fileRevision + " before review",
312 "File removed form repository");
313
314 break;
315 case Added:
316 Messages.showErrorDialog(project,
317 "Your project does not contain requested file. Please update to revision "
318 + toRevision + " before review",
319 "Project out of date");
320 break;
321 default:
322 Messages.showErrorDialog(project,
323 "Your project does not contain requested file. Please update before review",
324 "Project out of date");
325 break;
326 }
327 }
328 });
329
330 }
331 } else {
332 ApplicationManager.getApplication().invokeLater(new Runnable() {
333 public void run() {
334 Messages.showErrorDialog(project,
335 "Your project does not contain requested file.",
336 "File not found");
337 }
338 });
339 }
340 }
341
342 public static void openFile(final Project project, @NotNull final VirtualFile virtualFile,
343 @NotNull final String fileRevision, final int line, final int col,
344 @Nullable final OpenFileDescriptorAction action) {
345
346
347 ApplicationManager.getApplication().invokeLater(new Runnable() {
348 public void run() {
349 fetchAndOpenFile(project, fileRevision, virtualFile, line, col, action);
350 }
351 });
352 }
353
354 public interface OpenFileDescriptorAction {
355
356
357
358
359
360 void run(OpenFileDescriptor ofd);
361 }
362
363 public interface OpenDiffAction {
364
365
366
367
368
369
370
371 void run(OpenFileDescriptor displayFile, VirtualFile referenceDocument, CommitType commitType);
372 }
373
374 private static class FetchingTwoFilesTask extends Task.Backgroundable {
375 private OpenFileDescriptor displayDescriptor;
376 private VirtualFile referenceVirtualFile;
377
378 private VcsException exception;
379 private final Project project;
380 private final String niceFileMessage;
381 private final CommitType commitType;
382 private final VirtualFile virtualFile;
383 private final String fromRevision;
384 private final String toRevision;
385 private final int line;
386 private final int column;
387 private final OpenDiffAction action;
388
389 public FetchingTwoFilesTask(final Project project, final String niceFileMessage, final CommitType commitType,
390 final VirtualFile virtualFile, final String fromRevision, final String toRevision, final int line,
391 final int column,
392 final OpenDiffAction action) {
393 super(project, "Fetching file" + niceFileMessage, false);
394 this.project = project;
395 this.niceFileMessage = niceFileMessage;
396 this.commitType = commitType;
397 this.virtualFile = virtualFile;
398 this.fromRevision = fromRevision;
399 this.toRevision = toRevision;
400 this.line = line;
401 this.column = column;
402 this.action = action;
403 displayDescriptor = null;
404 referenceVirtualFile = null;
405 }
406
407 @Override
408 public boolean shouldStartInBackground() {
409 return false;
410 }
411
412 @Override
413 public void run(ProgressIndicator indicator) {
414 indicator.setIndeterminate(false);
415 VirtualFile displayVirtualFile = null;
416 try {
417 switch (commitType) {
418 case Modified:
419 case Moved:
420 case Copied:
421 referenceVirtualFile = getVcsVirtualFile(project, virtualFile, fromRevision);
422 displayVirtualFile = getVcsVirtualFile(project, virtualFile, toRevision);
423 break;
424 case Added:
425 displayVirtualFile = getVcsVirtualFile(project, virtualFile, toRevision);
426 break;
427 case Deleted:
428 referenceVirtualFile = getVcsVirtualFile(project, virtualFile, fromRevision);
429 displayVirtualFile = getVcsVirtualFile(project, virtualFile, toRevision);
430 break;
431 default:
432 break;
433 }
434 if (displayVirtualFile != null) {
435 displayDescriptor = new OpenFileDescriptor(project, displayVirtualFile, line, column);
436 } else {
437
438 displayDescriptor = null;
439 }
440 } catch (VcsException e) {
441 exception = e;
442 }
443 }
444
445 @Override
446 public void onSuccess() {
447 if (exception != null) {
448 Messages.showErrorDialog(project, "The following error has occured while fetching "
449 + niceFileMessage + ":\n" + exception.getMessage(), "Error fetching file");
450 return;
451 }
452 if (displayDescriptor != null) {
453 if (action != null) {
454 action.run(displayDescriptor, referenceVirtualFile, commitType);
455 }
456 displayDescriptor.navigate(true);
457 }
458
459 }
460 }
461
462 private static class FetchingFileTask extends Task.Backgroundable {
463
464 private OpenFileDescriptor ofd;
465
466 private VcsException exception;
467 private final Project project;
468 private final String niceFileMessage;
469 private final VirtualFile virtualFile;
470 private final String revision;
471 private final int line;
472 private final int column;
473 private final OpenFileDescriptorAction action;
474
475 public FetchingFileTask(final Project project, final String niceFileMessage, final VirtualFile virtualFile,
476 final String revision,
477 final int line, final int column, final OpenFileDescriptorAction action) {
478 super(project, "Fetching file " + niceFileMessage, false);
479 this.project = project;
480 this.niceFileMessage = niceFileMessage;
481 this.virtualFile = virtualFile;
482 this.revision = revision;
483 this.line = line;
484 this.column = column;
485 this.action = action;
486 }
487
488 @Override
489 public boolean shouldStartInBackground() {
490 return false;
491 }
492
493 @Override
494 public void run(ProgressIndicator indicator) {
495
496 indicator.setIndeterminate(true);
497 final VirtualFile myVirtualFile;
498 try {
499 myVirtualFile = getVcsVirtualFile(project, virtualFile, revision);
500 } catch (VcsException e) {
501 exception = e;
502 return;
503 }
504 if (myVirtualFile != null) {
505 ofd = new OpenFileDescriptor(project, myVirtualFile, line, column);
506 }
507 }
508
509 @Override
510 public void onSuccess() {
511 if (exception != null) {
512 Messages.showErrorDialog(project, "The following error has occured while fetching "
513 + niceFileMessage + ":\n" + exception.getMessage(), "Error fetching file");
514 return;
515 }
516 if (ofd != null) {
517 if (action != null) {
518 action.run(ofd);
519 }
520 ofd.navigate(true);
521 }
522
523 }
524 }
525 }