package org.tmatesoft.svn.core.internal.wc16; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.tmatesoft.svn.core.ISVNLogEntryHandler; import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.SVNLogEntryPath; import org.tmatesoft.svn.core.SVNMergeInfo; import org.tmatesoft.svn.core.SVNMergeInfoInheritance; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager; import org.tmatesoft.svn.core.internal.wc.SVNWCManager; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; import org.tmatesoft.svn.core.io.SVNLocationEntry; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.DefaultSVNRepositoryPool; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.ISVNPathListHandler; import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; import org.tmatesoft.svn.core.wc.SVNCommitItem; import org.tmatesoft.svn.core.wc.SVNDiffClient; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNUpdateClient; import org.tmatesoft.svn.core.wc.SVNWCUtil; import org.tmatesoft.svn.util.ISVNDebugLog; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * The <b>SVNBasicClient</b> is the base class of all <b>SVN</b>*<b>Client</b> * classes that provides a common interface and realization. * <p> * All of <b>SVN</b>*<b>Client</b> classes use inherited methods of * <b>SVNBasicClient</b> to access Working Copies metadata, to create a driver * object to access a repository if it's necessary, etc. In addition * <b>SVNBasicClient</b> provides some interface methods - such as those that * allow you to set your {@link ISVNEventHandler event handler}, obtain run-time * configuration options, and others. * * @version 1.3 * @author TMate Software Ltd. * @since 1.2 */ public class SVNBasicDelegate implements ISVNEventHandler { private ISVNRepositoryPool myRepositoryPool; private ISVNOptions myOptions; private ISVNEventHandler myEventDispatcher; private List myPathPrefixesStack; private boolean myIsIgnoreExternals; private boolean myIsLeaveConflictsUnresolved; private ISVNDebugLog myDebugLog; private ISVNPathListHandler myPathListHandler; public SVNBasicDelegate(final ISVNAuthenticationManager authManager, ISVNOptions options) { this(new DefaultSVNRepositoryPool(authManager == null ? SVNWCUtil.createDefaultAuthenticationManager() : authManager, options, 0, false), options); } protected SVNBasicDelegate(ISVNRepositoryPool repositoryPool, ISVNOptions options) { myRepositoryPool = repositoryPool; setOptions(options); myPathPrefixesStack = new LinkedList(); } /** * Gets run-time configuration options used by this object. * * @return the run-time options being in use */ public ISVNOptions getOptions() { return this.myOptions; } /** * Sets run-time global configuration options to this object. * * @param options * the run-time configuration options */ public void setOptions(ISVNOptions options) { this.myOptions = options; } /** * Sets externals definitions to be ignored or not during operations. * <p> * For example, if external definitions are set to be ignored then a * checkout operation won't fetch them into a Working Copy. * * @param ignore * <span class="javakeyword">true</span> to ignore externals * definitions, <span class="javakeyword">false</span> - not to * @see #isIgnoreExternals() */ public void setIgnoreExternals(boolean ignore) { myIsIgnoreExternals = ignore; } /** * Determines if externals definitions are ignored. * * @return <span class="javakeyword">true</span> if ignored, otherwise <span * class="javakeyword">false</span> * @see #setIgnoreExternals(boolean) */ public boolean isIgnoreExternals() { return myIsIgnoreExternals; } /** * Sets (or unsets) all conflicted working files to be untouched by update * and merge operations. * <p> * By default when a file receives changes from the repository that are in * conflict with local edits, an update operation places two sections for * each conflicting snatch into the working file one of which is a user's * local edit and the second is the one just received from the repository. * Like this: * * <pre class="javacode"> * <<<<<<< .mine * user's text * ======= * received text * >>>>>>> .r2 * </pre> * * <br /> * Also the operation creates three temporary files that appear in the same * directory as the working file. Now if you call this method with * <code>leave</code> set to <span class="javakeyword">true</span>, an * update will still create temporary files but won't place those two * sections into your working file. And this behaviour also concerns merge * operations: any merging to a conflicted file will be prevented. In * addition if there is any registered event handler for an * <b>SVNDiffClient</b> or <b>SVNUpdateClient</b> instance then the handler * will be dispatched an event with the status type set to * {@link SVNStatusType#CONFLICTED_UNRESOLVED}. * <p> * The default value is <span class="javakeyword">false</span> until a * caller explicitly changes it calling this method. * * @param leave * <span class="javakeyword">true</span> to prevent conflicted * files from merging (all merging operations will be skipped), * otherwise <span class="javakeyword">false</span> * @see #isLeaveConflictsUnresolved() * @see SVNUpdateClient * @see SVNDiffClient * @see ISVNEventHandler * @deprecated this method should not be used anymore */ public void setLeaveConflictsUnresolved(boolean leave) { myIsLeaveConflictsUnresolved = leave; } /** * Determines if conflicted files should be left unresolved preventing from * merging their contents during update and merge operations. * * @return <span class="javakeyword">true</span> if conflicted files are set * to be prevented from merging, <span * class="javakeyword">false</span> if there's no such restriction * @see #setLeaveConflictsUnresolved(boolean) * @deprecated this method should not be used anymore */ public boolean isLeaveConflictsUnresolved() { return myIsLeaveConflictsUnresolved; } /** * Sets an event handler for this object. This event handler will be * dispatched {@link SVNEvent} objects to provide detailed information about * actions and progress state of version control operations performed by * <b>do</b>*<b>()</b> methods of <b>SVN</b>*<b>Client</b> classes. * * @param dispatcher * an event handler */ public void setEventHandler(ISVNEventHandler dispatcher) { myEventDispatcher = dispatcher; } /** * Sets a path list handler implementation to this object. * * @param handler * handler implementation * @since 1.2.0 */ public void setPathListHandler(ISVNPathListHandler handler) { this.myPathListHandler = handler; } /** * Sets a logger to write debug log information to. * * @param log * a debug logger */ public void setDebugLog(ISVNDebugLog log) { this.myDebugLog = log; } /** * Returns the debug logger currently in use. * <p> * If no debug logger has been specified by the time this call occurs, a * default one (returned by * <code>org.tmatesoft.svn.util.SVNDebugLog.getDefaultLog()</code>) will be * created and used. * * @return a debug logger */ public ISVNDebugLog getDebugLog() { return this.myDebugLog; } /** * Returns the root of the repository. * <p/> * If <code>path</code> is not <span class="javakeyword">null</span> and * <code>pegRevision</code> is either {@link SVNRevision#WORKING} or * {@link SVNRevision#BASE}, then attempts to fetch the repository root from * the working copy represented by <code>path</code>. If these conditions * are not met or if the repository root is not recorded in the working * copy, then a repository connection is established and the repository root * is fetched from the session. * <p/> * When fetching the repository root from the working copy and if * <code>access</code> is <span class="javakeyword">null</span>, a new * working copy access will be created and the working copy will be opened * non-recursively for reading only. * <p/> * All necessary cleanup (session or|and working copy close) will be * performed automatically as the routine finishes. * * @param path * working copy path * @param url * repository url * @param pegRevision * revision in which the target is valid * @param adminArea * working copy administrative area object * @param access * working copy access object * @return repository root url * @throws SVNException * @since 1.2.0 */ public SVNURL getReposRoot(File path, SVNURL url, SVNRevision pegRevision, SVNAdminArea adminArea, SVNWCAccess access) throws SVNException { SVNURL reposRoot = null; if (path != null && (pegRevision == SVNRevision.WORKING || pegRevision == SVNRevision.BASE)) { if (access == null) { access = createWCAccess(); } boolean needCleanUp = false; try { if (adminArea == null) { adminArea = access.probeOpen(path, false, 0); needCleanUp = true; } SVNEntry entry = access.getVersionedEntry(path, false); url = getEntryLocation(path, entry, null, SVNRevision.UNDEFINED); reposRoot = entry.getRepositoryRootURL(); } finally { if (needCleanUp) { access.closeAdminArea(path); } } } if (reposRoot == null) { SVNRepository repos = null; try { repos = createRepository(url, path, null, pegRevision, pegRevision, null); reposRoot = repos.getRepositoryRoot(true); } finally { if (repos != null) { repos.closeSession(); } } } return reposRoot; } protected void sleepForTimeStamp() { if (myPathPrefixesStack == null || myPathPrefixesStack.isEmpty()) { SVNFileUtil.sleepForTimestamp(); } } protected SVNRepository createRepository(SVNURL url, File path, SVNWCAccess access, boolean mayReuse) throws SVNException { String uuid = null; if (access != null) { SVNEntry entry = access.getEntry(path, false); if (entry != null) { uuid = entry.getUUID(); } } return createRepository(url, uuid, mayReuse); } protected SVNRepository createRepository(SVNURL url, String uuid, boolean mayReuse) throws SVNException { SVNRepository repository = null; if (myRepositoryPool == null) { repository = SVNRepositoryFactory.create(url, null); } else { repository = myRepositoryPool.createRepository(url, mayReuse); } if (uuid != null) { String reposUUID = repository.getRepositoryUUID(true); if (!uuid.equals(reposUUID)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_UUID_MISMATCH, "Repository UUID ''{0}'' doesn''t match expected UUID ''{1}''", new Object[] { reposUUID, uuid }); SVNErrorManager.error(err, SVNLogType.WC); } } repository.setDebugLog(getDebugLog()); repository.setCanceller(getEventDispatcher()); return repository; } protected ISVNRepositoryPool getRepositoryPool() { return this.myRepositoryPool; } protected void dispatchEvent(SVNEvent event) throws SVNException { dispatchEvent(event, ISVNEventHandler.UNKNOWN); } protected void dispatchEvent(SVNEvent event, double progress) throws SVNException { if (myEventDispatcher != null) { try { myEventDispatcher.handleEvent(event, progress); } catch (SVNException e) { throw e; } catch (Throwable th) { SVNDebugLog.getDefaultLog().logSevere(SVNLogType.WC, th); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Error while dispatching event: {0}", new Object[] { th.getMessage() }, SVNErrorMessage.TYPE_ERROR, th); SVNErrorManager.error(err, th, SVNLogType.DEFAULT); } } } /** * Removes or adds a path prefix. This method is not intended for users * (from an API point of view). * * @param prefix * a path prefix */ public void setEventPathPrefix(String prefix) { if (prefix == null && !myPathPrefixesStack.isEmpty()) { myPathPrefixesStack.remove(myPathPrefixesStack.size() - 1); } else if (prefix != null) { myPathPrefixesStack.add(prefix); } } protected ISVNEventHandler getEventDispatcher() { return this.myEventDispatcher; } protected SVNWCAccess createWCAccess() { return createWCAccess(null); } protected SVNWCAccess createWCAccess(final String pathPrefix) { ISVNEventHandler eventHandler = null; if (pathPrefix != null) { eventHandler = new ISVNEventHandler() { public void handleEvent(SVNEvent event, double progress) throws SVNException { dispatchEvent(event, progress); } public void checkCancelled() throws SVNCancelException { SVNBasicDelegate.this.checkCancelled(); } }; } else { eventHandler = this; } SVNWCAccess access = SVNWCAccess.newInstance(eventHandler); access.setOptions(myOptions); return access; } /** * Dispatches events to the registered event handler (if any). * * @param event * the current event * @param progress * progress state (from 0 to 1) * @throws SVNException */ public void handleEvent(SVNEvent event, double progress) throws SVNException { dispatchEvent(event, progress); } /** * Handles a next working copy path with the {@link ISVNPathListHandler path * list handler} if any was provided to this object through * {@link #setPathListHandler(ISVNPathListHandler)}. * <p/> * Note: used by <code>SVNKit</code> internals. * * @param path * working copy path * @throws SVNException * @since 1.2.0 */ public void handlePathListItem(File path) throws SVNException { if (myPathListHandler != null && path != null) { myPathListHandler.handlePathListItem(path); } } /** * Redirects this call to the registered event handler (if any). * * @throws SVNCancelException * if the current operation was cancelled */ public void checkCancelled() throws SVNCancelException { if (myEventDispatcher != null) { myEventDispatcher.checkCancelled(); } } protected long getRevisionNumber(SVNRevision revision, SVNRepository repository, File path) throws SVNException { return getRevisionNumber(revision, null, repository, path); } protected long getRevisionNumber(SVNRevision revision, long[] latestRevisionNumber, SVNRepository repository, File path) throws SVNException { if (repository == null && (revision == SVNRevision.HEAD || revision.getDate() != null)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_RA_ACCESS_REQUIRED); SVNErrorManager.error(err, SVNLogType.WC); } if (revision.getNumber() >= 0) { return revision.getNumber(); } else if (revision.getDate() != null) { return repository.getDatedRevision(revision.getDate()); } else if (revision == SVNRevision.HEAD) { if (latestRevisionNumber != null && latestRevisionNumber.length > 0 && SVNRevision.isValidRevisionNumber(latestRevisionNumber[0])) { return latestRevisionNumber[0]; } long latestRevision = repository.getLatestRevision(); if (latestRevisionNumber != null && latestRevisionNumber.length > 0) { latestRevisionNumber[0] = latestRevision; } return latestRevision; } else if (!revision.isValid()) { return -1; } else if (revision == SVNRevision.COMMITTED || revision == SVNRevision.WORKING || revision == SVNRevision.BASE || revision == SVNRevision.PREVIOUS) { if (path == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED); SVNErrorManager.error(err, SVNLogType.WC); } SVNWCAccess wcAccess = createWCAccess(); wcAccess.probeOpen(path, false, 0); SVNEntry entry = null; try { entry = wcAccess.getVersionedEntry(path, false); } finally { wcAccess.close(); } if (revision == SVNRevision.WORKING || revision == SVNRevision.BASE) { return entry.getRevision(); } if (entry.getCommittedRevision() < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Path ''{0}'' has no committed revision", path); SVNErrorManager.error(err, SVNLogType.WC); } return revision == SVNRevision.PREVIOUS ? entry.getCommittedRevision() - 1 : entry.getCommittedRevision(); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Unrecognized revision type requested for ''{0}''", path != null ? path : (Object) repository.getLocation()); SVNErrorManager.error(err, SVNLogType.WC); } return -1; } protected SVNRepository createRepository(SVNURL url, File path, SVNAdminArea area, SVNRevision pegRevision, SVNRevision revision, long[] pegRev) throws SVNException { if (url == null) { SVNURL pathURL = getURL(path); if (pathURL == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", path); SVNErrorManager.error(err, SVNLogType.WC); } } SVNRevision startRevision = revision; SVNRevision[] resolvedRevisions = resolveRevisions(pegRevision, startRevision, url != null, true); pegRevision = resolvedRevisions[0]; startRevision = resolvedRevisions[1]; SVNRepositoryLocation[] locations = getLocations(url, path, null, pegRevision, startRevision, SVNRevision.UNDEFINED); url = locations[0].getURL(); long actualRevision = locations[0].getRevisionNumber(); SVNRepository repository = createRepository(url, area != null ? area.getRoot() : null, area != null ? area.getWCAccess() : null, true); actualRevision = getRevisionNumber(SVNRevision.create(actualRevision), repository, path); if (actualRevision < 0) { actualRevision = repository.getLatestRevision(); } if (pegRev != null && pegRev.length > 0) { pegRev[0] = actualRevision; } return repository; } protected SVNRevision[] resolveRevisions(SVNRevision pegRevision, SVNRevision revision, boolean isURL, boolean noticeLocalModifications) { if (!pegRevision.isValid()) { if (isURL) { pegRevision = SVNRevision.HEAD; } else { if (noticeLocalModifications) { pegRevision = SVNRevision.WORKING; } else { pegRevision = SVNRevision.BASE; } } } if (!revision.isValid()) { revision = pegRevision; } return new SVNRevision[] { pegRevision, revision }; } protected void elideMergeInfo(SVNWCAccess access, File path, SVNEntry entry, File wcElisionLimitPath) throws SVNException { if (wcElisionLimitPath == null || !wcElisionLimitPath.equals(path)) { Map mergeInfo = null; Map targetMergeInfo = null; boolean[] inherited = new boolean[1]; targetMergeInfo = getWCMergeInfo(path, entry, wcElisionLimitPath, SVNMergeInfoInheritance.INHERITED, false, inherited); if (inherited[0] || targetMergeInfo == null) { return; } mergeInfo = getWCMergeInfo(path, entry, wcElisionLimitPath, SVNMergeInfoInheritance.NEAREST_ANCESTOR, false, inherited); if (mergeInfo == null && wcElisionLimitPath == null) { mergeInfo = getWCOrRepositoryMergeInfo(path, entry, SVNMergeInfoInheritance.NEAREST_ANCESTOR, inherited, true, null); } if (mergeInfo == null && wcElisionLimitPath != null) { return; } SVNMergeInfoUtil.elideMergeInfo(mergeInfo, targetMergeInfo, path, null, access); } } /** * @param path * path relative to the repository location. */ protected Map getReposMergeInfo(SVNRepository repository, String path, long revision, SVNMergeInfoInheritance inheritance, boolean squelchIncapable) throws SVNException { Map reposMergeInfo = null; try { reposMergeInfo = repository.getMergeInfo(new String[] { path }, revision, inheritance, false); } catch (SVNException svne) { if (!squelchIncapable || svne.getErrorMessage().getErrorCode() != SVNErrorCode.UNSUPPORTED_FEATURE) { throw svne; } } String rootRelativePath = getPathRelativeToRoot(null, repository.getLocation(), repository.getRepositoryRoot(false), null, repository); Map targetMergeInfo = null; if (reposMergeInfo != null) { SVNMergeInfo mergeInfo = (SVNMergeInfo) reposMergeInfo.get(rootRelativePath); if (mergeInfo != null) { targetMergeInfo = mergeInfo.getMergeSourcesToMergeLists(); } } return targetMergeInfo; } protected Map getWCOrRepositoryMergeInfo(File path, SVNEntry entry, SVNMergeInfoInheritance inherit, boolean[] indirect, boolean reposOnly, SVNRepository repository) throws SVNException { Map mergeInfo = null; long targetRev[] = { -1 }; SVNURL url = getEntryLocation(path, entry, targetRev, SVNRevision.WORKING); long revision = targetRev[0]; if (!reposOnly) { mergeInfo = getWCMergeInfo(path, entry, null, inherit, false, indirect); } if (mergeInfo == null) { if (!entry.isScheduledForAddition()) { Map fileToProp = SVNPropertiesManager.getWorkingCopyPropertyValues(path, entry, SVNProperty.MERGE_INFO, SVNDepth.EMPTY, true); SVNPropertyValue mergeInfoProp = (SVNPropertyValue) fileToProp.get(path); if (mergeInfoProp == null) { SVNURL oldLocation = null; boolean closeRepository = false; Map reposMergeInfo = null; String repositoryPath = null; try { if (repository == null) { repository = createRepository(url, null, null, false); closeRepository = true; } repositoryPath = getPathRelativeToSession(url, null, repository); oldLocation = repository.getLocation(); if (repositoryPath == null) { repositoryPath = ""; repository.setLocation(url, false); } reposMergeInfo = getReposMergeInfo(repository, repositoryPath, revision, inherit, true); } finally { if (closeRepository) { repository.closeSession(); } else if (oldLocation != null) { repository.setLocation(oldLocation, false); } } if (reposMergeInfo != null) { indirect[0] = true; mergeInfo = reposMergeInfo; } } } } return mergeInfo; } /** * mergeInfo must not be null! */ protected Map getWCMergeInfo(File path, SVNEntry entry, File limitPath, SVNMergeInfoInheritance inherit, boolean base, boolean[] inherited) throws SVNException { String walkPath = ""; Map wcMergeInfo = null; SVNWCAccess wcAccess = createWCAccess(); Map mergeInfo = null; if (limitPath != null) { limitPath = new File(SVNPathUtil.validateFilePath(limitPath.getAbsolutePath())).getAbsoluteFile(); } try { while (true) { if (inherit == SVNMergeInfoInheritance.NEAREST_ANCESTOR) { wcMergeInfo = null; inherit = SVNMergeInfoInheritance.INHERITED; } else { wcMergeInfo = SVNPropertiesManager.parseMergeInfo(path, entry, base); } if (SVNWCManager.isEntrySwitched(path, entry)) { break; } path = new File(SVNPathUtil.validateFilePath(path.getAbsolutePath())).getAbsoluteFile(); if (wcMergeInfo == null && inherit != SVNMergeInfoInheritance.EXPLICIT && path.getParentFile() != null) { if (limitPath != null && limitPath.equals(path)) { break; } walkPath = SVNPathUtil.append(path.getName(), walkPath); path = path.getParentFile(); try { wcAccess.open(path, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { mergeInfo = wcMergeInfo; inherited[0] = false; return mergeInfo; } throw svne; } entry = wcAccess.getEntry(path, false); if (entry != null) { continue; } } break; } } finally { wcAccess.close(); } inherited[0] = false; if (walkPath.length() == 0) { mergeInfo = wcMergeInfo; } else { if (wcMergeInfo != null) { mergeInfo = SVNMergeInfoUtil.adjustMergeInfoSourcePaths(null, walkPath, wcMergeInfo); inherited[0] = true; } else { mergeInfo = null; } } if (inherited[0]) { mergeInfo = SVNMergeInfoUtil.getInheritableMergeInfo(mergeInfo, null, SVNRepository.INVALID_REVISION, SVNRepository.INVALID_REVISION); SVNMergeInfoUtil.removeEmptyRangeLists(mergeInfo); } return mergeInfo; } protected long getPathLastChangeRevision(String relPath, long revision, SVNRepository repository) throws SVNException { final long[] rev = new long[1]; rev[0] = SVNRepository.INVALID_REVISION; repository.log(new String[] { relPath }, 1, revision, false, true, 1, false, null, new ISVNLogEntryHandler() { public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { rev[0] = logEntry.getRevision(); } }); return rev[0]; } protected String getPathRelativeToRoot(File path, SVNURL url, SVNURL reposRootURL, SVNWCAccess wcAccess, SVNRepository repos) throws SVNException { if (path != null) { boolean cleanUp = false; try { if (wcAccess == null) { wcAccess = createWCAccess(); wcAccess.probeOpen(path, false, 0); cleanUp = true; } SVNEntry entry = wcAccess.getVersionedEntry(path, false); url = getEntryLocation(path, entry, null, SVNRevision.UNDEFINED); if (reposRootURL == null) { reposRootURL = entry.getRepositoryRootURL(); } } finally { if (cleanUp) { wcAccess.closeAdminArea(path); } } } if (reposRootURL == null) { reposRootURL = repos.getRepositoryRoot(true); } String reposRootPath = reposRootURL.getPath(); String absPath = url.getPath(); if (!absPath.startsWith(reposRootPath)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "URL ''{0}'' is not a child of repository root URL ''{1}''", new Object[] { url, reposRootURL }); SVNErrorManager.error(err, SVNLogType.WC); } absPath = absPath.substring(reposRootPath.length()); if (!absPath.startsWith("/")) { absPath = "/" + absPath; } return absPath; } protected String getPathRelativeToSession(SVNURL url, SVNURL sessionURL, SVNRepository repos) { if (sessionURL == null) { sessionURL = repos.getLocation(); } String reposPath = sessionURL.getPath(); String absPath = url.getPath(); if (!absPath.startsWith(reposPath + "/") && !absPath.equals(reposPath)) { return null; } absPath = absPath.substring(reposPath.length()); if (absPath.startsWith("/")) { absPath = absPath.substring(1); } return absPath; } protected SVNRepositoryLocation[] getLocations(SVNURL url, File path, SVNRepository repository, SVNRevision revision, SVNRevision start, SVNRevision end) throws SVNException { if (!revision.isValid() || !start.isValid()) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION), SVNLogType.DEFAULT); } long pegRevisionNumber = -1; long startRevisionNumber; long endRevisionNumber; long youngestRevNumber[] = { SVNRepository.INVALID_REVISION }; if (path != null) { SVNWCAccess wcAccess = SVNWCAccess.newInstance(null); try { wcAccess.openAnchor(path, false, 0); SVNEntry entry = wcAccess.getVersionedEntry(path, false); if (entry.getCopyFromURL() != null && revision == SVNRevision.WORKING) { url = entry.getCopyFromSVNURL(); pegRevisionNumber = entry.getCopyFromRevision(); if (entry.getURL() == null || !entry.getURL().equals(entry.getCopyFromURL())) { repository = null; } } else if (entry.getURL() != null) { url = entry.getSVNURL(); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", path); SVNErrorManager.error(err, SVNLogType.WC); } } finally { wcAccess.close(); } } String repoPath = ""; Map locations = null; SVNURL rootURL = null; if (repository == null) { repository = createRepository(url, null, null, true); } else { repoPath = SVNPathUtil.getPathAsChild(repository.getLocation().toString(), url.toString()); if (repoPath == null) { repoPath = ""; } } if (pegRevisionNumber < 0) { pegRevisionNumber = getRevisionNumber(revision, youngestRevNumber, repository, path); } if (revision == start && revision == SVNRevision.HEAD) { startRevisionNumber = pegRevisionNumber; } else { startRevisionNumber = getRevisionNumber(start, youngestRevNumber, repository, path); } if (!end.isValid()) { endRevisionNumber = startRevisionNumber; } else { endRevisionNumber = getRevisionNumber(end, youngestRevNumber, repository, path); } if (endRevisionNumber == pegRevisionNumber && startRevisionNumber == pegRevisionNumber) { SVNRepositoryLocation[] result = new SVNRepositoryLocation[2]; result[0] = new SVNRepositoryLocation(url, startRevisionNumber); result[1] = new SVNRepositoryLocation(url, endRevisionNumber); return result; } rootURL = repository.getRepositoryRoot(true); long[] revisionsRange = startRevisionNumber == endRevisionNumber ? new long[] { startRevisionNumber } : new long[] { startRevisionNumber, endRevisionNumber }; try { locations = repository.getLocations(repoPath, (Map) null, pegRevisionNumber, revisionsRange); } catch (SVNException e) { if (e.getErrorMessage() != null && e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) { locations = getLocations10(repository, pegRevisionNumber, startRevisionNumber, endRevisionNumber); } else { throw e; } } SVNLocationEntry startPath = (SVNLocationEntry) locations.get(new Long(startRevisionNumber)); SVNLocationEntry endPath = (SVNLocationEntry) locations.get(new Long(endRevisionNumber)); if (startPath == null) { Object source = path != null ? (Object) path : (Object) url; SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "Unable to find repository location for ''{0}'' in revision ''{1}''", new Object[] { source, new Long(startRevisionNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } if (endPath == null) { Object source = path != null ? (Object) path : (Object) url; SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "The location for ''{0}'' for revision {1} does not exist in the " + "repository or refers to an unrelated object", new Object[] { source, new Long(endRevisionNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } SVNRepositoryLocation[] result = new SVNRepositoryLocation[2]; SVNURL startURL = SVNURL.parseURIEncoded(SVNPathUtil.append(rootURL.toString(), SVNEncodingUtil.uriEncode(startPath.getPath()))); result[0] = new SVNRepositoryLocation(startURL, startRevisionNumber); if (end.isValid()) { SVNURL endURL = SVNURL.parseURIEncoded(SVNPathUtil.append(rootURL.toString(), SVNEncodingUtil.uriEncode(endPath.getPath()))); result[1] = new SVNRepositoryLocation(endURL, endRevisionNumber); } return result; } private Map getLocations10(SVNRepository repos, final long pegRevision, final long startRevision, final long endRevision) throws SVNException { final String path = repos.getRepositoryPath(""); final SVNNodeKind kind = repos.checkPath("", pegRevision); if (kind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "path ''{0}'' doesn't exist at revision {1}", new Object[] { path, new Long(pegRevision) }); SVNErrorManager.error(err, SVNLogType.WC); } long logStart = pegRevision; logStart = Math.max(startRevision, logStart); logStart = Math.max(endRevision, logStart); long logEnd = pegRevision; logStart = Math.min(startRevision, logStart); logStart = Math.min(endRevision, logStart); LocationsLogEntryHandler handler = new LocationsLogEntryHandler(path, startRevision, endRevision, pegRevision, kind, getEventDispatcher()); repos.log(new String[] { "" }, logStart, logEnd, true, false, handler); String pegPath = handler.myPegPath == null ? handler.myCurrentPath : handler.myPegPath; String startPath = handler.myStartPath == null ? handler.myCurrentPath : handler.myStartPath; String endPath = handler.myEndPath == null ? handler.myCurrentPath : handler.myEndPath; if (pegPath == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "path ''{0}'' in revision {1} is an unrelated object", new Object[] { path, new Long(logStart) }); SVNErrorManager.error(err, SVNLogType.WC); } Map result = new SVNHashMap(); result.put(new Long(startRevision), new SVNLocationEntry(-1, startPath)); result.put(new Long(endRevision), new SVNLocationEntry(-1, endPath)); return result; } private static String getPreviousLogPath(String path, SVNLogEntry logEntry, SVNNodeKind kind) throws SVNException { String prevPath = null; SVNLogEntryPath logPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(path); if (logPath != null) { if (logPath.getType() != SVNLogEntryPath.TYPE_ADDED && logPath.getType() != SVNLogEntryPath.TYPE_REPLACED) { return logPath.getPath(); } if (logPath.getCopyPath() != null) { return logPath.getCopyPath(); } return null; } else if (!logEntry.getChangedPaths().isEmpty()) { Map sortedMap = new SVNHashMap(); sortedMap.putAll(logEntry.getChangedPaths()); List pathsList = new ArrayList(sortedMap.keySet()); Collections.sort(pathsList, SVNPathUtil.PATH_COMPARATOR); Collections.reverse(pathsList); for (Iterator paths = pathsList.iterator(); paths.hasNext();) { String p = (String) paths.next(); if (path.startsWith(p + "/")) { SVNLogEntryPath lPath = (SVNLogEntryPath) sortedMap.get(p); if (lPath.getCopyPath() != null) { prevPath = SVNPathUtil.append(lPath.getCopyPath(), path.substring(p.length())); break; } } } } if (prevPath == null) { if (kind == SVNNodeKind.DIR) { prevPath = path; } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "Missing changed-path information for ''{0}'' in revision {1}", new Object[] { path, new Long(logEntry.getRevision()) }); SVNErrorManager.error(err, SVNLogType.WC); } } return prevPath; } protected SVNURL getURL(File path) throws SVNException { return deriveLocation(path, null, null, SVNRevision.UNDEFINED, null, null); } protected SVNURL deriveLocation(File path, SVNURL url, long[] pegRevisionNumber, SVNRevision pegRevision, SVNRepository repos, SVNWCAccess access) throws SVNException { if (path != null) { SVNEntry entry = null; if (access != null) { entry = access.getVersionedEntry(path, false); } else { SVNWCAccess wcAccess = createWCAccess(); try { wcAccess.probeOpen(path, false, 0); entry = wcAccess.getVersionedEntry(path, false); } finally { wcAccess.close(); } } url = getEntryLocation(path, entry, pegRevisionNumber, pegRevision); } if (pegRevisionNumber != null && pegRevisionNumber.length > 0 && !SVNRevision.isValidRevisionNumber(pegRevisionNumber[0])) { boolean closeRepository = false; try { if (repos == null) { repos = createRepository(url, null, null, false); closeRepository = true; } pegRevisionNumber[0] = getRevisionNumber(pegRevision, null, repos, path); } finally { if (closeRepository) { repos.closeSession(); } } } return url; } protected SVNURL getEntryLocation(File path, SVNEntry entry, long[] revNum, SVNRevision pegRevision) throws SVNException { SVNURL url = null; if (entry.getCopyFromURL() != null && pegRevision == SVNRevision.WORKING) { url = entry.getCopyFromSVNURL(); if (revNum != null && revNum.length > 0) { revNum[0] = entry.getCopyFromRevision(); } } else if (entry.getURL() != null) { url = entry.getSVNURL(); if (revNum != null && revNum.length > 0) { revNum[0] = entry.getRevision(); } } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Entry for ''{0}'' has no URL", path); SVNErrorManager.error(err, SVNLogType.WC); } return url; } protected SVNURL ensureSessionURL(SVNRepository repository, SVNURL url) throws SVNException { SVNURL oldURL = repository.getLocation(); if (url == null) { url = repository.getRepositoryRoot(true); } if (!url.equals(oldURL)) { repository.setLocation(url, false); return oldURL; } return null; } protected int getLevelsToLockFromDepth(SVNDepth depth) { return depth == SVNDepth.EMPTY || depth == SVNDepth.FILES ? 0 : (depth == SVNDepth.IMMEDIATES ? 1 : SVNWCAccess.INFINITE_DEPTH); } protected void setCommitItemAccess(SVNCommitItem item, SVNWCAccess access) { item.setWCAccess(access); } protected void setCommitItemProperty(SVNCommitItem item, String name, SVNPropertyValue value) { item.setProperty(name, value); } protected void setCommitItemFlags(SVNCommitItem item, boolean contentModified, boolean propertiesModified) { item.setContentsModified(contentModified); item.setPropertiesModified(propertiesModified); } private static final class LocationsLogEntryHandler implements ISVNLogEntryHandler { private String myCurrentPath = null; private String myStartPath = null; private String myEndPath = null; private String myPegPath = null; private long myStartRevision; private long myEndRevision; private long myPegRevision; private SVNNodeKind myKind; private ISVNEventHandler myEventHandler; private LocationsLogEntryHandler(String path, long startRevision, long endRevision, long pegRevision, SVNNodeKind kind, ISVNEventHandler eventHandler) { myCurrentPath = path; myStartRevision = startRevision; myEndRevision = endRevision; myPegRevision = pegRevision; myEventHandler = eventHandler; myKind = kind; } public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { if (myEventHandler != null) { myEventHandler.checkCancelled(); } if (logEntry.getChangedPaths() == null) { return; } if (myCurrentPath == null) { return; } if (myStartPath == null && logEntry.getRevision() <= myStartRevision) { myStartPath = myCurrentPath; } if (myEndPath == null && logEntry.getRevision() <= myEndRevision) { myEndPath = myCurrentPath; } if (myPegPath == null && logEntry.getRevision() <= myPegRevision) { myPegPath = myCurrentPath; } myCurrentPath = getPreviousLogPath(myCurrentPath, logEntry, myKind); } } protected static class RepositoryReference { public RepositoryReference(String url, long rev) { URL = url; Revision = rev; } public String URL; public long Revision; } protected static class SVNRepositoryLocation { private SVNURL myURL; private long myRevision; public SVNRepositoryLocation(SVNURL url, long rev) { myURL = url; myRevision = rev; } public long getRevisionNumber() { return myRevision; } public SVNURL getURL() { return myURL; } } }