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.wc;
013:
014: import java.io.File;
015: import java.util.ArrayList;
016: import java.util.Collections;
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.LinkedList;
020: import java.util.List;
021: import java.util.Map;
022:
023: import org.tmatesoft.svn.core.ISVNLogEntryHandler;
024: import org.tmatesoft.svn.core.SVNCancelException;
025: import org.tmatesoft.svn.core.SVNErrorCode;
026: import org.tmatesoft.svn.core.SVNErrorMessage;
027: import org.tmatesoft.svn.core.SVNException;
028: import org.tmatesoft.svn.core.SVNLogEntry;
029: import org.tmatesoft.svn.core.SVNLogEntryPath;
030: import org.tmatesoft.svn.core.SVNNodeKind;
031: import org.tmatesoft.svn.core.SVNURL;
032: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
033: import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
034: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
035: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
036: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
037: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
038: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
039: import org.tmatesoft.svn.core.io.SVNLocationEntry;
040: import org.tmatesoft.svn.core.io.SVNRepository;
041: import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
042: import org.tmatesoft.svn.util.ISVNDebugLog;
043: import org.tmatesoft.svn.util.SVNDebugLog;
044:
045: /**
046: * The <b>SVNBasicClient</b> is the base class of all
047: * <b>SVN</b>*<b>Client</b> classes that provides a common interface
048: * and realization.
049: *
050: * <p>
051: * All of <b>SVN</b>*<b>Client</b> classes use inherited methods of
052: * <b>SVNBasicClient</b> to access Working Copies metadata, to create
053: * a driver object to access a repository if it's necessary, etc. In addition
054: * <b>SVNBasicClient</b> provides some interface methods - such as those
055: * that allow you to set your {@link ISVNEventHandler event handler},
056: * obtain run-time configuration options, and others.
057: *
058: * @version 1.1.1
059: * @author TMate Software Ltd.
060: */
061: public class SVNBasicClient implements ISVNEventHandler {
062:
063: private ISVNRepositoryPool myRepositoryPool;
064: private ISVNOptions myOptions;
065: private ISVNEventHandler myEventDispatcher;
066: private List myPathPrefixesStack;
067: private boolean myIsIgnoreExternals;
068: private boolean myIsLeaveConflictsUnresolved;
069: private ISVNDebugLog myDebugLog;
070:
071: protected SVNBasicClient(
072: final ISVNAuthenticationManager authManager,
073: ISVNOptions options) {
074: this (new DefaultSVNRepositoryPool(
075: authManager == null ? SVNWCUtil
076: .createDefaultAuthenticationManager()
077: : authManager, options, 0, false), options);
078: }
079:
080: protected SVNBasicClient(ISVNRepositoryPool repositoryPool,
081: ISVNOptions options) {
082: myRepositoryPool = repositoryPool;
083: setOptions(options);
084: myPathPrefixesStack = new LinkedList();
085: }
086:
087: /**
088: * Gets a run-time configuration area driver used by this object.
089: *
090: * @return the run-time options driver being in use
091: */
092: public ISVNOptions getOptions() {
093: return myOptions;
094: }
095:
096: public void setOptions(ISVNOptions options) {
097: myOptions = options;
098: if (myOptions == null) {
099: myOptions = SVNWCUtil.createDefaultOptions(true);
100: }
101: }
102:
103: /**
104: * Sets externals definitions to be ignored or not during
105: * operations.
106: *
107: * <p>
108: * For example, if external definitions are set to be ignored
109: * then a checkout operation won't fetch them into a Working Copy.
110: *
111: * @param ignore <span class="javakeyword">true</span> to ignore
112: * externals definitions, <span class="javakeyword">false</span> -
113: * not to
114: * @see #isIgnoreExternals()
115: */
116: public void setIgnoreExternals(boolean ignore) {
117: myIsIgnoreExternals = ignore;
118: }
119:
120: /**
121: * Determines if externals definitions are ignored.
122: *
123: * @return <span class="javakeyword">true</span> if ignored,
124: * otherwise <span class="javakeyword">false</span>
125: * @see #setIgnoreExternals(boolean)
126: */
127: public boolean isIgnoreExternals() {
128: return myIsIgnoreExternals;
129: }
130:
131: /**
132: * Sets (or unsets) all conflicted working files to be untouched
133: * by update and merge operations.
134: *
135: * <p>
136: * By default when a file receives changes from the repository
137: * that are in conflict with local edits, an update operation places
138: * two sections for each conflicting snatch into the working file
139: * one of which is a user's local edit and the second is the one just
140: * received from the repository. Like this:
141: * <pre class="javacode">
142: * <<<<<<< .mine
143: * user's text
144: * =======
145: * received text
146: * >>>>>>> .r2</pre><br />
147: * Also the operation creates three temporary files that appear in the
148: * same directory as the working file. Now if you call this method with
149: * <code>leave</code> set to <span class="javakeyword">true</span>,
150: * an update will still create temporary files but won't place those two
151: * sections into your working file. And this behaviour also concerns
152: * merge operations: any merging to a conflicted file will be prevented.
153: * In addition if there is any registered event
154: * handler for an <b>SVNDiffClient</b> or <b>SVNUpdateClient</b>
155: * instance then the handler will be dispatched an event with
156: * the status type set to {@link SVNStatusType#CONFLICTED_UNRESOLVED}.
157: *
158: * <p>
159: * The default value is <span class="javakeyword">false</span> until
160: * a caller explicitly changes it calling this method.
161: *
162: * @param leave <span class="javakeyword">true</span> to prevent
163: * conflicted files from merging (all merging operations
164: * will be skipped), otherwise <span class="javakeyword">false</span>
165: * @see #isLeaveConflictsUnresolved()
166: * @see SVNUpdateClient
167: * @see SVNDiffClient
168: * @see ISVNEventHandler
169: */
170: public void setLeaveConflictsUnresolved(boolean leave) {
171: myIsLeaveConflictsUnresolved = leave;
172: }
173:
174: /**
175: * Determines if conflicted files should be left unresolved
176: * preventing from merging their contents during update and merge
177: * operations.
178: *
179: * @return <span class="javakeyword">true</span> if conflicted files
180: * are set to be prevented from merging, <span class="javakeyword">false</span>
181: * if there's no such restriction
182: * @see #setLeaveConflictsUnresolved(boolean)
183: */
184: public boolean isLeaveConflictsUnresolved() {
185: return myIsLeaveConflictsUnresolved;
186: }
187:
188: /**
189: * Sets an event handler for this object. This event handler
190: * will be dispatched {@link SVNEvent} objects to provide
191: * detailed information about actions and progress state
192: * of version control operations performed by <b>do</b>*<b>()</b>
193: * methods of <b>SVN</b>*<b>Client</b> classes.
194: *
195: * @param dispatcher an event handler
196: */
197: public void setEventHandler(ISVNEventHandler dispatcher) {
198: myEventDispatcher = dispatcher;
199: }
200:
201: /**
202: * Sets a logger to write debug log information to.
203: *
204: * @param log a debug logger
205: */
206: public void setDebugLog(ISVNDebugLog log) {
207: myDebugLog = log;
208: }
209:
210: /**
211: * Returns the debug logger currently in use.
212: *
213: * <p>
214: * If no debug logger has been specified by the time this call occurs,
215: * a default one (returned by <code>org.tmatesoft.svn.util.SVNDebugLog.getDefaultLog()</code>)
216: * will be created and used.
217: *
218: * @return a debug logger
219: */
220: public ISVNDebugLog getDebugLog() {
221: if (myDebugLog == null) {
222: return SVNDebugLog.getDefaultLog();
223: }
224: return myDebugLog;
225: }
226:
227: protected void sleepForTimeStamp() {
228: if (myPathPrefixesStack == null
229: || myPathPrefixesStack.isEmpty()) {
230: SVNFileUtil.sleepForTimestamp();
231: }
232: }
233:
234: protected SVNRepository createRepository(SVNURL url,
235: boolean mayReuse) throws SVNException {
236: SVNRepository repository = null;
237: if (myRepositoryPool == null) {
238: repository = SVNRepositoryFactory.create(url, null);
239: } else {
240: repository = myRepositoryPool.createRepository(url,
241: mayReuse);
242: }
243: repository.setDebugLog(getDebugLog());
244: repository.setCanceller(getEventDispatcher());
245: return repository;
246: }
247:
248: protected ISVNRepositoryPool getRepositoryPool() {
249: return myRepositoryPool;
250: }
251:
252: protected void dispatchEvent(SVNEvent event) throws SVNException {
253: dispatchEvent(event, ISVNEventHandler.UNKNOWN);
254:
255: }
256:
257: protected void dispatchEvent(SVNEvent event, double progress)
258: throws SVNException {
259: if (myEventDispatcher != null) {
260: String path = "";
261: if (!myPathPrefixesStack.isEmpty()) {
262: for (Iterator paths = myPathPrefixesStack.iterator(); paths
263: .hasNext();) {
264: String segment = (String) paths.next();
265: path = SVNPathUtil.append(path, segment);
266: }
267: }
268: if (path != null && !"".equals(path)) {
269: path = SVNPathUtil.append(path, event.getPath());
270: event.setPath(path);
271: }
272: try {
273: myEventDispatcher.handleEvent(event, progress);
274: } catch (SVNException e) {
275: throw e;
276: } catch (Throwable th) {
277: SVNErrorMessage err = SVNErrorMessage.create(
278: SVNErrorCode.UNKNOWN,
279: "Error while dispatching event: {0}", th
280: .getMessage());
281: SVNErrorManager.error(err, th);
282: }
283: }
284: }
285:
286: /**
287: * Removes or adds a path prefix. This method is not intended for
288: * users (from an API point of view).
289: *
290: * @param prefix a path prefix
291: */
292: public void setEventPathPrefix(String prefix) {
293: if (prefix == null && !myPathPrefixesStack.isEmpty()) {
294: myPathPrefixesStack.remove(myPathPrefixesStack.size() - 1);
295: } else if (prefix != null) {
296: myPathPrefixesStack.add(prefix);
297: }
298: }
299:
300: protected ISVNEventHandler getEventDispatcher() {
301: return myEventDispatcher;
302: }
303:
304: protected SVNWCAccess createWCAccess() {
305: return createWCAccess((String) null);
306: }
307:
308: protected SVNWCAccess createWCAccess(final String pathPrefix) {
309: ISVNEventHandler eventHandler = null;
310: if (pathPrefix != null) {
311: eventHandler = new ISVNEventHandler() {
312: public void handleEvent(SVNEvent event, double progress)
313: throws SVNException {
314: String fullPath = SVNPathUtil.append(pathPrefix,
315: event.getPath());
316: event.setPath(fullPath);
317: dispatchEvent(event, progress);
318: }
319:
320: public void checkCancelled() throws SVNCancelException {
321: SVNBasicClient.this .checkCancelled();
322: }
323: };
324: } else {
325: eventHandler = this ;
326: }
327: SVNWCAccess access = SVNWCAccess.newInstance(eventHandler);
328: access.setOptions(myOptions);
329: return access;
330: }
331:
332: /**
333: * Dispatches events to the registered event handler (if any).
334: *
335: * @param event the current event
336: * @param progress progress state (from 0 to 1)
337: * @throws SVNException
338: */
339: public void handleEvent(SVNEvent event, double progress)
340: throws SVNException {
341: dispatchEvent(event, progress);
342: }
343:
344: /**
345: * Redirects this call to the registered event handler (if any).
346: *
347: * @throws SVNCancelException if the current operation
348: * was cancelled
349: */
350: public void checkCancelled() throws SVNCancelException {
351: if (myEventDispatcher != null) {
352: myEventDispatcher.checkCancelled();
353: }
354: }
355:
356: protected long getRevisionNumber(SVNRevision revision,
357: SVNRepository repository, File path) throws SVNException {
358: if (repository == null
359: && (revision == SVNRevision.HEAD || revision.getDate() != null)) {
360: SVNErrorMessage err = SVNErrorMessage
361: .create(SVNErrorCode.CLIENT_RA_ACCESS_REQUIRED);
362: SVNErrorManager.error(err);
363: }
364: if (revision.getNumber() >= 0) {
365: return revision.getNumber();
366: } else if (revision.getDate() != null) {
367: return repository.getDatedRevision(revision.getDate());
368: } else if (revision == SVNRevision.HEAD) {
369: return repository.getLatestRevision();
370: } else if (!revision.isValid()) {
371: return -1;
372: } else if (revision == SVNRevision.COMMITTED
373: || revision == SVNRevision.WORKING
374: || revision == SVNRevision.BASE
375: || revision == SVNRevision.PREVIOUS) {
376: if (path == null) {
377: SVNErrorMessage err = SVNErrorMessage
378: .create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED);
379: SVNErrorManager.error(err);
380: }
381: SVNWCAccess wcAccess = createWCAccess();
382: wcAccess.probeOpen(path, false, 0);
383: SVNEntry entry = wcAccess.getEntry(path, false);
384: wcAccess.close();
385:
386: if (entry == null) {
387: SVNErrorMessage err = SVNErrorMessage.create(
388: SVNErrorCode.UNVERSIONED_RESOURCE,
389: "''{0}'' is not under version control", path);
390: SVNErrorManager.error(err);
391: }
392: if (revision == SVNRevision.WORKING
393: || revision == SVNRevision.BASE) {
394: return entry.getRevision();
395: }
396: if (entry.getCommittedRevision() < 0) {
397: SVNErrorMessage err = SVNErrorMessage.create(
398: SVNErrorCode.CLIENT_BAD_REVISION,
399: "Path ''{0}'' has no committed revision", path);
400: SVNErrorManager.error(err);
401: }
402: return revision == SVNRevision.PREVIOUS ? entry
403: .getCommittedRevision() - 1 : entry
404: .getCommittedRevision();
405: } else {
406: SVNErrorMessage err = SVNErrorMessage.create(
407: SVNErrorCode.CLIENT_BAD_REVISION,
408: "Unrecognized revision type requested for ''{0}''",
409: path != null ? path : (Object) repository
410: .getLocation());
411: SVNErrorManager.error(err);
412: }
413: return -1;
414: }
415:
416: protected SVNRepository createRepository(SVNURL url, File path,
417: SVNRevision pegRevision, SVNRevision revision)
418: throws SVNException {
419: return createRepository(url, path, pegRevision, revision, null);
420: }
421:
422: protected SVNRepository createRepository(SVNURL url, File path,
423: SVNRevision pegRevision, SVNRevision revision, long[] pegRev)
424: throws SVNException {
425: if (url == null) {
426: SVNURL pathURL = getURL(path);
427: if (pathURL == null) {
428: SVNErrorMessage err = SVNErrorMessage.create(
429: SVNErrorCode.ENTRY_MISSING_URL,
430: "''{0}'' has no URL", path);
431: SVNErrorManager.error(err);
432: }
433: }
434: if (!revision.isValid() && pegRevision.isValid()) {
435: revision = pegRevision;
436: }
437: SVNRevision startRevision = SVNRevision.UNDEFINED;
438: if (path == null) {
439: if (!revision.isValid()) {
440: startRevision = SVNRevision.HEAD;
441: } else {
442: startRevision = revision;
443: }
444: if (!pegRevision.isValid()) {
445: pegRevision = SVNRevision.HEAD;
446: }
447: } else {
448: if (!revision.isValid()) {
449: startRevision = SVNRevision.BASE;
450: } else {
451: startRevision = revision;
452: }
453: if (!pegRevision.isValid()) {
454: pegRevision = SVNRevision.WORKING;
455: }
456: }
457: // SVNRepository repository = createRepository(initialURL, true);
458:
459: SVNRepositoryLocation[] locations = getLocations(url, path,
460: null, pegRevision, startRevision, SVNRevision.UNDEFINED);
461: url = locations[0].getURL();
462: long actualRevision = locations[0].getRevisionNumber();
463: SVNRepository repository = createRepository(url, true);
464: actualRevision = getRevisionNumber(SVNRevision
465: .create(actualRevision), repository, path);
466: if (actualRevision < 0) {
467: actualRevision = repository.getLatestRevision();
468: }
469: if (pegRev != null && pegRev.length > 0) {
470: pegRev[0] = actualRevision;
471: }
472: return repository;
473: }
474:
475: protected SVNRepositoryLocation[] getLocations(SVNURL url,
476: File path, SVNRepository repository, SVNRevision revision,
477: SVNRevision start, SVNRevision end) throws SVNException {
478: if (!revision.isValid() || !start.isValid()) {
479: SVNErrorManager.error(SVNErrorMessage
480: .create(SVNErrorCode.CLIENT_BAD_REVISION));
481: }
482: long pegRevisionNumber = -1;
483: long startRevisionNumber;
484: long endRevisionNumber;
485:
486: if (path != null) {
487: SVNWCAccess wcAccess = SVNWCAccess.newInstance(null);
488: try {
489: wcAccess.openAnchor(path, false, 0);
490: SVNEntry entry = wcAccess.getEntry(path, false);
491: if (entry.getCopyFromURL() != null
492: && revision == SVNRevision.WORKING) {
493: url = entry.getCopyFromSVNURL();
494: pegRevisionNumber = entry.getCopyFromRevision();
495: } else if (entry.getURL() != null) {
496: url = entry.getSVNURL();
497: } else {
498: SVNErrorMessage err = SVNErrorMessage.create(
499: SVNErrorCode.ENTRY_MISSING_URL,
500: "''{0}'' has no URL", path);
501: SVNErrorManager.error(err);
502: }
503: } finally {
504: wcAccess.close();
505: }
506: }
507: if (repository == null) {
508: repository = createRepository(url, true);
509: }
510: if (pegRevisionNumber < 0) {
511: pegRevisionNumber = getRevisionNumber(revision, repository,
512: path);
513: }
514: if (revision == start && revision == SVNRevision.HEAD) {
515: startRevisionNumber = pegRevisionNumber;
516: } else {
517: startRevisionNumber = getRevisionNumber(start, repository,
518: path);
519: }
520: if (!end.isValid()) {
521: endRevisionNumber = startRevisionNumber;
522: } else {
523: endRevisionNumber = getRevisionNumber(end, repository, path);
524: }
525: if (endRevisionNumber == pegRevisionNumber
526: && startRevisionNumber == pegRevisionNumber) {
527: SVNRepositoryLocation[] result = new SVNRepositoryLocation[2];
528: result[0] = new SVNRepositoryLocation(url,
529: startRevisionNumber);
530: result[1] = new SVNRepositoryLocation(url,
531: endRevisionNumber);
532: return result;
533: }
534: SVNURL rootURL = repository.getRepositoryRoot(true);
535: long[] revisionsRange = startRevisionNumber == endRevisionNumber ? new long[] { startRevisionNumber }
536: : new long[] { startRevisionNumber, endRevisionNumber };
537:
538: Map locations = null;
539: try {
540: locations = repository.getLocations("", (Map) null,
541: pegRevisionNumber, revisionsRange);
542: } catch (SVNException e) {
543: if (e.getErrorMessage() != null
544: && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
545: locations = getLocations10(repository,
546: pegRevisionNumber, startRevisionNumber,
547: endRevisionNumber);
548: } else {
549: throw e;
550: }
551: }
552: // try to get locations with 'log' method.
553: SVNLocationEntry startPath = (SVNLocationEntry) locations
554: .get(new Long(startRevisionNumber));
555: SVNLocationEntry endPath = (SVNLocationEntry) locations
556: .get(new Long(endRevisionNumber));
557:
558: if (startPath == null) {
559: Object source = path != null ? (Object) path : (Object) url;
560: SVNErrorMessage err = SVNErrorMessage
561: .create(
562: SVNErrorCode.CLIENT_UNRELATED_RESOURCES,
563: "Unable to find repository location for ''{0}'' in revision ''{1}''",
564: new Object[] { source,
565: new Long(startRevisionNumber) });
566: SVNErrorManager.error(err);
567: }
568: if (endPath == null) {
569: Object source = path != null ? (Object) path : (Object) url;
570: SVNErrorMessage err = SVNErrorMessage
571: .create(
572: SVNErrorCode.CLIENT_UNRELATED_RESOURCES,
573: "The location for ''{0}'' for revision {1} does not exist in the "
574: + "repository or refers to an unrelated object",
575: new Object[] { source,
576: new Long(endRevisionNumber) });
577: SVNErrorManager.error(err);
578: }
579:
580: SVNRepositoryLocation[] result = new SVNRepositoryLocation[2];
581: SVNURL startURL = SVNURL.parseURIEncoded(SVNPathUtil.append(
582: rootURL.toString(), SVNEncodingUtil.uriEncode(startPath
583: .getPath())));
584: result[0] = new SVNRepositoryLocation(startURL,
585: startRevisionNumber);
586: if (end.isValid()) {
587: SVNURL endURL = SVNURL.parseURIEncoded(SVNPathUtil.append(
588: rootURL.toString(), SVNEncodingUtil
589: .uriEncode(endPath.getPath())));
590: result[1] = new SVNRepositoryLocation(endURL,
591: endRevisionNumber);
592: }
593: return result;
594: }
595:
596: private Map getLocations10(SVNRepository repos,
597: final long pegRevision, final long startRevision,
598: final long endRevision) throws SVNException {
599: final String path = repos.getRepositoryPath("");
600: final SVNNodeKind kind = repos.checkPath("", pegRevision);
601: if (kind == SVNNodeKind.NONE) {
602: SVNErrorMessage err = SVNErrorMessage.create(
603: SVNErrorCode.FS_NOT_FOUND,
604: "path ''{0}'' doesn't exist at revision {1}",
605: new Object[] { path, new Long(pegRevision) });
606: SVNErrorManager.error(err);
607: }
608: long logStart = pegRevision;
609: logStart = Math.max(startRevision, logStart);
610: logStart = Math.max(endRevision, logStart);
611: long logEnd = pegRevision;
612: logStart = Math.min(startRevision, logStart);
613: logStart = Math.min(endRevision, logStart);
614:
615: LocationsLogEntryHandler handler = new LocationsLogEntryHandler(
616: path, startRevision, endRevision, pegRevision, kind,
617: getEventDispatcher());
618: repos.log(new String[] { "" }, logStart, logEnd, true, false,
619: handler);
620:
621: String pegPath = handler.myPegPath == null ? handler.myCurrentPath
622: : handler.myPegPath;
623: String startPath = handler.myStartPath == null ? handler.myCurrentPath
624: : handler.myStartPath;
625: String endPath = handler.myEndPath == null ? handler.myCurrentPath
626: : handler.myEndPath;
627:
628: if (pegPath == null) {
629: SVNErrorMessage err = SVNErrorMessage
630: .create(
631: SVNErrorCode.FS_NOT_FOUND,
632: "path ''{0}'' in revision {1} is an unrelated object",
633: new Object[] { path, new Long(logStart) });
634: SVNErrorManager.error(err);
635: }
636: Map result = new HashMap();
637: result.put(new Long(startRevision), new SVNLocationEntry(-1,
638: startPath));
639: result.put(new Long(endRevision), new SVNLocationEntry(-1,
640: endPath));
641: return result;
642: }
643:
644: private static String getPreviousLogPath(String path,
645: SVNLogEntry logEntry, SVNNodeKind kind) throws SVNException {
646: String prevPath = null;
647: SVNLogEntryPath logPath = (SVNLogEntryPath) logEntry
648: .getChangedPaths().get(path);
649: if (logPath != null) {
650: if (logPath.getType() != SVNLogEntryPath.TYPE_ADDED
651: && logPath.getType() != SVNLogEntryPath.TYPE_REPLACED) {
652: return logPath.getPath();
653: }
654: if (logPath.getCopyPath() != null) {
655: return logPath.getCopyPath();
656: }
657: return null;
658: } else if (!logEntry.getChangedPaths().isEmpty()) {
659: Map sortedMap = new HashMap();
660: sortedMap.putAll(logEntry.getChangedPaths());
661: List pathsList = new ArrayList(sortedMap.keySet());
662: Collections.sort(pathsList, SVNPathUtil.PATH_COMPARATOR);
663: Collections.reverse(pathsList);
664: for (Iterator paths = pathsList.iterator(); paths.hasNext();) {
665: String p = (String) paths.next();
666: if (path.startsWith(p + "/")) {
667: SVNLogEntryPath lPath = (SVNLogEntryPath) sortedMap
668: .get(p);
669: if (lPath.getCopyPath() != null) {
670: prevPath = SVNPathUtil.append(lPath
671: .getCopyPath(), path.substring(p
672: .length()));
673: break;
674: }
675: }
676: }
677: }
678: if (prevPath == null) {
679: if (kind == SVNNodeKind.DIR) {
680: prevPath = path;
681: } else {
682: SVNErrorMessage err = SVNErrorMessage
683: .create(
684: SVNErrorCode.CLIENT_UNRELATED_RESOURCES,
685: "Missing changed-path information for ''{0}'' in revision {1}",
686: new Object[] {
687: path,
688: new Long(logEntry.getRevision()) });
689: SVNErrorManager.error(err);
690: }
691: }
692: return prevPath;
693: }
694:
695: protected SVNURL getURL(File path) throws SVNException {
696: if (path == null) {
697: return null;
698: }
699: SVNWCAccess wcAccess = createWCAccess();
700: try {
701: wcAccess.probeOpen(path, false, 0);
702: SVNEntry entry = wcAccess.getEntry(path, false);
703: return entry != null ? entry.getSVNURL() : null;
704: } finally {
705: wcAccess.close();
706: }
707: }
708:
709: private static final class LocationsLogEntryHandler implements
710: ISVNLogEntryHandler {
711:
712: private String myCurrentPath = null;
713: private String myStartPath = null;
714: private String myEndPath = null;
715: private String myPegPath = null;
716:
717: private long myStartRevision;
718: private long myEndRevision;
719: private long myPegRevision;
720: private SVNNodeKind myKind;
721: private ISVNEventHandler myEventHandler;
722:
723: private LocationsLogEntryHandler(String path,
724: long startRevision, long endRevision, long pegRevision,
725: SVNNodeKind kind, ISVNEventHandler eventHandler) {
726: myCurrentPath = path;
727: myStartRevision = startRevision;
728: myEndRevision = endRevision;
729: myPegRevision = pegRevision;
730: myEventHandler = eventHandler;
731: myKind = kind;
732: }
733:
734: public void handleLogEntry(SVNLogEntry logEntry)
735: throws SVNException {
736: if (myEventHandler != null) {
737: myEventHandler.checkCancelled();
738: }
739: if (logEntry.getChangedPaths() == null) {
740: return;
741: }
742: if (myCurrentPath == null) {
743: return;
744: }
745: if (myStartPath == null
746: && logEntry.getRevision() <= myStartRevision) {
747: myStartPath = myCurrentPath;
748: }
749: if (myEndPath == null
750: && logEntry.getRevision() <= myEndRevision) {
751: myEndPath = myCurrentPath;
752: }
753: if (myPegPath == null
754: && logEntry.getRevision() <= myPegRevision) {
755: myPegPath = myCurrentPath;
756: }
757: myCurrentPath = getPreviousLogPath(myCurrentPath, logEntry,
758: myKind);
759: }
760: }
761:
762: protected static class RepositoryReference {
763:
764: public RepositoryReference(String url, long rev) {
765: URL = url;
766: Revision = rev;
767: }
768:
769: public String URL;
770:
771: public long Revision;
772: }
773:
774: protected static class SVNRepositoryLocation {
775:
776: private SVNURL myURL;
777: private long myRevision;
778:
779: public SVNRepositoryLocation(SVNURL url, long rev) {
780: myURL = url;
781: myRevision = rev;
782: }
783:
784: public long getRevisionNumber() {
785: return myRevision;
786: }
787:
788: public SVNURL getURL() {
789: return myURL;
790: }
791: }
792:
793: }
|