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
.*;
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";
59 public static void crawlWCRoots(VirtualFile path
, NotNullFunction
<VirtualFile
, Collection
<VirtualFile
>> callback
) {
63 final boolean isDirectory
= path
.isDirectory();
64 final VirtualFile parentVFile
= (!isDirectory
) || (!path
.exists()) ? path
.getParent() : path
;
65 if (parentVFile
== null) {
68 final File parent
= new File(parentVFile
.getPath());
70 if (SVNWCUtil
.isVersionedDirectory(parent
)) {
72 final Collection
<VirtualFile
> pending
= callback
.fun(path
);
74 for (VirtualFile virtualFile
: pending
) {
75 crawlWCRoots(virtualFile
, callback
);
78 else if (isDirectory
) {
80 VirtualFile
[] children
= path
.getChildren();
81 for (int i
= 0; children
!= null && i
< children
.length
; i
++) {
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
));
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
));
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
;
143 // TODO[yole]: check for shift pressed
144 if (activeVcs
.getCheckoutOptions().getValue()) {
145 LockDialog dialog
= new LockDialog(project
, true, ioFiles
!= null && ioFiles
.length
> 1);
147 if (!dialog
.isOK()) {
150 lockMessage
= dialog
.getComment();
151 force
= dialog
.isForce();
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());
171 public void checkCancelled() {
175 Runnable command
= new Runnable() {
177 ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
178 SVNWCClient wcClient
;
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();
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
) {
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());
235 public void checkCancelled() {
239 Runnable command
= new Runnable() {
241 ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
242 SVNWCClient wcClient
;
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();
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
) {
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");
297 private static class LocationsCrawler
implements SvnWCRootCrawler
{
298 private final SvnVcs myVcs
;
299 private final Map
<String
, File
> myLocations
;
301 public LocationsCrawler(SvnVcs 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()));
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
) {
335 public static String
getRepositoryUUID(final SvnVcs vcs
, final File file
) {
336 final SVNWCClient client
= vcs
.createWCClient();
338 final SVNInfo info
= client
.doInfo(file
, SVNRevision
.WORKING
);
339 return (info
== null) ?
null : info
.getRepositoryUUID();
340 } catch (SVNException e
) {
346 public static String
getRepositoryUUID(final SvnVcs vcs
, final SVNURL url
) {
347 final SVNWCClient client
= vcs
.createWCClient();
349 final SVNInfo info
= client
.doInfo(url
, SVNRevision
.WORKING
, SVNRevision
.WORKING
);
350 return (info
== null) ?
null : info
.getRepositoryUUID();
351 } catch (SVNException e
) {
357 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final File file
) {
358 final SVNWCClient client
= vcs
.createWCClient();
360 final SVNInfo info
= client
.doInfo(file
, SVNRevision
.WORKING
);
361 return (info
== null) ?
null : info
.getRepositoryRootURL();
362 } catch (SVNException e
) {
368 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final String url
) {
370 return getRepositoryRoot(vcs
, SVNURL
.parseURIEncoded(url
));
372 catch (SVNException e
) {
378 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final SVNURL url
) {
379 final SVNWCClient client
= vcs
.createWCClient();
381 SVNInfo info
= client
.doInfo(url
, SVNRevision
.UNDEFINED
, SVNRevision
.HEAD
);
382 return (info
== null) ?
null : info
.getRepositoryRootURL();
383 } catch (SVNException e
) {
388 public static boolean isWorkingCopyRoot(final File file
) {
390 return SVNWCUtil
.isWorkingCopyRoot(file
);
391 } catch (SVNException e
) {
397 public static File
getWorkingCopyRoot(final File inFile
) {
399 while ((file
!= null) && (file
.isFile() || (! file
.exists()))) {
400 file
= file
.getParentFile();
408 return SVNWCUtil
.getWorkingCopyRoot(file
, true);
409 } catch (SVNException e
) {
415 public static SVNURL
getWorkingCopyUrl(final SvnVcs vcs
, final File file
) {
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
) {
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
>() {
439 public VirtualFile
compute() {
440 return VirtualFileManager
.getInstance().findFileByUrl(path
);
446 public static SVNURL
getBranchForUrl(final SvnVcs vcs
, final VirtualFile vcsRoot
, final String urlPath
) {
447 final SvnBranchConfigurationNew configuration
;
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
) {
455 } catch (VcsException e1
) {
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();
472 public static VirtualFile
correctRoot(final Project project
, final String path
) {
473 if (path
.length() == 0) {
475 return project
.getBaseDir();
477 return LocalFileSystem
.getInstance().findFileByPath(path
);
481 public static VirtualFile
correctRoot(final Project project
, final VirtualFile file
) {
482 if (file
.getPath().length() == 0) {
484 return project
.getBaseDir();
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()) {
498 final String url
= info
.getUrl().toString();
499 if ((location
!= null) && (! location
.toPresentableString().startsWith(url
)) &&
500 (! url
.startsWith(location
.toPresentableString()))) {
503 if (! checkRepositoryVersion15(vcs
, url
)) {
511 public static boolean checkRepositoryVersion15(final SvnVcs vcs
, final String url
) {
512 SVNRepository repository
= null;
514 repository
= vcs
.createRepository(url
);
515 return repository
.hasCapability(SVNCapability
.MERGE_INFO
);
517 catch (SVNException e
) {
521 if (repository
!= null) {
522 repository
.closeSession();
527 public static SVNStatus
getStatus(final SvnVcs vcs
, final File file
) {
528 final SVNStatusClient statusClient
= vcs
.createStatusClient();
530 return statusClient
.doStatus(file
, false);
532 catch (SVNException e
) {
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()) {
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
)) {
556 if (parent
!= null) {
557 if (parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
560 parent
= parent
.getParentFile();
561 if (parent
!= null && parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
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
)) {
574 if (parent
!= null) {
575 if (parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
578 parent
= parent
.getParent();
579 if (parent
!= null && parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {