001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.wc.admin;
013:
014: import java.io.File;
015: import java.util.ArrayList;
016: import java.util.Collection;
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.List;
020: import java.util.Map;
021: import java.util.StringTokenizer;
022:
023: import org.tmatesoft.svn.core.SVNCancelException;
024: import org.tmatesoft.svn.core.SVNErrorCode;
025: import org.tmatesoft.svn.core.SVNErrorMessage;
026: import org.tmatesoft.svn.core.SVNException;
027: import org.tmatesoft.svn.core.SVNNodeKind;
028: import org.tmatesoft.svn.core.SVNURL;
029: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
030: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
031: import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
032: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
033: import org.tmatesoft.svn.core.internal.wc.SVNExternalInfo;
034: import org.tmatesoft.svn.core.internal.wc.SVNFileType;
035: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
036: import org.tmatesoft.svn.core.wc.ISVNEventHandler;
037: import org.tmatesoft.svn.core.wc.ISVNOptions;
038: import org.tmatesoft.svn.core.wc.SVNEvent;
039:
040: /**
041: * @version 1.1.1
042: * @author TMate Software Ltd.
043: */
044: public class SVNWCAccess implements ISVNEventHandler {
045:
046: public static final int INFINITE_DEPTH = -1;
047:
048: private ISVNEventHandler myEventHandler;
049: private ISVNOptions myOptions;
050: private Map myAdminAreas;
051: private Map myCleanupHandlers;
052:
053: private File myAnchor;
054:
055: public static SVNWCAccess newInstance(ISVNEventHandler eventHandler) {
056: return new SVNWCAccess(eventHandler);
057: }
058:
059: private SVNWCAccess(ISVNEventHandler handler) {
060: myEventHandler = handler;
061: }
062:
063: public void setEventHandler(ISVNEventHandler handler) {
064: myEventHandler = handler;
065: }
066:
067: public ISVNEventHandler getEventHandler() {
068: return myEventHandler;
069: }
070:
071: public void checkCancelled() throws SVNCancelException {
072: if (myEventHandler != null) {
073: myEventHandler.checkCancelled();
074: }
075: }
076:
077: public void handleEvent(SVNEvent event) throws SVNException {
078: handleEvent(event, ISVNEventHandler.UNKNOWN);
079: }
080:
081: public void registerCleanupHandler(SVNAdminArea area,
082: ISVNCleanupHandler handler) {
083: if (area == null || handler == null) {
084: return;
085: }
086: if (myCleanupHandlers == null) {
087: myCleanupHandlers = new HashMap();
088: }
089: myCleanupHandlers.put(area, handler);
090: }
091:
092: public void handleEvent(SVNEvent event, double progress)
093: throws SVNException {
094: if (myEventHandler != null) {
095: try {
096: myEventHandler.handleEvent(event, progress);
097: } catch (SVNException e) {
098: throw e;
099: } catch (Throwable th) {
100: SVNErrorMessage err = SVNErrorMessage.create(
101: SVNErrorCode.UNKNOWN,
102: "Error while dispatching event: {0}", th
103: .getMessage());
104: SVNErrorManager.error(err, th);
105: }
106: }
107: }
108:
109: public void setOptions(ISVNOptions options) {
110: myOptions = options;
111: }
112:
113: public ISVNOptions getOptions() {
114: if (myOptions == null) {
115: myOptions = new DefaultSVNOptions();
116: }
117: return myOptions;
118: }
119:
120: public void setAnchor(File anchor) {
121: myAnchor = anchor;
122: }
123:
124: public File getAnchor() {
125: return myAnchor;
126: }
127:
128: public SVNAdminAreaInfo openAnchor(File path, boolean writeLock,
129: int depth) throws SVNException {
130: File parent = path.getParentFile();
131: if (parent == null) {
132: SVNAdminArea anchor = open(path, writeLock, depth);
133: return new SVNAdminAreaInfo(this , anchor, anchor, "");
134: }
135:
136: String name = path.getName();
137: SVNAdminArea parentArea = null;
138: SVNAdminArea targetArea = null;
139: SVNException parentError = null;
140:
141: try {
142: parentArea = open(parent, writeLock, false, 0);
143: } catch (SVNException svne) {
144: if (writeLock
145: && svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LOCKED) {
146: try {
147: parentArea = open(parent, false, false, 0);
148: } catch (SVNException svne2) {
149: throw svne;
150: }
151: parentError = svne;
152: } else if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) {
153: throw svne;
154: }
155: }
156:
157: try {
158: targetArea = open(path, writeLock, false, depth);
159: } catch (SVNException svne) {
160: if (parentArea == null
161: || svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) {
162: try {
163: close();
164: } catch (SVNException svne2) {
165: //
166: }
167: throw svne;
168: }
169: }
170:
171: if (parentArea != null && targetArea != null) {
172: SVNEntry parentEntry = null;
173: SVNEntry targetEntry = null;
174: SVNEntry targetInParent = null;
175: try {
176: targetInParent = parentArea.getEntry(name, false);
177: targetEntry = targetArea.getEntry(targetArea
178: .getThisDirName(), false);
179: parentEntry = parentArea.getEntry(parentArea
180: .getThisDirName(), false);
181: } catch (SVNException svne) {
182: try {
183: close();
184: } catch (SVNException svne2) {
185: //
186: }
187: throw svne;
188: }
189:
190: SVNURL parentURL = parentEntry != null ? parentEntry
191: .getSVNURL() : null;
192: SVNURL targetURL = targetEntry != null ? targetEntry
193: .getSVNURL() : null;
194: String encodedName = SVNEncodingUtil.uriEncode(name);
195: if (targetInParent == null
196: || (parentURL != null && targetURL != null && (!parentURL
197: .equals(targetURL.removePathTail()) || !encodedName
198: .equals(SVNPathUtil.tail(targetURL
199: .getURIEncodedPath()))))) {
200: if (myAdminAreas != null) {
201: myAdminAreas.remove(parent);
202: }
203: try {
204: doClose(parentArea, false);
205: } catch (SVNException svne) {
206: try {
207: close();
208: } catch (SVNException svne2) {
209: //
210: }
211: throw svne;
212: }
213: parentArea = null;
214: }
215: }
216:
217: if (parentArea != null) {
218: if (parentError != null && targetArea != null) {
219: if (parentError.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LOCKED) {
220: // try to work without 'anchor'
221: try {
222: doClose(parentArea, false);
223: } catch (SVNException svne) {
224: try {
225: close();
226: } catch (SVNException svne2) {
227: //
228: }
229: throw svne;
230: }
231: parentArea = null;
232: } else {
233: try {
234: close();
235: } catch (SVNException svne) {
236: //
237: }
238: throw parentError;
239: }
240: }
241: }
242:
243: if (targetArea == null) {
244: SVNEntry targetEntry = null;
245: try {
246: targetEntry = parentArea.getEntry(name, false);
247: } catch (SVNException svne) {
248: try {
249: close();
250: } catch (SVNException svne2) {
251: //
252: }
253: throw svne;
254: }
255: if (targetEntry != null && targetEntry.isDirectory()) {
256: if (myAdminAreas != null) {
257: myAdminAreas.put(path, null);
258: }
259: }
260: }
261: SVNAdminArea anchor = parentArea != null ? parentArea
262: : targetArea;
263: SVNAdminArea target = targetArea != null ? targetArea
264: : parentArea;
265: return new SVNAdminAreaInfo(this , anchor, target,
266: parentArea == null ? "" : name);
267: }
268:
269: public SVNAdminArea open(File path, boolean writeLock, int depth)
270: throws SVNException {
271: return open(path, writeLock, false, depth);
272: }
273:
274: public SVNAdminArea open(File path, boolean writeLock,
275: boolean stealLock, int depth) throws SVNException {
276: Map tmp = new HashMap();
277: SVNAdminArea area;
278: try {
279: area = doOpen(path, writeLock, stealLock, depth, tmp);
280: } finally {
281: for (Iterator paths = tmp.keySet().iterator(); paths
282: .hasNext();) {
283: Object childPath = paths.next();
284: SVNAdminArea childArea = (SVNAdminArea) tmp
285: .get(childPath);
286: myAdminAreas.put(childPath, childArea);
287: }
288: }
289: return area;
290: }
291:
292: public SVNAdminArea probeOpen(File path, boolean writeLock,
293: int depth) throws SVNException {
294: File dir = probe(path);
295: if (!path.equals(dir)) {
296: depth = 0;
297: }
298: SVNAdminArea adminArea = null;
299: try {
300: adminArea = open(dir, writeLock, false, depth);
301: } catch (SVNException svne) {
302: SVNFileType childKind = SVNFileType.getType(path);
303: SVNErrorCode errCode = svne.getErrorMessage()
304: .getErrorCode();
305: if (!path.equals(dir) && childKind == SVNFileType.DIRECTORY
306: && errCode == SVNErrorCode.WC_NOT_DIRECTORY) {
307: SVNErrorMessage err = SVNErrorMessage.create(
308: SVNErrorCode.WC_NOT_DIRECTORY,
309: "''{0}'' is not a working copy", path);
310: SVNErrorManager.error(err);
311: } else {
312: throw svne;
313: }
314: }
315: return adminArea;
316: }
317:
318: public SVNAdminArea probeTry(File path, boolean writeLock, int depth)
319: throws SVNException {
320: SVNAdminArea adminArea = null;
321: try {
322: adminArea = probeRetrieve(path);
323: } catch (SVNException svne) {
324: if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
325: try {
326: adminArea = probeOpen(path, writeLock, depth);
327: } catch (SVNException svne2) {
328: if (svne2.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) {
329: throw svne2;
330: }
331: }
332: } else {
333: throw svne;
334: }
335: }
336: return adminArea;
337: }
338:
339: public void close() throws SVNException {
340: if (myAdminAreas != null) {
341: doClose(myAdminAreas, false);
342: myAdminAreas.clear();
343: }
344: myCleanupHandlers = null;
345: }
346:
347: public void closeAdminArea(File path) throws SVNException {
348: if (myAdminAreas != null) {
349: SVNAdminArea area = (SVNAdminArea) myAdminAreas.get(path);
350: if (area != null) {
351: doClose(area, false);
352: myAdminAreas.remove(path);
353: }
354: }
355: }
356:
357: private SVNAdminArea doOpen(File path, boolean writeLock,
358: boolean stealLock, int depth, Map tmp) throws SVNException {
359: // no support for 'under consturction here' - it will go to adminAreaFactory.
360: tmp = tmp == null ? new HashMap() : tmp;
361: if (myAdminAreas != null) {
362: SVNAdminArea existing = (SVNAdminArea) myAdminAreas
363: .get(path);
364: if (myAdminAreas.containsKey(path) && existing != null) {
365: SVNErrorMessage error = SVNErrorMessage.create(
366: SVNErrorCode.WC_LOCKED,
367: "Working copy ''{0}'' locked", path);
368: SVNErrorManager.error(error);
369: }
370: } else {
371: myAdminAreas = new HashMap();
372: }
373:
374: SVNAdminArea area = SVNAdminAreaFactory.open(path);
375: area.setWCAccess(this );
376:
377: if (writeLock) {
378: area.lock(stealLock);
379: area = SVNAdminAreaFactory.upgrade(area);
380: }
381: tmp.put(path, area);
382:
383: if (depth != 0) {
384: if (depth > 0) {
385: depth--;
386: }
387: for (Iterator entries = area.entries(false); entries
388: .hasNext();) {
389: try {
390: checkCancelled();
391: } catch (SVNCancelException e) {
392: doClose(tmp, false);
393: throw e;
394: }
395:
396: SVNEntry entry = (SVNEntry) entries.next();
397: if (entry.getKind() != SVNNodeKind.DIR
398: || area.getThisDirName()
399: .equals(entry.getName())) {
400: continue;
401: }
402: File childPath = new File(path, entry.getName());
403: try {
404: // this method will put created area into our map.
405: doOpen(childPath, writeLock, stealLock, depth, tmp);
406: } catch (SVNException e) {
407: if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) {
408: doClose(tmp, false);
409: throw e;
410: }
411: // only for missing!
412: tmp.put(childPath, null);
413: continue;
414: }
415: }
416: }
417: return area;
418: }
419:
420: private void doClose(Map adminAreas, boolean preserveLocks)
421: throws SVNException {
422: for (Iterator paths = adminAreas.keySet().iterator(); paths
423: .hasNext();) {
424: File path = (File) paths.next();
425: SVNAdminArea adminArea = (SVNAdminArea) adminAreas
426: .get(path);
427: if (adminArea == null) {
428: paths.remove();
429: continue;
430: }
431: doClose(adminArea, preserveLocks);
432: paths.remove();
433: }
434: }
435:
436: private void doClose(SVNAdminArea adminArea, boolean preserveLocks)
437: throws SVNException {
438: if (adminArea == null) {
439: return;
440: }
441: if (myCleanupHandlers != null) {
442: ISVNCleanupHandler handler = (ISVNCleanupHandler) myCleanupHandlers
443: .remove(adminArea);
444: if (handler != null) {
445: handler.cleanup(adminArea);
446: }
447: }
448: if (!preserveLocks && adminArea.isLocked()) {
449: adminArea.unlock();
450: }
451: }
452:
453: public SVNAdminArea probeRetrieve(File path) throws SVNException {
454: File dir = probe(path);
455: return retrieve(dir);
456: }
457:
458: public boolean isMissing(File path) {
459: if (myAdminAreas != null) {
460: return myAdminAreas.containsKey(path)
461: && myAdminAreas.get(path) == null;
462: }
463: return false;
464: }
465:
466: public boolean isLocked(File path) throws SVNException {
467: File lockFile = new File(path, SVNFileUtil
468: .getAdminDirectoryName());
469: lockFile = new File(lockFile, "lock");
470: if (SVNFileType.getType(lockFile) == SVNFileType.FILE) {
471: return true;
472: } else if (SVNFileType.getType(lockFile) == SVNFileType.NONE) {
473: return false;
474: }
475: SVNErrorMessage err = SVNErrorMessage.create(
476: SVNErrorCode.WC_LOCKED,
477: "Lock file ''{0}'' is not a regular file", lockFile);
478: SVNErrorManager.error(err);
479: return false;
480: }
481:
482: public boolean isWCRoot(File path) throws SVNException {
483: SVNEntry entry = getEntry(path, false);
484: if (path.getParentFile() == null && entry != null) {
485: return true;
486: }
487: SVNAdminArea parentArea = getAdminArea(path.getParentFile());
488: if (parentArea == null) {
489: try {
490: parentArea = probeOpen(path.getParentFile(), false, 0);
491: } catch (SVNException svne) {
492: return true;
493: }
494: }
495:
496: SVNEntry parentEntry = getEntry(path.getParentFile(), false);
497: if (parentEntry == null) {
498: return true;
499: }
500:
501: if (parentEntry.getURL() == null) {
502: SVNErrorMessage err = SVNErrorMessage.create(
503: SVNErrorCode.ENTRY_MISSING_URL,
504: "''{0}'' has no ancestry information", path
505: .getParentFile());
506: SVNErrorManager.error(err);
507: }
508:
509: // what about switched paths?
510: /*
511: if (entry != null && entry.getURL() != null) {
512: if (!entry.getURL().equals(SVNPathUtil.append(parentEntry.getURL(), SVNEncodingUtil.uriEncode(path.getName())))) {
513: return true;
514: }
515: }*/
516: entry = parentArea.getEntry(path.getName(), false);
517: if (entry == null) {
518: return true;
519: }
520: return false;
521: }
522:
523: public SVNEntry getEntry(File path, boolean showHidden)
524: throws SVNException {
525: SVNAdminArea adminArea = getAdminArea(path);
526: String entryName = null;
527: if (adminArea == null) {
528: adminArea = getAdminArea(path.getParentFile());
529: entryName = path.getName();
530: } else {
531: entryName = adminArea.getThisDirName();
532: }
533:
534: if (adminArea != null) {
535: return adminArea.getEntry(entryName, showHidden);
536: }
537: return null;
538: }
539:
540: public void setRepositoryRoot(File path, SVNURL reposRoot)
541: throws SVNException {
542: SVNEntry entry = getEntry(path, false);
543: if (entry == null) {
544: return;
545: }
546: SVNAdminArea adminArea = null;
547: String name = null;
548: if (entry.isFile()) {
549: adminArea = getAdminArea(path.getParentFile());
550: name = path.getName();
551: } else {
552: adminArea = getAdminArea(path);
553: name = adminArea != null ? adminArea.getThisDirName()
554: : null;
555: }
556:
557: if (adminArea == null) {
558: return;
559: }
560: if (adminArea.tweakEntry(name, null, reposRoot.toString(), -1,
561: false)) {
562: adminArea.saveEntries(false);
563: }
564: }
565:
566: public SVNAdminArea[] getAdminAreas() {
567: if (myAdminAreas != null) {
568: return (SVNAdminArea[]) myAdminAreas.values().toArray(
569: new SVNAdminArea[myAdminAreas.size()]);
570: }
571: return new SVNAdminArea[0];
572: }
573:
574: public SVNAdminArea retrieve(File path) throws SVNException {
575: SVNAdminArea adminArea = getAdminArea(path);
576: if (adminArea == null) {
577: SVNEntry subEntry = null;
578: try {
579: SVNAdminArea dirAdminArea = getAdminArea(path
580: .getParentFile());
581: if (dirAdminArea != null) {
582: subEntry = dirAdminArea.getEntry(path.getName(),
583: true);
584: }
585: } catch (SVNException svne) {
586: subEntry = null;
587: }
588: SVNFileType type = SVNFileType.getType(path);
589: if (subEntry != null) {
590: if (subEntry.getKind() == SVNNodeKind.DIR
591: && type == SVNFileType.FILE) {
592: SVNErrorMessage err = SVNErrorMessage
593: .create(
594: SVNErrorCode.WC_NOT_LOCKED,
595: "Expected ''{0}'' to be a directory but found a file",
596: path);
597: SVNErrorManager.error(err);
598: } else if (subEntry.getKind() == SVNNodeKind.FILE
599: && type == SVNFileType.DIRECTORY) {
600: SVNErrorMessage err = SVNErrorMessage
601: .create(
602: SVNErrorCode.WC_NOT_LOCKED,
603: "Expected ''{0}'' to be a file but found a directory",
604: path);
605: SVNErrorManager.error(err);
606: }
607: }
608: File adminDir = new File(path, SVNFileUtil
609: .getAdminDirectoryName());
610: SVNFileType wcType = SVNFileType.getType(adminDir);
611:
612: if (type == SVNFileType.NONE) {
613: SVNErrorMessage err = SVNErrorMessage.create(
614: SVNErrorCode.WC_NOT_LOCKED,
615: "Directory ''{0}'' is missing", path);
616: SVNErrorManager.error(err);
617: } else if (type == SVNFileType.DIRECTORY
618: && wcType == SVNFileType.NONE) {
619: SVNErrorMessage err = SVNErrorMessage
620: .create(
621: SVNErrorCode.WC_NOT_LOCKED,
622: "Directory ''{0}'' containing working copy admin area is missing",
623: adminDir);
624: SVNErrorManager.error(err);
625: } else if (type == SVNFileType.DIRECTORY
626: && wcType == SVNFileType.DIRECTORY) {
627: SVNErrorMessage err = SVNErrorMessage.create(
628: SVNErrorCode.WC_NOT_LOCKED,
629: "Unable to lock ''{0}''", path);
630: SVNErrorManager.error(err);
631: }
632: SVNErrorMessage err = SVNErrorMessage.create(
633: SVNErrorCode.WC_NOT_LOCKED,
634: "Working copy ''{0}'' is not locked", path);
635: SVNErrorManager.error(err);
636: }
637: return adminArea;
638: }
639:
640: public static SVNExternalInfo[] parseExternals(String rootPath,
641: String externals) {
642: Collection result = new ArrayList();
643: if (externals == null) {
644: return (SVNExternalInfo[]) result
645: .toArray(new SVNExternalInfo[result.size()]);
646: }
647:
648: for (StringTokenizer lines = new StringTokenizer(externals,
649: "\n\r"); lines.hasMoreTokens();) {
650: String line = lines.nextToken().trim();
651: if (line.length() == 0 || line.startsWith("#")) {
652: continue;
653: }
654: String url = null;
655: String path;
656: long rev = -1;
657: List parts = new ArrayList(4);
658: for (StringTokenizer tokens = new StringTokenizer(line,
659: " \t"); tokens.hasMoreTokens();) {
660: String token = tokens.nextToken().trim();
661: parts.add(token);
662: }
663: if (parts.size() < 2) {
664: continue;
665: }
666: path = SVNPathUtil.append(rootPath, (String) parts.get(0));
667: if (path.endsWith("/")) {
668: path = path.substring(0, path.length() - 1);
669: }
670: if (parts.size() == 2) {
671: url = (String) parts.get(1);
672: } else if (parts.size() == 3
673: && parts.get(1).toString().startsWith("-r")) {
674: String revStr = parts.get(1).toString();
675: revStr = revStr.substring("-r".length());
676: if (!"HEAD".equals(revStr)) {
677: try {
678: rev = Long.parseLong(revStr);
679: } catch (NumberFormatException nfe) {
680: continue;
681: }
682: }
683: url = (String) parts.get(2);
684: } else if (parts.size() == 4 && "-r".equals(parts.get(1))) {
685: String revStr = parts.get(2).toString();
686: if (!"HEAD".equals(revStr)) {
687: try {
688: rev = Long.parseLong(revStr);
689: } catch (NumberFormatException nfe) {
690: continue;
691: }
692: }
693: url = (String) parts.get(3);
694: }
695: if (path != null && url != null) {
696: if ("".equals(rootPath)
697: && ((String) parts.get(0)).startsWith("/")) {
698: path = "/" + path;
699: }
700: try {
701: url = SVNURL.parseURIEncoded(url).toString();
702: } catch (SVNException e) {
703: continue;
704: }
705:
706: try {
707: SVNExternalInfo info = new SVNExternalInfo("",
708: null, path, SVNURL.parseURIEncoded(url),
709: rev);
710: result.add(info);
711: } catch (SVNException e) {
712: }
713: }
714: }
715: return (SVNExternalInfo[]) result
716: .toArray(new SVNExternalInfo[result.size()]);
717: }
718:
719: //analogous to retrieve_internal
720: public SVNAdminArea getAdminArea(File path) {
721: //internal retrieve
722: SVNAdminArea adminArea = null;
723: if (myAdminAreas != null) {
724: adminArea = (SVNAdminArea) myAdminAreas.get(path);
725: }
726: return adminArea;
727: }
728:
729: private File probe(File path) throws SVNException {
730: int wcFormat = -1;
731: SVNFileType type = SVNFileType.getType(path);
732: if (type == SVNFileType.DIRECTORY) {
733: wcFormat = SVNAdminAreaFactory.checkWC(path, true);
734: } else {
735: wcFormat = 0;
736: }
737:
738: //non wc
739: if (type != SVNFileType.DIRECTORY || wcFormat == 0) {
740: if ("..".equals(path.getName())
741: || ".".equals(path.getName())) {
742: SVNErrorMessage err = SVNErrorMessage
743: .create(
744: SVNErrorCode.WC_BAD_PATH,
745: "Path ''{0}'' ends in ''{1}'', which is unsupported for this operation",
746: new Object[] { path, path.getName() });
747: SVNErrorManager.error(err);
748: }
749: path = path.getParentFile();
750: }
751: return path;
752: }
753: }
|