IDEADEV-41714 ("Collecting Information on Changes" hangs entire GUI) r=yole
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnUtil.java
blobcb9142fab1ea6b6609ab7d85bcd0781b657a3a2c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.idea.svn;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.progress.ProcessCanceledException;
20 import com.intellij.openapi.progress.ProgressIndicator;
21 import com.intellij.openapi.progress.ProgressManager;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Computable;
24 import com.intellij.openapi.vcs.AbstractVcsHelper;
25 import com.intellij.openapi.vcs.RepositoryLocation;
26 import com.intellij.openapi.vcs.VcsException;
27 import com.intellij.openapi.vfs.LocalFileSystem;
28 import com.intellij.openapi.vfs.VfsUtil;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.openapi.vfs.VirtualFileManager;
31 import com.intellij.openapi.wm.WindowManager;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.NotNullFunction;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew;
37 import org.jetbrains.idea.svn.dialogs.LockDialog;
38 import org.jetbrains.idea.svn.dialogs.WCInfo;
39 import org.tmatesoft.svn.core.SVNException;
40 import org.tmatesoft.svn.core.SVNURL;
41 import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
42 import org.tmatesoft.svn.core.io.SVNCapability;
43 import org.tmatesoft.svn.core.io.SVNRepository;
44 import org.tmatesoft.svn.core.wc.*;
46 import java.io.File;
47 import java.util.*;
49 public class SvnUtil {
50 @NonNls public static final String SVN_ADMIN_DIR_NAME = SVNFileUtil.getAdminDirectoryName();
51 @NonNls public static final String ENTRIES_FILE_NAME = "entries";
52 @NonNls public static final String DIR_PROPS_FILE_NAME = "dir-props";
53 @NonNls public static final String PATH_TO_LOCK_FILE = SVN_ADMIN_DIR_NAME + "/lock";
54 @NonNls public static final String LOCK_FILE_NAME = "lock";
56 private SvnUtil() {
59 public static void crawlWCRoots(VirtualFile path, NotNullFunction<VirtualFile, Collection<VirtualFile>> callback) {
60 if (path == null) {
61 return;
63 final boolean isDirectory = path.isDirectory();
64 final VirtualFile parentVFile = (!isDirectory) || (!path.exists()) ? path.getParent() : path;
65 if (parentVFile == null) {
66 return;
68 final File parent = new File(parentVFile.getPath());
70 if (SVNWCUtil.isVersionedDirectory(parent)) {
71 checkCanceled();
72 final Collection<VirtualFile> pending = callback.fun(path);
73 checkCanceled();
74 for (VirtualFile virtualFile : pending) {
75 crawlWCRoots(virtualFile, callback);
78 else if (isDirectory) {
79 checkCanceled();
80 VirtualFile[] children = path.getChildren();
81 for (int i = 0; children != null && i < children.length; i++) {
82 checkCanceled();
83 VirtualFile child = children[i];
84 if (child.isDirectory()) {
85 crawlWCRoots(child, callback);
91 public static Collection<File> crawlWCRoots(File path, SvnWCRootCrawler callback, ProgressIndicator progress) {
92 final Collection<File> result = new HashSet<File>();
93 File parent = path.isFile() || !path.exists() ? path.getParentFile() : path;
94 if (SVNWCUtil.isVersionedDirectory(parent)) {
95 checkCanceled(progress);
96 final Collection<File> pending = callback.handleWorkingCopyRoot(path, progress);
97 checkCanceled(progress);
98 for (final File aPending : pending) {
99 result.addAll(crawlWCRoots(aPending, callback, progress));
101 result.add(path);
103 else if (path.isDirectory()) {
104 checkCanceled(progress);
105 File[] children = path.listFiles();
106 for (int i = 0; children != null && i < children.length; i++) {
107 checkCanceled(progress);
108 File child = children[i];
109 if (child.isDirectory()) {
110 result.addAll(crawlWCRoots(child, callback, progress));
114 return result;
117 private static void checkCanceled() {
118 final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
119 checkCanceled(indicator);
122 private static void checkCanceled(final ProgressIndicator progress) {
123 if (progress != null && progress.isCanceled()) {
124 throw new ProcessCanceledException();
128 public static String[] getLocationsForModule(final SvnVcs vcs, File path, ProgressIndicator progress) {
129 LocationsCrawler crawler = new LocationsCrawler(vcs);
130 crawlWCRoots(path, crawler, progress);
131 return crawler.getLocations();
134 public static Map<String, File> getLocationInfoForModule(final SvnVcs vcs, File path, ProgressIndicator progress) {
135 final LocationsCrawler crawler = new LocationsCrawler(vcs);
136 crawlWCRoots(path, crawler, progress);
137 return crawler.getLocationInfos();
140 public static void doLockFiles(Project project, final SvnVcs activeVcs, final File[] ioFiles) throws VcsException {
141 final String lockMessage;
142 final boolean force;
143 // TODO[yole]: check for shift pressed
144 if (activeVcs.getCheckoutOptions().getValue()) {
145 LockDialog dialog = new LockDialog(project, true, ioFiles != null && ioFiles.length > 1);
146 dialog.show();
147 if (!dialog.isOK()) {
148 return;
150 lockMessage = dialog.getComment();
151 force = dialog.isForce();
153 else {
154 lockMessage = "";
155 force = false;
158 final SVNException[] exception = new SVNException[1];
159 final Collection<String> failedLocks = new ArrayList<String>();
160 final int[] count = new int[]{ioFiles.length};
161 final ISVNEventHandler eventHandler = new ISVNEventHandler() {
162 public void handleEvent(SVNEvent event, double progress) {
163 if (event.getAction() == SVNEventAction.LOCK_FAILED) {
164 failedLocks.add(event.getErrorMessage() != null ?
165 event.getErrorMessage().getFullMessage() :
166 event.getFile().getAbsolutePath());
167 count[0]--;
171 public void checkCancelled() {
175 Runnable command = new Runnable() {
176 public void run() {
177 ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
178 SVNWCClient wcClient;
180 try {
181 wcClient = activeVcs.createWCClient();
182 wcClient.setEventHandler(eventHandler);
183 if (progress != null) {
184 progress.setText(SvnBundle.message("progress.text.locking.files"));
186 for (File ioFile : ioFiles) {
187 if (progress != null) {
188 progress.checkCanceled();
190 File file = ioFile;
191 if (progress != null) {
192 progress.setText2(SvnBundle.message("progress.text2.processing.file", file.getName()));
194 wcClient.doLock(new File[]{file}, force, lockMessage);
197 catch (SVNException e) {
198 exception[0] = e;
203 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.lock.files"), false, project);
204 if (!failedLocks.isEmpty()) {
205 String[] failedFiles = ArrayUtil.toStringArray(failedLocks);
206 List<VcsException> exceptions = new ArrayList<VcsException>();
208 for (String file : failedFiles) {
209 exceptions.add(new VcsException(SvnBundle.message("exception.text.locking.file.failed", file)));
211 AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.lock.failures"));
214 WindowManager.getInstance().getStatusBar(project).setInfo(SvnBundle.message("message.text.files.locked", count[0]));
215 if (exception[0] != null) {
216 throw new VcsException(exception[0]);
220 public static void doUnlockFiles(Project project, final SvnVcs activeVcs, final File[] ioFiles) throws VcsException {
221 final boolean force = true;
222 final SVNException[] exception = new SVNException[1];
223 final Collection<String> failedUnlocks = new ArrayList<String>();
224 final int[] count = new int[]{ioFiles.length};
225 final ISVNEventHandler eventHandler = new ISVNEventHandler() {
226 public void handleEvent(SVNEvent event, double progress) {
227 if (event.getAction() == SVNEventAction.UNLOCK_FAILED) {
228 failedUnlocks.add(event.getErrorMessage() != null ?
229 event.getErrorMessage().getFullMessage() :
230 event.getFile().getAbsolutePath());
231 count[0]--;
235 public void checkCancelled() {
239 Runnable command = new Runnable() {
240 public void run() {
241 ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
242 SVNWCClient wcClient;
244 try {
245 wcClient = activeVcs.createWCClient();
246 wcClient.setEventHandler(eventHandler);
247 if (progress != null) {
248 progress.setText(SvnBundle.message("progress.text.unlocking.files"));
250 for (File ioFile : ioFiles) {
251 if (progress != null) {
252 progress.checkCanceled();
254 File file = ioFile;
255 if (progress != null) {
256 progress.setText2(SvnBundle.message("progress.text2.processing.file", file.getName()));
258 wcClient.doUnlock(new File[]{file}, force);
261 catch (SVNException e) {
262 exception[0] = e;
267 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.unlock.files"), false, project);
268 if (!failedUnlocks.isEmpty()) {
269 String[] failedFiles = ArrayUtil.toStringArray(failedUnlocks);
270 List<VcsException> exceptions = new ArrayList<VcsException>();
272 for (String file : failedFiles) {
273 exceptions.add(new VcsException(SvnBundle.message("exception.text.failed.to.unlock.file", file)));
275 AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.unlock.failures"));
278 WindowManager.getInstance().getStatusBar(project).setInfo(SvnBundle.message("message.text.files.unlocked", count[0]));
279 if (exception[0] != null) {
280 throw new VcsException(exception[0]);
284 public static String formatRepresentation(final WorkingCopyFormat format) {
285 if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) {
286 return SvnBundle.message("dialog.show.svn.map.table.version16.text");
287 } else if (WorkingCopyFormat.ONE_DOT_FIVE.equals(format)) {
288 return SvnBundle.message("dialog.show.svn.map.table.version15.text");
289 } else if (WorkingCopyFormat.ONE_DOT_FOUR.equals(format)) {
290 return SvnBundle.message("dialog.show.svn.map.table.version14.text");
291 } else if (WorkingCopyFormat.ONE_DOT_THREE.equals(format)) {
292 return SvnBundle.message("dialog.show.svn.map.table.version13.text");
294 return "";
297 private static class LocationsCrawler implements SvnWCRootCrawler {
298 private final SvnVcs myVcs;
299 private final Map<String, File> myLocations;
301 public LocationsCrawler(SvnVcs vcs) {
302 myVcs = vcs;
303 myLocations = new HashMap<String, File>();
306 public String[] getLocations() {
307 final Set<String> set = myLocations.keySet();
308 return ArrayUtil.toStringArray(set);
311 public Map<String, File> getLocationInfos() {
312 return Collections.unmodifiableMap(myLocations);
315 public Collection<File> handleWorkingCopyRoot(File root, ProgressIndicator progress) {
316 final Collection<File> result = new HashSet<File>();
317 if (progress != null) {
318 progress.setText(SvnBundle.message("progress.text.discovering.location", root.getAbsolutePath()));
320 try {
321 SVNWCClient wcClient = myVcs.createWCClient();
322 SVNInfo info = wcClient.doInfo(root, SVNRevision.WORKING);
323 if (info != null && info.getURL() != null) {
324 myLocations.put(info.getURL().toString(), info.getFile());
327 catch (SVNException e) {
330 return result;
334 @Nullable
335 public static String getRepositoryUUID(final SvnVcs vcs, final File file) {
336 final SVNWCClient client = vcs.createWCClient();
337 try {
338 final SVNInfo info = client.doInfo(file, SVNRevision.WORKING);
339 return (info == null) ? null : info.getRepositoryUUID();
340 } catch (SVNException e) {
341 return null;
345 @Nullable
346 public static String getRepositoryUUID(final SvnVcs vcs, final SVNURL url) {
347 final SVNWCClient client = vcs.createWCClient();
348 try {
349 final SVNInfo info = client.doInfo(url, SVNRevision.WORKING, SVNRevision.WORKING);
350 return (info == null) ? null : info.getRepositoryUUID();
351 } catch (SVNException e) {
352 return null;
356 @Nullable
357 public static SVNURL getRepositoryRoot(final SvnVcs vcs, final File file) {
358 final SVNWCClient client = vcs.createWCClient();
359 try {
360 final SVNInfo info = client.doInfo(file, SVNRevision.WORKING);
361 return (info == null) ? null : info.getRepositoryRootURL();
362 } catch (SVNException e) {
363 return null;
367 @Nullable
368 public static SVNURL getRepositoryRoot(final SvnVcs vcs, final String url) {
369 try {
370 return getRepositoryRoot(vcs, SVNURL.parseURIEncoded(url));
372 catch (SVNException e) {
373 return null;
377 @Nullable
378 public static SVNURL getRepositoryRoot(final SvnVcs vcs, final SVNURL url) {
379 final SVNWCClient client = vcs.createWCClient();
380 try {
381 SVNInfo info = client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD);
382 return (info == null) ? null : info.getRepositoryRootURL();
383 } catch (SVNException e) {
384 return null;
388 public static boolean isWorkingCopyRoot(final File file) {
389 try {
390 return SVNWCUtil.isWorkingCopyRoot(file);
391 } catch (SVNException e) {
392 return false;
396 @Nullable
397 public static File getWorkingCopyRoot(final File inFile) {
398 File file = inFile;
399 while ((file != null) && (file.isFile() || (! file.exists()))) {
400 file = file.getParentFile();
403 if (file == null) {
404 return null;
407 try {
408 return SVNWCUtil.getWorkingCopyRoot(file, true);
409 } catch (SVNException e) {
410 return null;
414 @Nullable
415 public static SVNURL getWorkingCopyUrl(final SvnVcs vcs, final File file) {
416 try {
417 if(SVNWCUtil.isWorkingCopyRoot(file)) {
418 final SVNWCClient client = vcs.createWCClient();
419 final SVNInfo info = client.doInfo(file, SVNRevision.WORKING);
420 return info.getURL();
422 } catch (SVNException e) {
425 return null;
428 public static File fileFromUrl(final File baseDir, final String baseUrl, final String fullUrl) throws SVNException {
429 assert fullUrl.startsWith(baseUrl);
431 final String part = fullUrl.substring(baseUrl.length()).replace('/', File.separatorChar).replace('\\', File.separatorChar);
432 return new File(baseDir, part);
435 public static VirtualFile getVirtualFile(final String filePath) {
436 @NonNls final String path = VfsUtil.pathToUrl(filePath.replace(File.separatorChar, '/'));
437 return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
438 @Nullable
439 public VirtualFile compute() {
440 return VirtualFileManager.getInstance().findFileByUrl(path);
445 @Nullable
446 public static SVNURL getBranchForUrl(final SvnVcs vcs, final VirtualFile vcsRoot, final String urlPath) {
447 final SvnBranchConfigurationNew configuration;
448 try {
449 final SVNURL url = SVNURL.parseURIEncoded(urlPath);
450 configuration = SvnBranchConfigurationManager.getInstance(vcs.getProject()).get(vcsRoot);
451 return (configuration == null) ? null : configuration.getWorkingBranch(url);
453 catch (SVNException e) {
454 return null;
455 } catch (VcsException e1) {
456 return null;
460 @Nullable
461 public static String getPathForProgress(final SVNEvent event) {
462 if (event.getFile() != null) {
463 return event.getFile().getName();
465 if (event.getURL() != null) {
466 return event.getURL().toString();
468 return null;
471 @Nullable
472 public static VirtualFile correctRoot(final Project project, final String path) {
473 if (path.length() == 0) {
474 // project root
475 return project.getBaseDir();
477 return LocalFileSystem.getInstance().findFileByPath(path);
480 @Nullable
481 public static VirtualFile correctRoot(final Project project, final VirtualFile file) {
482 if (file.getPath().length() == 0) {
483 // project root
484 return project.getBaseDir();
486 return file;
489 public static boolean isOneDotFiveAvailable(final Project project, final RepositoryLocation location) {
490 final SvnVcs vcs = SvnVcs.getInstance(project);
491 final List<WCInfo> infos = vcs.getAllWcInfos();
493 for (WCInfo info : infos) {
494 if (! info.getFormat().supportsMergeInfo()) {
495 continue;
498 final String url = info.getUrl().toString();
499 if ((location != null) && (! location.toPresentableString().startsWith(url)) &&
500 (! url.startsWith(location.toPresentableString()))) {
501 continue;
503 if (! checkRepositoryVersion15(vcs, url)) {
504 continue;
506 return true;
508 return false;
511 public static boolean checkRepositoryVersion15(final SvnVcs vcs, final String url) {
512 SVNRepository repository = null;
513 try {
514 repository = vcs.createRepository(url);
515 return repository.hasCapability(SVNCapability.MERGE_INFO);
517 catch (SVNException e) {
518 return false;
520 finally {
521 if (repository != null) {
522 repository.closeSession();
527 public static SVNStatus getStatus(final SvnVcs vcs, final File file) {
528 final SVNStatusClient statusClient = vcs.createStatusClient();
529 try {
530 return statusClient.doStatus(file, false);
532 catch (SVNException e) {
533 return null;
537 public static boolean seemsLikeVersionedDir(final VirtualFile file) {
538 final String adminName = SVNFileUtil.getAdminDirectoryName();
539 final VirtualFile[] children = file.getChildren();
540 for (VirtualFile child : children) {
541 if (adminName.equals(child.getName()) && child.isDirectory()) {
542 return true;
545 return false;
548 public static boolean isAdminDirectory(final VirtualFile file) {
549 return isAdminDirectory(file.getParent(), file.getName());
552 public static boolean isAdminDirectory(File parent, final String name) {
553 if (name.equals(SVN_ADMIN_DIR_NAME)) {
554 return true;
556 if (parent != null) {
557 if (parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
558 return true;
560 parent = parent.getParentFile();
561 if (parent != null && parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
562 return true;
565 return false;
568 public static boolean isAdminDirectory(VirtualFile parent, String name) {
569 // never allow to delete admin directories by themselves (this can happen during LVCS undo,
570 // which deletes created directories from bottom to top)
571 if (name.equals(SVN_ADMIN_DIR_NAME)) {
572 return true;
574 if (parent != null) {
575 if (parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
576 return true;
578 parent = parent.getParent();
579 if (parent != null && parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
580 return true;
583 return false;