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
.internal
.wc
.admin
.SVNEntry
;
43 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNWCAccess
;
44 import org
.tmatesoft
.svn
.core
.io
.SVNCapability
;
45 import org
.tmatesoft
.svn
.core
.io
.SVNRepository
;
46 import org
.tmatesoft
.svn
.core
.wc
.*;
51 public class SvnUtil
{
52 @NonNls public static final String SVN_ADMIN_DIR_NAME
= SVNFileUtil
.getAdminDirectoryName();
53 @NonNls public static final String ENTRIES_FILE_NAME
= "entries";
54 @NonNls public static final String DIR_PROPS_FILE_NAME
= "dir-props";
55 @NonNls public static final String PATH_TO_LOCK_FILE
= SVN_ADMIN_DIR_NAME
+ "/lock";
56 @NonNls public static final String LOCK_FILE_NAME
= "lock";
61 public static void crawlWCRoots(VirtualFile path
, NotNullFunction
<VirtualFile
, Collection
<VirtualFile
>> callback
) {
65 final boolean isDirectory
= path
.isDirectory();
66 final VirtualFile parentVFile
= (!isDirectory
) || (!path
.exists()) ? path
.getParent() : path
;
67 if (parentVFile
== null) {
70 final File parent
= new File(parentVFile
.getPath());
72 if (SVNWCUtil
.isVersionedDirectory(parent
)) {
74 final Collection
<VirtualFile
> pending
= callback
.fun(path
);
76 for (VirtualFile virtualFile
: pending
) {
77 crawlWCRoots(virtualFile
, callback
);
80 else if (isDirectory
) {
82 VirtualFile
[] children
= path
.getChildren();
83 for (int i
= 0; children
!= null && i
< children
.length
; i
++) {
85 VirtualFile child
= children
[i
];
86 if (child
.isDirectory()) {
87 crawlWCRoots(child
, callback
);
93 public static Collection
<File
> crawlWCRoots(File path
, SvnWCRootCrawler callback
, ProgressIndicator progress
) {
94 final Collection
<File
> result
= new HashSet
<File
>();
95 File parent
= path
.isFile() || !path
.exists() ? path
.getParentFile() : path
;
96 if (SVNWCUtil
.isVersionedDirectory(parent
)) {
97 checkCanceled(progress
);
98 final Collection
<File
> pending
= callback
.handleWorkingCopyRoot(path
, progress
);
99 checkCanceled(progress
);
100 for (final File aPending
: pending
) {
101 result
.addAll(crawlWCRoots(aPending
, callback
, progress
));
105 else if (path
.isDirectory()) {
106 checkCanceled(progress
);
107 File
[] children
= path
.listFiles();
108 for (int i
= 0; children
!= null && i
< children
.length
; i
++) {
109 checkCanceled(progress
);
110 File child
= children
[i
];
111 if (child
.isDirectory()) {
112 result
.addAll(crawlWCRoots(child
, callback
, progress
));
119 private static void checkCanceled() {
120 final ProgressIndicator indicator
= ProgressManager
.getInstance().getProgressIndicator();
121 checkCanceled(indicator
);
124 private static void checkCanceled(final ProgressIndicator progress
) {
125 if (progress
!= null && progress
.isCanceled()) {
126 throw new ProcessCanceledException();
130 public static String
[] getLocationsForModule(final SvnVcs vcs
, File path
, ProgressIndicator progress
) {
131 LocationsCrawler crawler
= new LocationsCrawler(vcs
);
132 crawlWCRoots(path
, crawler
, progress
);
133 return crawler
.getLocations();
136 public static Map
<String
, File
> getLocationInfoForModule(final SvnVcs vcs
, File path
, ProgressIndicator progress
) {
137 final LocationsCrawler crawler
= new LocationsCrawler(vcs
);
138 crawlWCRoots(path
, crawler
, progress
);
139 return crawler
.getLocationInfos();
142 public static void doLockFiles(Project project
, final SvnVcs activeVcs
, final File
[] ioFiles
) throws VcsException
{
143 final String lockMessage
;
145 // TODO[yole]: check for shift pressed
146 if (activeVcs
.getCheckoutOptions().getValue()) {
147 LockDialog dialog
= new LockDialog(project
, true, ioFiles
!= null && ioFiles
.length
> 1);
149 if (!dialog
.isOK()) {
152 lockMessage
= dialog
.getComment();
153 force
= dialog
.isForce();
160 final SVNException
[] exception
= new SVNException
[1];
161 final Collection
<String
> failedLocks
= new ArrayList
<String
>();
162 final int[] count
= new int[]{ioFiles
.length
};
163 final ISVNEventHandler eventHandler
= new ISVNEventHandler() {
164 public void handleEvent(SVNEvent event
, double progress
) {
165 if (event
.getAction() == SVNEventAction
.LOCK_FAILED
) {
166 failedLocks
.add(event
.getErrorMessage() != null ?
167 event
.getErrorMessage().getFullMessage() :
168 event
.getFile().getAbsolutePath());
173 public void checkCancelled() {
177 Runnable command
= new Runnable() {
179 ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
180 SVNWCClient wcClient
;
183 wcClient
= activeVcs
.createWCClient();
184 wcClient
.setEventHandler(eventHandler
);
185 if (progress
!= null) {
186 progress
.setText(SvnBundle
.message("progress.text.locking.files"));
188 for (File ioFile
: ioFiles
) {
189 if (progress
!= null) {
190 progress
.checkCanceled();
193 if (progress
!= null) {
194 progress
.setText2(SvnBundle
.message("progress.text2.processing.file", file
.getName()));
196 wcClient
.doLock(new File
[]{file
}, force
, lockMessage
);
199 catch (SVNException e
) {
205 ProgressManager
.getInstance().runProcessWithProgressSynchronously(command
, SvnBundle
.message("progress.title.lock.files"), false, project
);
206 if (!failedLocks
.isEmpty()) {
207 String
[] failedFiles
= ArrayUtil
.toStringArray(failedLocks
);
208 List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
210 for (String file
: failedFiles
) {
211 exceptions
.add(new VcsException(SvnBundle
.message("exception.text.locking.file.failed", file
)));
213 AbstractVcsHelper
.getInstance(project
).showErrors(exceptions
, SvnBundle
.message("message.title.lock.failures"));
216 WindowManager
.getInstance().getStatusBar(project
).setInfo(SvnBundle
.message("message.text.files.locked", count
[0]));
217 if (exception
[0] != null) {
218 throw new VcsException(exception
[0]);
222 public static void doUnlockFiles(Project project
, final SvnVcs activeVcs
, final File
[] ioFiles
) throws VcsException
{
223 final boolean force
= true;
224 final SVNException
[] exception
= new SVNException
[1];
225 final Collection
<String
> failedUnlocks
= new ArrayList
<String
>();
226 final int[] count
= new int[]{ioFiles
.length
};
227 final ISVNEventHandler eventHandler
= new ISVNEventHandler() {
228 public void handleEvent(SVNEvent event
, double progress
) {
229 if (event
.getAction() == SVNEventAction
.UNLOCK_FAILED
) {
230 failedUnlocks
.add(event
.getErrorMessage() != null ?
231 event
.getErrorMessage().getFullMessage() :
232 event
.getFile().getAbsolutePath());
237 public void checkCancelled() {
241 Runnable command
= new Runnable() {
243 ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
244 SVNWCClient wcClient
;
247 wcClient
= activeVcs
.createWCClient();
248 wcClient
.setEventHandler(eventHandler
);
249 if (progress
!= null) {
250 progress
.setText(SvnBundle
.message("progress.text.unlocking.files"));
252 for (File ioFile
: ioFiles
) {
253 if (progress
!= null) {
254 progress
.checkCanceled();
257 if (progress
!= null) {
258 progress
.setText2(SvnBundle
.message("progress.text2.processing.file", file
.getName()));
260 wcClient
.doUnlock(new File
[]{file
}, force
);
263 catch (SVNException e
) {
269 ProgressManager
.getInstance().runProcessWithProgressSynchronously(command
, SvnBundle
.message("progress.title.unlock.files"), false, project
);
270 if (!failedUnlocks
.isEmpty()) {
271 String
[] failedFiles
= ArrayUtil
.toStringArray(failedUnlocks
);
272 List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
274 for (String file
: failedFiles
) {
275 exceptions
.add(new VcsException(SvnBundle
.message("exception.text.failed.to.unlock.file", file
)));
277 AbstractVcsHelper
.getInstance(project
).showErrors(exceptions
, SvnBundle
.message("message.title.unlock.failures"));
280 WindowManager
.getInstance().getStatusBar(project
).setInfo(SvnBundle
.message("message.text.files.unlocked", count
[0]));
281 if (exception
[0] != null) {
282 throw new VcsException(exception
[0]);
286 public static String
formatRepresentation(final WorkingCopyFormat format
) {
287 if (WorkingCopyFormat
.ONE_DOT_SIX
.equals(format
)) {
288 return SvnBundle
.message("dialog.show.svn.map.table.version16.text");
289 } else if (WorkingCopyFormat
.ONE_DOT_FIVE
.equals(format
)) {
290 return SvnBundle
.message("dialog.show.svn.map.table.version15.text");
291 } else if (WorkingCopyFormat
.ONE_DOT_FOUR
.equals(format
)) {
292 return SvnBundle
.message("dialog.show.svn.map.table.version14.text");
293 } else if (WorkingCopyFormat
.ONE_DOT_THREE
.equals(format
)) {
294 return SvnBundle
.message("dialog.show.svn.map.table.version13.text");
299 private static class LocationsCrawler
implements SvnWCRootCrawler
{
300 private final SvnVcs myVcs
;
301 private final Map
<String
, File
> myLocations
;
303 public LocationsCrawler(SvnVcs vcs
) {
305 myLocations
= new HashMap
<String
, File
>();
308 public String
[] getLocations() {
309 final Set
<String
> set
= myLocations
.keySet();
310 return ArrayUtil
.toStringArray(set
);
313 public Map
<String
, File
> getLocationInfos() {
314 return Collections
.unmodifiableMap(myLocations
);
317 public Collection
<File
> handleWorkingCopyRoot(File root
, ProgressIndicator progress
) {
318 final Collection
<File
> result
= new HashSet
<File
>();
319 if (progress
!= null) {
320 progress
.setText(SvnBundle
.message("progress.text.discovering.location", root
.getAbsolutePath()));
323 SVNWCClient wcClient
= myVcs
.createWCClient();
324 SVNInfo info
= wcClient
.doInfo(root
, SVNRevision
.WORKING
);
325 if (info
!= null && info
.getURL() != null) {
326 myLocations
.put(info
.getURL().toString(), info
.getFile());
329 catch (SVNException e
) {
337 public static String
getRepositoryUUID(final SvnVcs vcs
, final File file
) {
338 final SVNWCClient client
= vcs
.createWCClient();
340 final SVNInfo info
= client
.doInfo(file
, SVNRevision
.WORKING
);
341 return (info
== null) ?
null : info
.getRepositoryUUID();
342 } catch (SVNException e
) {
348 public static String
getRepositoryUUID(final SvnVcs vcs
, final SVNURL url
) {
349 final SVNWCClient client
= vcs
.createWCClient();
351 final SVNInfo info
= client
.doInfo(url
, SVNRevision
.WORKING
, SVNRevision
.WORKING
);
352 return (info
== null) ?
null : info
.getRepositoryUUID();
353 } catch (SVNException e
) {
359 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final File file
) {
360 final SVNWCClient client
= vcs
.createWCClient();
362 final SVNInfo info
= client
.doInfo(file
, SVNRevision
.WORKING
);
363 return (info
== null) ?
null : info
.getRepositoryRootURL();
364 } catch (SVNException e
) {
370 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final String url
) {
372 return getRepositoryRoot(vcs
, SVNURL
.parseURIEncoded(url
));
374 catch (SVNException e
) {
380 public static SVNURL
getRepositoryRoot(final SvnVcs vcs
, final SVNURL url
) {
381 final SVNWCClient client
= vcs
.createWCClient();
383 SVNInfo info
= client
.doInfo(url
, SVNRevision
.UNDEFINED
, SVNRevision
.HEAD
);
384 return (info
== null) ?
null : info
.getRepositoryRootURL();
385 } catch (SVNException e
) {
390 public static boolean isWorkingCopyRoot(final File file
) {
392 return SVNWCUtil
.isWorkingCopyRoot(file
);
393 } catch (SVNException e
) {
399 public static File
getWorkingCopyRoot(final File inFile
) {
401 while ((file
!= null) && (file
.isFile() || (! file
.exists()))) {
402 file
= file
.getParentFile();
410 return SVNWCUtil
.getWorkingCopyRoot(file
, true);
411 } catch (SVNException e
) {
417 public static SVNURL
getWorkingCopyUrl(final SvnVcs vcs
, final File file
) {
419 if(SVNWCUtil
.isWorkingCopyRoot(file
)) {
420 final SVNWCClient client
= vcs
.createWCClient();
421 final SVNInfo info
= client
.doInfo(file
, SVNRevision
.WORKING
);
422 return info
.getURL();
424 } catch (SVNException e
) {
430 public static File
fileFromUrl(final File baseDir
, final String baseUrl
, final String fullUrl
) throws SVNException
{
431 assert fullUrl
.startsWith(baseUrl
);
433 final String part
= fullUrl
.substring(baseUrl
.length()).replace('/', File
.separatorChar
).replace('\\', File
.separatorChar
);
434 return new File(baseDir
, part
);
437 public static VirtualFile
getVirtualFile(final String filePath
) {
438 @NonNls final String path
= VfsUtil
.pathToUrl(filePath
.replace(File
.separatorChar
, '/'));
439 return ApplicationManager
.getApplication().runReadAction(new Computable
<VirtualFile
>() {
441 public VirtualFile
compute() {
442 return VirtualFileManager
.getInstance().findFileByUrl(path
);
448 public static SVNURL
getBranchForUrl(final SvnVcs vcs
, final VirtualFile vcsRoot
, final String urlPath
) {
449 final SvnBranchConfigurationNew configuration
;
451 final SVNURL url
= SVNURL
.parseURIEncoded(urlPath
);
452 configuration
= SvnBranchConfigurationManager
.getInstance(vcs
.getProject()).get(vcsRoot
);
453 return (configuration
== null) ?
null : configuration
.getWorkingBranch(url
);
455 catch (SVNException e
) {
457 } catch (VcsException e1
) {
463 public static String
getPathForProgress(final SVNEvent event
) {
464 if (event
.getFile() != null) {
465 return event
.getFile().getName();
467 if (event
.getURL() != null) {
468 return event
.getURL().toString();
474 public static VirtualFile
correctRoot(final Project project
, final String path
) {
475 if (path
.length() == 0) {
477 return project
.getBaseDir();
479 return LocalFileSystem
.getInstance().findFileByPath(path
);
483 public static VirtualFile
correctRoot(final Project project
, final VirtualFile file
) {
484 if (file
.getPath().length() == 0) {
486 return project
.getBaseDir();
491 public static boolean isOneDotFiveAvailable(final Project project
, final RepositoryLocation location
) {
492 final SvnVcs vcs
= SvnVcs
.getInstance(project
);
493 final List
<WCInfo
> infos
= vcs
.getAllWcInfos();
495 for (WCInfo info
: infos
) {
496 if (! info
.getFormat().supportsMergeInfo()) {
500 final String url
= info
.getUrl().toString();
501 if ((location
!= null) && (! location
.toPresentableString().startsWith(url
)) &&
502 (! url
.startsWith(location
.toPresentableString()))) {
505 if (! checkRepositoryVersion15(vcs
, url
)) {
513 public static boolean checkRepositoryVersion15(final SvnVcs vcs
, final String url
) {
514 SVNRepository repository
= null;
516 repository
= vcs
.createRepository(url
);
517 return repository
.hasCapability(SVNCapability
.MERGE_INFO
);
519 catch (SVNException e
) {
523 if (repository
!= null) {
524 repository
.closeSession();
529 public static SVNStatus
getStatus(final SvnVcs vcs
, final File file
) {
530 final SVNStatusClient statusClient
= vcs
.createStatusClient();
532 return statusClient
.doStatus(file
, false);
534 catch (SVNException e
) {
539 public static boolean seemsLikeVersionedDir(final VirtualFile file
) {
540 final String adminName
= SVNFileUtil
.getAdminDirectoryName();
541 final VirtualFile
[] children
= file
.getChildren();
542 for (VirtualFile child
: children
) {
543 if (adminName
.equals(child
.getName()) && child
.isDirectory()) {
550 public static boolean isAdminDirectory(final VirtualFile file
) {
551 return isAdminDirectory(file
.getParent(), file
.getName());
554 public static boolean isAdminDirectory(File parent
, final String name
) {
555 if (name
.equals(SVN_ADMIN_DIR_NAME
)) {
558 if (parent
!= null) {
559 if (parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
562 parent
= parent
.getParentFile();
563 if (parent
!= null && parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
570 public static boolean isAdminDirectory(VirtualFile parent
, String name
) {
571 // never allow to delete admin directories by themselves (this can happen during LVCS undo,
572 // which deletes created directories from bottom to top)
573 if (name
.equals(SVN_ADMIN_DIR_NAME
)) {
576 if (parent
!= null) {
577 if (parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
580 parent
= parent
.getParent();
581 if (parent
!= null && parent
.getName().equals(SVN_ADMIN_DIR_NAME
)) {
589 public static SVNURL
getUrl(final File file
) {
590 SVNWCAccess wcAccess
= SVNWCAccess
.newInstance(null);
592 wcAccess
.probeOpen(file
, false, 0);
593 SVNEntry entry
= wcAccess
.getVersionedEntry(file
, false);
594 return entry
.getSVNURL();
595 } catch (SVNException e
) {
601 catch (SVNException e
) {