/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.tmatesoft.svn.core.SVNCommitInfo; 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.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.admin.ISVNCleanupHandler; import org.tmatesoft.svn.core.internal.wc.admin.ISVNEntryHandler; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo; import org.tmatesoft.svn.core.internal.wc.admin.SVNChecksumInputStream; import org.tmatesoft.svn.core.internal.wc.admin.SVNChecksumOutputStream; import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; import org.tmatesoft.svn.core.internal.wc.admin.SVNLog; import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties; import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNConflictAction; import org.tmatesoft.svn.core.wc.SVNConflictReason; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNOperation; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNUpdateEditor implements ISVNUpdateEditor, ISVNCleanupHandler { private String mySwitchURL; private String myTarget; private String myTargetURL; private String myRootURL; private SVNAdminAreaInfo myAdminInfo; private SVNDirectoryInfo myCurrentDirectory; private SVNFileInfo myCurrentFile; private long myTargetRevision; private boolean myIsRootOpen; private boolean myIsTargetDeleted; private boolean myIsUnversionedObstructionsAllowed; private boolean myIsDepthSticky; // File objects private Collection mySkippedTrees; private Collection myDeletedTrees; private SVNWCAccess myWCAccess; private SVNDeltaProcessor myDeltaProcessor; private SVNDepth myRequestedDepth; private String[] myExtensionPatterns; private ISVNFileFetcher myFileFetcher; private boolean myIsLockOnDemand; private SVNUpdateEditor(SVNAdminAreaInfo info, String switchURL, boolean allowUnversionedObstructions, boolean depthIsSticky, SVNDepth depth, String[] preservedExtensions, String targetURL, String rootURL, ISVNFileFetcher fileFetcher, boolean lockOnDemand) { myAdminInfo = info; myWCAccess = info.getWCAccess(); myIsUnversionedObstructionsAllowed = allowUnversionedObstructions; myTarget = info.getTargetName(); mySwitchURL = switchURL; myTargetRevision = -1; myRequestedDepth = depth; myIsDepthSticky = depthIsSticky; myDeltaProcessor = new SVNDeltaProcessor(); myExtensionPatterns = preservedExtensions; myFileFetcher = fileFetcher; myTargetURL = targetURL; myRootURL = rootURL; myIsLockOnDemand = lockOnDemand; if (myTarget != null) { myTargetURL = SVNPathUtil.append(myTargetURL, SVNEncodingUtil.uriEncode(myTarget)); } if ("".equals(myTarget)) { myTarget = null; } } private Collection getSkippedTrees() { if (mySkippedTrees == null) { mySkippedTrees = new LinkedList(); } return mySkippedTrees; } private Collection getDeletedTrees() { if (myDeletedTrees == null) { myDeletedTrees = new LinkedList(); } return myDeletedTrees; } private void addSkippedTree(File path) { getSkippedTrees().add(path); } private void addDeletedTree(File path) { getDeletedTrees().add(path); } private boolean inSkippedTree(File path) { while (path != null && !path.equals(myAdminInfo.getAnchor().getRoot())) { if (getSkippedTrees().contains(path)) { return true; } path = path.getParentFile(); } return false; } private boolean inDeletedTree(File path, boolean includeRoot) { if (!includeRoot) { path = path.getParentFile(); } while (path != null && !path.equals(myAdminInfo.getAnchor().getRoot())) { if (getDeletedTrees().contains(path)) { return true; } path = path.getParentFile(); } return false; } public void targetRevision(long revision) throws SVNException { myTargetRevision = revision; } public long getTargetRevision() { return myTargetRevision; } public void openRoot(long revision) throws SVNException { myIsRootOpen = true; myCurrentDirectory = createDirectoryInfo(null, "", false); myWCAccess.registerCleanupHandler(myCurrentDirectory.getAdminArea(), myCurrentDirectory); if (myTarget == null) { SVNAdminArea adminArea = myCurrentDirectory.getAdminArea(); SVNEntry entry = adminArea.getEntry(adminArea.getThisDirName(), false); if (entry != null) { myCurrentDirectory.myAmbientDepth = entry.getDepth(); myCurrentDirectory.wasIncomplete = entry.isIncomplete(); myCurrentDirectory.myPreviousRevision = entry.getRevision(); } Map attributes = new SVNHashMap(); attributes.put(SVNProperty.REVISION, Long.toString(myTargetRevision)); attributes.put(SVNProperty.URL, myCurrentDirectory.URL); attributes.put(SVNProperty.INCOMPLETE, Boolean.TRUE.toString()); if (myRootURL != null && SVNPathUtil.isAncestor(myRootURL, myCurrentDirectory.URL)) { attributes.put(SVNProperty.REPOS, myRootURL); } adminArea.modifyEntry(adminArea.getThisDirName(), attributes, true, false); if (mySwitchURL != null) { clearWCProperty(myCurrentDirectory.getAdminArea(), null); } } else if (mySwitchURL != null) { if (myAdminInfo.getTarget() == myAdminInfo.getAnchor()) { clearWCProperty(myAdminInfo.getTarget(), myTarget); } else { clearWCProperty(myAdminInfo.getTarget(), null); } } } private void doDeleteEntry(String path, SVNAdminArea parentArea, SVNDirectoryInfo parent, SVNURL theirURL) throws SVNException { File fullPath = myAdminInfo.getAnchor().getFile(path); String name = SVNPathUtil.tail(path); SVNEntry entry = myWCAccess.getVersionedEntry(fullPath, true); parent.anyChangesInChildren = true; if (entry.getDepth() == SVNDepth.EXCLUDE) { parentArea.deleteEntry(name); parentArea.saveEntries(true); if (path.equals(myTarget)) { myIsTargetDeleted = true; } return; } SVNNodeKind kind = entry.getKind(); long previousRevision = entry.getRevision(); SVNURL url = entry.getSVNURL(); if (inSkippedTree(fullPath) && !inDeletedTree(fullPath, true)) { return; } File victim = alreadyInTreeConflict(fullPath); if (victim != null) { addSkippedTree(fullPath); SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, entry.getKind(), null, myTargetRevision, SVNEventAction.SKIP, SVNEventAction.UPDATE_DELETE, null, null); event.setPreviousRevision(previousRevision); event.setPreviousURL(url); myWCAccess.handleEvent(event); return; } SVNLog log = parent == null ? parentArea.getLog() : parent.getLog(); SVNTreeConflictDescription treeConflict; if (kind == SVNNodeKind.DIR && myWCAccess.isMissing(fullPath) && (entry.isScheduledForDeletion() || entry.isScheduledForReplacement())) { treeConflict = null; } else { treeConflict = checkTreeConflict(fullPath, entry, parentArea, log, SVNConflictAction.DELETE, SVNNodeKind.NONE, theirURL); } if (treeConflict != null) { addSkippedTree(fullPath); SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, entry.getKind(), null, myTargetRevision, SVNEventAction.TREE_CONFLICT, SVNEventAction.UPDATE_DELETE, null, null); event.setPreviousRevision(entry.getRevision()); event.setPreviousURL(entry.getSVNURL()); myWCAccess.handleEvent(event); if (treeConflict.getConflictReason() == SVNConflictReason.EDITED) { if (parent != null) { parent.flushLog(); parent.runLogs(); } else { if (log != null) { log.save(); parentArea.runLogs(); } } scheduleExistingEntryForReAdd(entry, fullPath, theirURL, true); return; } else if (treeConflict.getConflictReason() == SVNConflictReason.DELETED) { // The item does not exist locally (except perhaps as a skeleton // directory tree) because it was already scheduled for delete. // We must complete the deletion, leaving the tree conflict info // as the only difference from a normal deletion. // // Fall through to the normal "delete" code path. if (entry.isScheduledForReplacement()) { if (parent != null) { parent.flushLog(); parent.runLogs(); } else { if (log != null) { log.save(); parentArea.runLogs(); } } scheduleExistingEntryForReAdd(entry, fullPath, theirURL, false); return; } } else { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Unexpected tree conflict reason"); SVNErrorManager.error(error, SVNLogType.WC); } } log = parent == null ? parentArea.getLog() : parent.getLog(); SVNProperties attributes = new SVNProperties(); attributes.put(SVNLog.NAME_ATTR, name); log.addCommand(SVNLog.DELETE_ENTRY, attributes, false); attributes.clear(); if (path.equals(myTarget)) { attributes.put(SVNLog.NAME_ATTR, name); attributes.put(SVNProperty.shortPropertyName(SVNProperty.KIND), kind == SVNNodeKind.DIR ? SVNProperty.KIND_DIR : SVNProperty.KIND_FILE); attributes.put(SVNProperty.shortPropertyName(SVNProperty.REVISION), Long.toString(myTargetRevision)); attributes.put(SVNProperty.shortPropertyName(SVNProperty.DELETED), Boolean.TRUE.toString()); log.addCommand(SVNLog.MODIFY_ENTRY, attributes, false); myIsTargetDeleted = true; } try { if (parent != null) { parent.flushLog(); } else { if (log != null) { log.save(); } } } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Error writing log file for ''{0}''", parent.getPath()); SVNErrorManager.error(err, svne, SVNLogType.WC); } if (myIsLockOnDemand && kind == SVNNodeKind.DIR) { SVNAdminArea childArea = myWCAccess.getAdminArea(parentArea.getFile(name)); if (childArea != null && !childArea.isLocked()) { childArea.lock(false); } } if (mySwitchURL != null && kind == SVNNodeKind.DIR) { SVNAdminArea childArea = myWCAccess.retrieve(parentArea.getFile(name)); if (childArea != null) { try { childArea.removeFromRevisionControl(childArea.getThisDirName(), true, false); } catch (SVNException svne) { handleLeftLocalModificationsError(svne); } } } try { if (parent != null) { parent.runLogs(); } else { parentArea.runLogs(); } } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Error running log file for ''{0}''", parentArea.getRoot()); SVNErrorManager.error(err, svne, SVNLogType.WC); } if (treeConflict == null && !inDeletedTree(fullPath, true)) { SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, kind, null, myTargetRevision, SVNEventAction.UPDATE_DELETE, null, null, null); event.setPreviousRevision(previousRevision); event.setPreviousURL(url); myWCAccess.handleEvent(event); } } private File alreadyInTreeConflict(File path) throws SVNException { File ancestor = path; List ancestors = new ArrayList(); SVNWCAccess access = SVNWCAccess.newInstance(myWCAccess); try { access.probeOpen(ancestor, false, 0); SVNEntry entry = access.getEntry(path, true); if (entry != null) { ancestors.add(ancestor); } } finally { access.close(); } ancestor = ancestor.getParentFile(); access = SVNWCAccess.newInstance(myWCAccess); try { while (ancestor != null) { SVNAdminArea adminArea = access.probeOpen(ancestor, false, 0); if (adminArea == null) { break; } boolean isWCRoot = access.isWCRoot(ancestor); if (isWCRoot) { break; } ancestors.add(ancestor); ancestor = ancestor.getParentFile(); } } finally { access.close(); } for (int i = ancestors.size() - 1; i >= 0; i--) { ancestor = (File) ancestors.get(i); SVNTreeConflictDescription treeConflict = access.getTreeConflict(ancestor); if (treeConflict != null) { return ancestor; } } return null; } private SVNTreeConflictDescription checkTreeConflict(File path, SVNEntry entry, SVNAdminArea parentArea, SVNLog log, SVNConflictAction action, SVNNodeKind theirKind, SVNURL theirURL) throws SVNException { boolean allModsAreDeletes = false; boolean isSubtreeOfLocallyDeleted = inDeletedTree(path, false); SVNConflictReason reason = null; if (action == SVNConflictAction.EDIT) { if ((entry.isScheduledForDeletion() || entry.isScheduledForReplacement()) && !isSubtreeOfLocallyDeleted) { reason = SVNConflictReason.DELETED; } } else if (action == SVNConflictAction.ADD) { if (entry != null && entry.getExternalFilePath() == null) { reason = SVNConflictReason.ADDED; } } else if (action == SVNConflictAction.DELETE) { if (entry.isScheduledForDeletion() || entry.isScheduledForReplacement()) { if (!isSubtreeOfLocallyDeleted) { reason = SVNConflictReason.DELETED; } } else { boolean modified = false; if (entry.isFile()) { modified = entryHasLocalModifications(parentArea, path, SVNNodeKind.FILE, entry.getSchedule()); if (entry.isScheduledForDeletion()) { allModsAreDeletes = true; } } else if (entry.isDirectory()) { SVNAdminArea adminArea = myWCAccess.probeRetrieve(path); if (adminArea.getRoot().equals(path)) { boolean[] allEditsAreDeletes = new boolean[1]; modified = treeHasLocalModifications(adminArea, allEditsAreDeletes); allModsAreDeletes = allEditsAreDeletes[0]; } } if (modified) { if (allModsAreDeletes) { reason = SVNConflictReason.DELETED; } else { reason = SVNConflictReason.EDITED; } } } } if (reason != null) { SVNNodeKind leftKind = entry.isScheduledForAddition() ? SVNNodeKind.NONE : entry.isScheduledForDeletion() ? SVNNodeKind.UNKNOWN : entry.getKind(); SVNURL repoRoot = entry.getRepositoryRootURL(); String repoPath = SVNPathUtil.getPathAsChild(repoRoot.getPath(), entry.getSVNURL().getPath()); repoPath = repoPath != null ? repoPath : "/"; SVNConflictVersion srcLeftVersion = new SVNConflictVersion(repoRoot, repoPath, entry.getRevision(), leftKind); if (mySwitchURL != null) { if (theirURL != null) { repoPath = SVNPathUtil.getPathAsChild(repoRoot.getPath(), theirURL.getPath()); } else { SVNURL switchURL = SVNURL.parseURIEncoded(mySwitchURL); repoPath = SVNPathUtil.getPathAsChild(repoRoot.getPath(), switchURL.getPath()); } } SVNConflictVersion srcRightVersion = new SVNConflictVersion(repoRoot, repoPath, myTargetRevision, theirKind); SVNTreeConflictDescription treeConflict = new SVNTreeConflictDescription(path, entry.getKind(), action, reason, mySwitchURL != null ? SVNOperation.SWITCH : SVNOperation.UPDATE, srcLeftVersion, srcRightVersion); SVNProperties command = getTreeConflictCreationAttributes(parentArea, treeConflict); log.addCommand(SVNLog.ADD_TREE_CONFLICT, command, false); return treeConflict; } return null; } public boolean treeHasLocalModifications(SVNAdminArea adminArea, final boolean[] allModsAreDeletes) throws SVNException { final boolean[] modified = new boolean[] { false }; if (allModsAreDeletes != null) { allModsAreDeletes[0] = true; } final ISVNEntryHandler entryHandler = new ISVNEntryHandler() { public void handleEntry(File path, SVNEntry entry) throws SVNException { boolean hasModifications; SVNAdminArea entryArea; try { entryArea = myWCAccess.probeRetrieve(path); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { ISVNEventHandler eventHandler = myWCAccess.getEventHandler(); myWCAccess.setEventHandler(null); try { entryArea = myWCAccess.open(path, false, -1); } finally { myWCAccess.setEventHandler(eventHandler); } } else { throw e; } } hasModifications = entryHasLocalModifications(entryArea, path, entry.getKind(), entry.getSchedule()); if (hasModifications) { modified[0] = true; if (!entry.isScheduledForDeletion()) { allModsAreDeletes[0] = false; } } } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } }; adminArea.walkThisDirectory(entryHandler, false, SVNDepth.INFINITY); return modified[0]; } private boolean entryHasLocalModifications(SVNAdminArea adminArea, File path, SVNNodeKind kind, String schedule) throws SVNException { boolean modified; if (schedule != null) { modified = true; } else { boolean textModified = false; if (kind == SVNNodeKind.FILE) { textModified = adminArea.hasTextModifications(path.getName(), false); } boolean propsModified; if (kind == SVNNodeKind.FILE) { propsModified = adminArea.hasPropModifications(path.getName()); } else if (path.equals(adminArea.getRoot())) { propsModified = adminArea.hasPropModifications(adminArea.getThisDirName()); } else { SVNAdminArea tmpArea = myWCAccess.probeRetrieve(path); if (tmpArea.getRoot().equals(path)) { propsModified = tmpArea.hasPropModifications(tmpArea.getThisDirName()); } else { propsModified = tmpArea.hasPropConflict(path.getName()); } } modified = textModified || propsModified; } return modified; } private void scheduleExistingEntryForReAdd(final SVNEntry entry, final File path, SVNURL theirURL, boolean modifyCopyFrom) throws SVNException { final File parentPath = path.getParentFile(); String entryName = path.getName(); Map attributes = new SVNHashMap(); attributes.put(SVNProperty.URL, theirURL.toString()); attributes.put(SVNProperty.SCHEDULE, SVNProperty.SCHEDULE_ADD); if (modifyCopyFrom) { attributes.put(SVNProperty.COPYFROM_URL, entry.getURL()); attributes.put(SVNProperty.COPYFROM_REVISION, String.valueOf(entry.getRevision())); attributes.put(SVNProperty.COPIED, Boolean.TRUE.toString()); } if (myIsLockOnDemand && entry.isDirectory()) { SVNAdminArea area = myWCAccess.getAdminArea(path); if (area != null && !area.isLocked()) { area.lock(false); } } final SVNAdminArea adminArea = myWCAccess.retrieve(entry.isDirectory() ? path : parentPath); adminArea.modifyEntry(entry.isDirectory() ? adminArea.getThisDirName() : entryName, attributes, true, true); if (entry.isDirectory()) { ISVNEntryHandler entryHandler = new ISVNEntryHandler() { public void handleEntry(File ePath, SVNEntry e) throws SVNException { if (!path.equals(ePath)) { SVNAdminArea eArea; if (myIsLockOnDemand && e.isDirectory() && !adminArea.getThisDirName().equals(e.getName())) { SVNAdminArea childArea = myWCAccess.getAdminArea(ePath); if (childArea != null && !childArea.isLocked()) { childArea.lock(false); } } if (adminArea.getThisDirName().equals(e.getName())) { eArea = myWCAccess.retrieve(ePath); } else { eArea = myWCAccess.retrieve(ePath.getParentFile()); } if (e.getSchedule() == null) { Map eAttrs = new SVNHashMap(); eAttrs.put(SVNProperty.COPIED, Boolean.TRUE.toString()); eArea.modifyEntry(e.getName(), eAttrs, true, false); } } } public void handleError(File path, SVNErrorMessage error) throws SVNException { SVNErrorManager.error(error, SVNLogType.WC); } }; adminArea.walkThisDirectory(entryHandler, false, SVNDepth.INFINITY); SVNAdminArea parentArea = myWCAccess.retrieve(parentPath); parentArea.getVersionedEntry(parentArea.getThisDirName(), true); parentArea.modifyEntry(entryName, attributes, true, true); } } public void deleteEntry(String path, long revision) throws SVNException { checkIfPathIsUnderRoot(path); String name = SVNPathUtil.tail(path); SVNURL url = SVNURL.parseURIEncoded(myCurrentDirectory.URL); SVNURL theirURL = url.appendPath(name, false); checkIfPathIsUnderRoot(path); doDeleteEntry(path, myCurrentDirectory.getAdminArea(), myCurrentDirectory, theirURL); } private void handleLeftLocalModificationsError(SVNException originalError) throws SVNException { SVNException error = null; for (error = originalError; error != null;) { if (error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) { break; } error = (error.getCause() instanceof SVNException) ? (SVNException) error.getCause() : null; } if (error != null) { return; } throw originalError; } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { File fullPath = myAdminInfo.getAnchor().getFile(path); String name = SVNPathUtil.tail(path); boolean isLocallyDeleted = inDeletedTree(fullPath, true); SVNAdminArea parentArea = myCurrentDirectory.getAdminArea(); SVNDirectoryInfo parentDirectory = myCurrentDirectory; myCurrentDirectory = createDirectoryInfo(myCurrentDirectory, path, true); myCurrentDirectory.myPreviousRevision = -1; if (path.equals(myTarget)) { myCurrentDirectory.myAmbientDepth = myRequestedDepth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : myRequestedDepth; } else if (myRequestedDepth == SVNDepth.IMMEDIATES || (myRequestedDepth == SVNDepth.UNKNOWN && parentDirectory.myAmbientDepth == SVNDepth.IMMEDIATES)) { myCurrentDirectory.myAmbientDepth = SVNDepth.EMPTY; } else { myCurrentDirectory.myAmbientDepth = SVNDepth.INFINITY; } parentDirectory.flushLog(); checkIfPathIsUnderRoot(path); if (inSkippedTree(fullPath) && !isLocallyDeleted) { return; } File victim = alreadyInTreeConflict(fullPath); if (victim != null) { addSkippedTree(fullPath); SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.DIR, null, myTargetRevision, SVNEventAction.SKIP, SVNEventAction.UPDATE_ADD, null, null); myWCAccess.handleEvent(event); } SVNFileType kind = SVNFileType.getType(fullPath); if (kind == SVNFileType.FILE || kind == SVNFileType.UNKNOWN) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add directory ''{0}'': a non-directory object of the same name already exists", path); SVNErrorManager.error(err, SVNLogType.WC); } else if (kind == SVNFileType.NONE) { SVNEntry entryInParent = parentArea.getEntry(name, false); if (entryInParent != null && entryInParent.isDirectory() && entryInParent.isScheduledForDeletion()) { addDeletedTree(fullPath); isLocallyDeleted = true; } } if (kind == SVNFileType.DIRECTORY) { SVNAdminArea adminArea = null; try { adminArea = SVNWCAccess.newInstance(null).open(fullPath, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) { throw svne; } if (myIsUnversionedObstructionsAllowed) { myCurrentDirectory.isExisted = true; } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add directory ''{0}'': an unversioned directory of the same name already exists", myCurrentDirectory.getPath()); SVNErrorManager.error(err, SVNLogType.WC); } } if (adminArea != null) { try { SVNEntry entry = adminArea.getEntry(adminArea.getThisDirName(), false); SVNEntry parentEntry = parentArea.getEntry(parentArea.getThisDirName(), false); SVNEntry entryInParent = (SVNEntry) parentArea.getEntries().get(name); if (entry != null && parentEntry != null && entry.getUUID() != null && parentEntry.getUUID() != null && !entry.getUUID().equals(parentEntry.getUUID())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "UUID mismatch: existing directory ''{0}'' was checked out from a different repository", myCurrentDirectory.getPath()); SVNErrorManager.error(err, SVNLogType.WC); } if (entry != null && mySwitchURL == null && myCurrentDirectory.URL != null && !myCurrentDirectory.URL.equals(entry.getURL())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "URL ''{0}'' of existing directory ''{1}'' does not match expected URL ''{2}''", new Object[] { entry.getURL(), myCurrentDirectory.getPath(), myCurrentDirectory.URL }); SVNErrorManager.error(err, SVNLogType.WC); } if (entryInParent == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add directory ''{0}'': a versioned directory of the same name already exists", myCurrentDirectory.getPath()); SVNErrorManager.error(err, SVNLogType.WC); } if (entry != null && (entry.isScheduledForAddition() || entry.isScheduledForReplacement()) && !entry.isCopied()) { myCurrentDirectory.isAddExisted = true; } else { SVNURL theirURL = SVNURL.parseURIEncoded(myCurrentDirectory.URL); SVNTreeConflictDescription treeConflict = checkTreeConflict(fullPath, entry, parentArea, parentDirectory.getLog(), SVNConflictAction.ADD, SVNNodeKind.DIR, theirURL); try { parentDirectory.flushLog(); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Error writing log file for ''{0}''", parentDirectory.getPath()); SVNErrorManager.error(err, svne, SVNLogType.WC); } if (treeConflict != null) { addSkippedTree(fullPath); SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.DIR, null, myTargetRevision, SVNEventAction.TREE_CONFLICT, SVNEventAction.UPDATE_ADD, null, null); if (entry != null) { event.setPreviousRevision(entry.getRevision()); event.setPreviousURL(entry.getSVNURL()); } myWCAccess.handleEvent(event); return; } } } finally { adminArea.getWCAccess().close(); } } } if (SVNFileUtil.getAdminDirectoryName().equals(name)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add directory ''{0}'': object of the same name as the administrative directory", path); SVNErrorManager.error(err, SVNLogType.WC); } if (copyFromPath != null || SVNRevision.isValidRevisionNumber(copyFromRevision)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Failed to add directory ''{0}'': copyfrom arguments not yet supported", path); SVNErrorManager.error(err, SVNLogType.WC); } SVNEntry entry = parentArea.getEntry(name, false); Map attributes = new SVNHashMap(); attributes.put(SVNProperty.KIND, SVNProperty.KIND_DIR); attributes.put(SVNProperty.ABSENT, null); attributes.put(SVNProperty.DELETED, null); boolean force = false; if (myCurrentDirectory.isAddExisted) { attributes.put(SVNProperty.SCHEDULE, null); force = true; } entry = parentArea.modifyEntry(name, attributes, true, force); if (myCurrentDirectory.isAddExisted) { attributes.put(SVNProperty.REVISION, Long.toString(myTargetRevision)); if (mySwitchURL != null) { attributes.put(SVNProperty.URL, myCurrentDirectory.URL); } SVNAdminArea adminArea = myCurrentDirectory.getAdminArea(); adminArea.modifyEntry(adminArea.getThisDirName(), attributes, true, true); } String rootURL = null; if (myRootURL != null && SVNPathUtil.isAncestor(myRootURL, myCurrentDirectory.URL)) { rootURL = myRootURL; } SVNWCManager.ensureAdminAreaExists(fullPath, myCurrentDirectory.URL, rootURL, null, myTargetRevision, myCurrentDirectory.myAmbientDepth); SVNAdminArea childArea = null; if (!myAdminInfo.getAnchor().getRoot().equals(fullPath)) { ISVNEventHandler eventHandler = myWCAccess.getEventHandler(); try { myWCAccess.setEventHandler(null); childArea = myWCAccess.open(fullPath, true, 0); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LOCKED) { childArea = myWCAccess.retrieve(fullPath); } else { throw e; } } finally { myWCAccess.setEventHandler(eventHandler); } } if (isLocallyDeleted) { attributes.clear(); attributes.put(SVNProperty.SCHEDULE, SVNProperty.SCHEDULE_DELETE); parentArea.modifyEntry(name, attributes, true, false); childArea = myWCAccess.retrieve(fullPath); childArea.modifyEntry(childArea.getThisDirName(), attributes, true, false); } try { childArea = myWCAccess.open(fullPath, true, 0); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LOCKED) { childArea = myWCAccess.retrieve(fullPath); } else { throw e; } } if (childArea != null) { myWCAccess.registerCleanupHandler(childArea, myCurrentDirectory); } if (!myCurrentDirectory.isAddExisted && !isLocallyDeleted) { SVNEvent event = SVNEventFactory.createSVNEvent(parentArea.getFile(entry.getName()), SVNNodeKind.DIR, null, myTargetRevision, myCurrentDirectory.isExisted ? SVNEventAction.UPDATE_EXISTS : SVNEventAction.UPDATE_ADD, null, null, null); event.setPreviousRevision(myCurrentDirectory.myPreviousRevision); event.setPreviousURL(entry.getSVNURL()); event.setURL(myCurrentDirectory.URL != null ? SVNURL.parseURIEncoded(myCurrentDirectory.URL) : null); myWCAccess.handleEvent(event); } } public void openDir(String path, long revision) throws SVNException { myCurrentDirectory.flushLog(); checkIfPathIsUnderRoot(path); SVNDirectoryInfo parentInfo = myCurrentDirectory; myCurrentDirectory = createDirectoryInfo(myCurrentDirectory, path, false); SVNAdminArea adminArea = myCurrentDirectory.getAdminArea(); myWCAccess.registerCleanupHandler(adminArea, myCurrentDirectory); SVNEntry entry = adminArea.getEntry(adminArea.getThisDirName(), true); if (entry != null) { myCurrentDirectory.myPreviousRevision = entry.getRevision(); myCurrentDirectory.myAmbientDepth = entry.getDepth(); myCurrentDirectory.wasIncomplete = entry.isIncomplete(); } else { myCurrentDirectory.myPreviousRevision = -1; } File fullPath = myAdminInfo.getAnchor().getFile(path); if (inSkippedTree(fullPath) && !inDeletedTree(fullPath, true)) { myCurrentDirectory.isSkipped = true; return; } SVNTreeConflictDescription treeConflict; File victim = alreadyInTreeConflict(fullPath); if (victim != null) { treeConflict = null; } else { //originally there were no tree conflict SVNURL theirURL = SVNURL.parseURIEncoded(myCurrentDirectory.URL); treeConflict = checkTreeConflict(fullPath, entry, parentInfo.getAdminArea(), parentInfo.getLog(), SVNConflictAction.EDIT, SVNNodeKind.DIR, theirURL); if (treeConflict != null && treeConflict.getConflictAction() == SVNConflictAction.EDIT) { //now tree conflict is created myCurrentDirectory.treeConflictCreationAttributes = getTreeConflictCreationAttributes(parentInfo.getAdminArea(), treeConflict); } } if (treeConflict != null && treeConflict.getConflictReason() == SVNConflictReason.DELETED && !inDeletedTree(fullPath, true)) { addDeletedTree(fullPath); } boolean hasPropConflicts = adminArea.hasPropConflict(adminArea.getThisDirName()); if (victim != null || treeConflict != null || hasPropConflicts) { if (!inDeletedTree(fullPath, true)) { myCurrentDirectory.isSkipped = true; } addSkippedTree(fullPath); if (!inDeletedTree(fullPath, false)) { SVNEventAction eventAction = hasPropConflicts ? SVNEventAction.SKIP : SVNEventAction.TREE_CONFLICT; SVNStatusType propStatus = hasPropConflicts ? SVNStatusType.CONFLICTED : null; SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.DIR, null, myTargetRevision, null, propStatus, null, eventAction, SVNEventAction.UPDATE_UPDATE, null, null); event.setPreviousRevision(myCurrentDirectory.myPreviousRevision); if (myCurrentDirectory.URL != null) { event.setURL(SVNURL.parseURIEncoded(myCurrentDirectory.URL)); } if (entry != null) { event.setPreviousURL(entry.getSVNURL()); } myWCAccess.handleEvent(event); } if (hasPropConflicts || (treeConflict != null && treeConflict.getConflictReason() != SVNConflictReason.DELETED)) { return; } } Map attributes = new SVNHashMap(); attributes.put(SVNProperty.REVISION, Long.toString(myTargetRevision)); attributes.put(SVNProperty.URL, myCurrentDirectory.URL); attributes.put(SVNProperty.INCOMPLETE, Boolean.TRUE.toString()); if (myRootURL != null && SVNPathUtil.isAncestor(myRootURL, myCurrentDirectory.URL)) { attributes.put(SVNProperty.REPOS, myRootURL); } entry = adminArea.modifyEntry(adminArea.getThisDirName(), attributes, true, false); } public void absentDir(String path) throws SVNException { absentEntry(path, SVNNodeKind.DIR); } public void absentFile(String path) throws SVNException { absentEntry(path, SVNNodeKind.FILE); } private void absentEntry(String path, SVNNodeKind kind) throws SVNException { String name = SVNPathUtil.tail(path); SVNAdminArea adminArea = myCurrentDirectory.getAdminArea(); SVNEntry entry = adminArea.getEntry(name, false); if (entry != null && entry.isScheduledForAddition()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to mark ''{0}'' absent: item of the same name is already scheduled for addition", path); SVNErrorManager.error(err, SVNLogType.WC); } Map attributes = new SVNHashMap(); attributes.put(SVNProperty.REVISION, Long.toString(myTargetRevision)); attributes.put(SVNProperty.KIND, kind.toString()); attributes.put(SVNProperty.DELETED, null); attributes.put(SVNProperty.ABSENT, Boolean.TRUE.toString()); entry = adminArea.modifyEntry(name, attributes, true, false); } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { if (!myCurrentDirectory.isSkipped) { myCurrentDirectory.propertyChanged(name, value); } } private void clearWCProperty(SVNAdminArea adminArea, String target) throws SVNException { if (adminArea == null) { return; } for (Iterator ents = adminArea.entries(false); ents.hasNext();) { SVNEntry entry = (SVNEntry) ents.next(); if (target != null) { if (entry.isFile() && target.equals(entry.getName())) { SVNVersionedProperties props = adminArea.getWCProperties(entry.getName()); props.setPropertyValue(SVNProperty.WC_URL, null); adminArea.saveWCProperties(false); } continue; } if (entry.isFile() || adminArea.getThisDirName().equals(entry.getName())) { SVNVersionedProperties props = adminArea.getWCProperties(entry.getName()); props.setPropertyValue(SVNProperty.WC_URL, null); adminArea.saveWCProperties(false); } else { SVNAdminArea childArea = myAdminInfo.getWCAccess().getAdminArea(adminArea.getFile(entry.getName())); clearWCProperty(childArea, null); } } } public void closeDir() throws SVNException { File fullPath = myAdminInfo.getAnchor().getFile(myCurrentDirectory.getPath()); if (inSkippedTree(fullPath) && !inDeletedTree(fullPath, true)) { maybeBumpDirInfo(myCurrentDirectory); myCurrentDirectory = myCurrentDirectory.Parent; return; } SVNProperties modifiedWCProps = myCurrentDirectory.getChangedWCProperties(); SVNProperties modifiedEntryProps = myCurrentDirectory.getChangedEntryProperties(); SVNProperties modifiedProps = myCurrentDirectory.getChangedProperties(); if (!myCurrentDirectory.anyChangesInChildren && (modifiedProps == null || modifiedProps.isEmpty())) { // a workaround for SVN issue 3525 on the client side // a situation is possible that openDir/openFile/closeFile/closeDir invocation describes no real changes // (except maybe entry- and wc- properties) // in this case we should make sure no tree conflict is created if (myCurrentDirectory.treeConflictCreationAttributes != null) { SVNDirectoryInfo parentDirectoryInfo = myCurrentDirectory.Parent; if (parentDirectoryInfo != null) { SVNLog log = parentDirectoryInfo.getLog(); log.deleteCommandsByNameAndAttributes(SVNLog.ADD_TREE_CONFLICT, myCurrentDirectory.treeConflictCreationAttributes, false); } } } else { SVNDirectoryInfo parentDirectoryInfo = myCurrentDirectory.Parent; if (parentDirectoryInfo != null) { parentDirectoryInfo.anyChangesInChildren = true; } } SVNStatusType propStatus = SVNStatusType.UNKNOWN; SVNAdminArea adminArea = myCurrentDirectory.getAdminArea(); if (myCurrentDirectory.wasIncomplete) { // delete all props. SVNVersionedProperties oldBaseProps = adminArea.getBaseProperties(adminArea.getThisDirName()); SVNProperties baseMap = oldBaseProps.asMap(); if (modifiedProps == null) { modifiedProps = new SVNProperties(); } for (Iterator names = baseMap.nameSet().iterator(); names.hasNext();) { String name = (String) names.next(); if (!modifiedProps.containsName(name)) { modifiedProps.put(name, SVNPropertyValue.create(null)); } } } if (modifiedWCProps != null || modifiedEntryProps != null || modifiedProps != null) { SVNLog log = myCurrentDirectory.getLog(); if (modifiedProps != null && !modifiedProps.isEmpty()) { if (modifiedProps.containsName(SVNProperty.EXTERNALS)) { String oldExternal = adminArea.getProperties(adminArea.getThisDirName()).getStringPropertyValue(SVNProperty.EXTERNALS); String newExternal = modifiedProps.getStringValue(SVNProperty.EXTERNALS); String path = myCurrentDirectory.getPath(); if (oldExternal == null && newExternal != null) { myAdminInfo.addExternal(path, oldExternal, newExternal); myAdminInfo.addDepth(path, myCurrentDirectory.myAmbientDepth); } else if (oldExternal != null && newExternal == null) { myAdminInfo.addExternal(path, oldExternal, newExternal); myAdminInfo.addDepth(path, myCurrentDirectory.myAmbientDepth); } else if (oldExternal != null && !oldExternal.equals(newExternal)) { myAdminInfo.addExternal(path, oldExternal, newExternal); myAdminInfo.addDepth(path, myCurrentDirectory.myAmbientDepth); } } SVNVersionedProperties oldBaseProps = adminArea.getBaseProperties(adminArea.getThisDirName()); try { propStatus = adminArea.mergeProperties(adminArea.getThisDirName(), oldBaseProps.asMap(), modifiedProps, null, null, true, false, log); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Couldn't do property merge"); SVNErrorManager.error(err, svne, SVNLogType.WC); } } log.logChangedEntryProperties(adminArea.getThisDirName(), modifiedEntryProps); log.logChangedWCProperties(adminArea.getThisDirName(), modifiedWCProps); } myCurrentDirectory.flushLog(); myCurrentDirectory.runLogs(); maybeBumpDirInfo(myCurrentDirectory); handleDuplicateEntries(); if (!myCurrentDirectory.isSkipped && (myCurrentDirectory.isAddExisted || !myCurrentDirectory.IsAdded) && !inDeletedTree(fullPath, true)) { if (!(adminArea == myAdminInfo.getAnchor() && !"".equals(myAdminInfo.getTargetName()))) { // skip event for anchor when there is a target. SVNEventAction action = myCurrentDirectory.isAddExisted || myCurrentDirectory.isExisted ? SVNEventAction.UPDATE_EXISTS : SVNEventAction.UPDATE_UPDATE; if (propStatus == SVNStatusType.UNKNOWN && action != SVNEventAction.UPDATE_EXISTS) { action = SVNEventAction.UPDATE_NONE; } SVNEvent event = SVNEventFactory.createSVNEvent(adminArea.getRoot(), SVNNodeKind.DIR, null, myTargetRevision, SVNStatusType.UNKNOWN, propStatus, null, action, null, null, null); event.setPreviousRevision(myCurrentDirectory.myPreviousRevision); event.setURL(myCurrentDirectory.URL != null ? SVNURL.parseURIEncoded(myCurrentDirectory.URL) : null); myWCAccess.handleEvent(event); } } myCurrentDirectory = myCurrentDirectory.Parent; } private void handleDuplicateEntries() throws SVNException { if (!SVNFileUtil.isCaseInsensitiveFS()) { return; } SVNAdminArea area = myCurrentDirectory.getAdminArea(); Map entries = area.getEntries(); Set duplicates = new HashSet(); Set entryNames = new HashSet(); for (Iterator names = entries.keySet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNEntry entry = (SVNEntry) entries.get(name); if (entry == null || entry.getKind() != SVNNodeKind.FILE || entry.isHidden()) { continue; } name = name.toLowerCase(); if (!duplicates.add(name)) { entryNames.add(name); } } if (entryNames.isEmpty()) { return; } SVNLog log = null; for (Iterator names = entries.keySet().iterator(); names.hasNext();) { myCurrentDirectory.flushLog(); log = myCurrentDirectory.getLog(); String name = (String) names.next(); SVNEntry entry = (SVNEntry) entries.get(name); if (entry == null || entry.getKind() != SVNNodeKind.FILE || entry.isHidden()) { continue; } if (entryNames.contains(name.toLowerCase())) { SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, name); command.put(SVNProperty.shortPropertyName(SVNProperty.ABSENT), Boolean.TRUE.toString()); command.put(SVNProperty.shortPropertyName(SVNProperty.CHECKSUM), "nameconflict"); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getTextBasePath(name, false)); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getTextRevertPath(name, false)); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getPropBasePath(name, SVNNodeKind.FILE, false)); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getPropPath(name, SVNNodeKind.FILE, false)); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getPropRevertPath(name, SVNNodeKind.FILE, false)); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, entry.getName()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); SVNEvent event = SVNEventFactory.createSVNEvent(area.getFile(name), SVNNodeKind.FILE, null, myTargetRevision, SVNEventAction.UPDATE_DELETE, null, null, null); myWCAccess.handleEvent(event); } } myCurrentDirectory.flushLog(); myCurrentDirectory.runLogs(); } public SVNCommitInfo closeEdit() throws SVNException { if (myTarget != null && myWCAccess.isMissing(myAdminInfo.getAnchor().getFile(myTarget))) { myCurrentDirectory = createDirectoryInfo(null, "", false); myWCAccess.registerCleanupHandler(myCurrentDirectory.getAdminArea(), myCurrentDirectory); doDeleteEntry(myTarget, myCurrentDirectory.getAdminArea(), myCurrentDirectory, null); } if (!myIsRootOpen) { if (myCurrentDirectory == null) { myCurrentDirectory = createDirectoryInfo(null, "", false); } completeDirectory(myCurrentDirectory); } if (!myIsTargetDeleted) { File targetFile = myTarget != null ? myAdminInfo.getAnchor().getFile(myTarget) : myAdminInfo.getAnchor().getRoot(); getSkippedTrees().removeAll(getDeletedTrees()); SVNWCManager.updateCleanup(targetFile, myWCAccess, mySwitchURL, myRootURL, myTargetRevision, true, getSkippedTrees(), myRequestedDepth, myIsLockOnDemand); } return null; } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { myCurrentFile = addFile(myCurrentDirectory, path, copyFromPath, copyFromRevision); } public void openFile(String path, long revision) throws SVNException { myCurrentFile = openFile(path, myCurrentDirectory); } public void changeFileProperty(String commitPath, String name, SVNPropertyValue value) throws SVNException { changeFileProperty(name, value, myCurrentFile); } public void applyTextDelta(String commitPath, String baseChecksum) throws SVNException { if (myCurrentFile.isSkipped) { return; } myCurrentFile.receivedTextDelta = true; SVNAdminArea adminArea = myCurrentFile.getAdminArea(); SVNEntry entry = adminArea.getEntry(myCurrentFile.name, false); boolean replaced = entry != null && entry.isScheduledForReplacement(); boolean useRevertBase = replaced; if (useRevertBase) { myCurrentFile.baseFile = adminArea.getFile(SVNAdminUtil.getTextRevertPath(myCurrentFile.name, false)); myCurrentFile.newBaseFile = adminArea.getFile(SVNAdminUtil.getTextRevertPath(myCurrentFile.name, true)); } else { myCurrentFile.baseFile = adminArea.getBaseFile(myCurrentFile.name, false); myCurrentFile.newBaseFile = adminArea.getBaseFile(myCurrentFile.name, true); } String checksum = entry != null ? entry.getChecksum() : null; if (!replaced && baseChecksum != null && checksum != null && !baseChecksum.equals(checksum)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT_TEXT_BASE, "Checksum mismatch for ''{0}''; expected: ''{1}'', recorded: ''{2}''", new Object[] { myCurrentFile.baseFile, baseChecksum, checksum }); SVNErrorManager.error(err, SVNLogType.WC); } File baseSrcFile = null; if (!myCurrentFile.IsAdded) { baseSrcFile = myCurrentFile.baseFile; } else { if (myCurrentFile.copiedBaseText != null) { baseSrcFile = myCurrentFile.copiedBaseText; } } if (replaced || checksum == null) { checksum = baseChecksum; } File baseTmpFile = myCurrentFile.newBaseFile; if (checksum != null) { myCurrentFile.expectedSrcChecksum = checksum; InputStream baseIS = baseSrcFile != null && baseSrcFile.exists() ? SVNFileUtil.openFileForReading(baseSrcFile) : SVNFileUtil.DUMMY_IN; myCurrentFile.sourceChecksumStream = baseIS != SVNFileUtil.DUMMY_IN ? new SVNChecksumInputStream(baseIS, SVNChecksumInputStream.MD5_ALGORITHM) : null; myDeltaProcessor.applyTextDelta(myCurrentFile.sourceChecksumStream != null ? myCurrentFile.sourceChecksumStream : baseIS, baseTmpFile, true); } else { myDeltaProcessor.applyTextDelta(baseSrcFile, baseTmpFile, true); } } public OutputStream textDeltaChunk(String commitPath, SVNDiffWindow diffWindow) throws SVNException { if (!myCurrentFile.isSkipped) { try { myDeltaProcessor.textDeltaChunk(diffWindow); } catch (SVNException svne) { myDeltaProcessor.textDeltaEnd(); SVNFileUtil.deleteFile(myCurrentFile.newBaseFile); myCurrentFile.newBaseFile = null; throw svne; } } return SVNFileUtil.DUMMY_OUT; } public void textDeltaEnd(String commitPath) throws SVNException { if (!myCurrentFile.isSkipped) { myCurrentFile.checksum = myDeltaProcessor.textDeltaEnd(); } if (myCurrentFile.expectedSrcChecksum != null) { String actualSourceChecksum = myCurrentFile.sourceChecksumStream != null ? myCurrentFile.sourceChecksumStream.getDigest() : null; if (!myCurrentFile.expectedSrcChecksum.equals(actualSourceChecksum)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT_TEXT_BASE, "Checksum mismatch while updating ''{0}''; expected: ''{1}'', actual: ''{2}''", new Object[] { myCurrentFile.baseFile, myCurrentFile.expectedSrcChecksum, actualSourceChecksum }); SVNErrorManager.error(err, SVNLogType.WC); } } } public void closeFile(String commitPath, String textChecksum) throws SVNException { closeFile(textChecksum, myCurrentFile, myCurrentDirectory); myCurrentFile = null; } public void abortEdit() throws SVNException { } private void checkIfPathIsUnderRoot(String path) throws SVNException { if (SVNFileUtil.isWindows && path != null) { String testPath = path.replace(File.separatorChar, '/'); int ind = -1; while (testPath.length() > 0 && (ind = testPath.indexOf("..")) != -1) { if (ind == 0 || testPath.charAt(ind - 1) == '/') { int i; for (i = ind + 2; i < testPath.length(); i++) { if (testPath.charAt(i) == '.') { continue; } else if (testPath.charAt(i) == '/') { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Path ''{0}'' is not in the working copy", path); SVNErrorManager.error(err, SVNLogType.WC); } else { break; } } if (i == testPath.length()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Path ''{0}'' is not in the working copy", path); SVNErrorManager.error(err, SVNLogType.WC); } testPath = testPath.substring(i); } else { testPath = testPath.substring(ind + 2); } } } } private void maybeBumpDirInfo(SVNDirectoryInfo dirInfo) throws SVNException { while (dirInfo != null) { dirInfo.RefCount--; if (dirInfo.RefCount > 0) { return; } if (!dirInfo.isSkipped) { completeDirectory(dirInfo); } dirInfo = dirInfo.Parent; } } private void completeDirectory(SVNDirectoryInfo dirInfo) throws SVNException { File file = myAdminInfo.getAnchor().getFile(dirInfo.getPath()); if (inSkippedTree(file) && !inDeletedTree(file, true)) { return; } if (dirInfo.Parent == null && myTarget != null) { if (myIsDepthSticky || myTarget != null) { SVNAdminArea adminArea = dirInfo.getAdminArea(); SVNEntry entry = adminArea.getEntry(myTarget, true); if (entry != null && entry.getDepth() == SVNDepth.EXCLUDE) { File target = myAdminInfo.getAnchor().getFile(myTarget); SVNAdminArea targetArea = myWCAccess.getAdminArea(target); if (targetArea == null && entry.isDirectory()) { doDeleteEntry(myTarget, myAdminInfo.getAnchor(), null, null); } else { entry.setDepth(SVNDepth.INFINITY); adminArea.saveEntries(false); } } } return; } SVNAdminArea adminArea = dirInfo.getAdminArea(); SVNEntry entry = adminArea.getEntry(adminArea.getThisDirName(), true); if (entry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "No ''.'' entry found in ''{0}''", adminArea.getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } entry.setIncomplete(false); if (!myIsDepthSticky && entry.isThisDir() && myRequestedDepth == SVNDepth.IMMEDIATES) { int directoryDepth = SVNPathUtil.getSegmentsCount(dirInfo.getPath()); if (myTarget != null && !"".equals(myTarget)) { directoryDepth--; } if (directoryDepth > 0) { SVNDepth ambientDepth = dirInfo.myAmbientDepth; if (ambientDepth == null || ambientDepth == SVNDepth.UNKNOWN || ambientDepth.compareTo(SVNDepth.EMPTY) > 0) { entry.setIncomplete(true); } } } File target = myAdminInfo.getTarget().getRoot(); if (myIsDepthSticky && (myRequestedDepth == SVNDepth.INFINITY || (adminArea.getRoot().equals(target) && myRequestedDepth.compareTo(entry.getDepth()) > 0))) { entry.setDepth(myRequestedDepth); myAdminInfo.addDepth(dirInfo.getPath(), myRequestedDepth); } for (Iterator ents = adminArea.entries(true); ents.hasNext();) { SVNEntry currentEntry = (SVNEntry) ents.next(); if (currentEntry.isDeleted()) { if (!currentEntry.isScheduledForAddition()) { adminArea.deleteEntry(currentEntry.getName()); } else { Map attributes = new SVNHashMap(); attributes.put(SVNProperty.DELETED, null); adminArea.modifyEntry(currentEntry.getName(), attributes, false, false); } } else if (currentEntry.isAbsent() && currentEntry.getRevision() != myTargetRevision) { adminArea.deleteEntry(currentEntry.getName()); } else if (currentEntry.getKind() == SVNNodeKind.DIR) { if (currentEntry.getDepth() == SVNDepth.EXCLUDE) { if (myIsDepthSticky && myRequestedDepth.compareTo(SVNDepth.IMMEDIATES) >= 0) { currentEntry.setDepth(SVNDepth.INFINITY); } } else if (myWCAccess.isMissing(adminArea.getFile(currentEntry.getName())) && !currentEntry.isAbsent() && !currentEntry.isScheduledForAddition()) { adminArea.deleteEntry(currentEntry.getName()); myWCAccess.handleEvent(SVNEventFactory.createSVNEvent(adminArea.getFile(currentEntry.getName()), currentEntry.getKind(), null, currentEntry.getRevision(), SVNEventAction.UPDATE_DELETE, null, null, null)); } } } adminArea.saveEntries(true); } private SVNFileInfo addFile(SVNDirectoryInfo parent, String path, String copyFromPath, long copyFromRevision) throws SVNException { File fullPath = myAdminInfo.getAnchor().getFile(path); boolean isLocallyDeleted = inDeletedTree(fullPath, true); if (copyFromPath != null || SVNRevision.isValidRevisionNumber(copyFromRevision)) { if (copyFromPath == null || !SVNRevision.isValidRevisionNumber(copyFromRevision)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_OP_ON_CWD, "Bad copyfrom arguments received."); SVNErrorManager.error(err, SVNLogType.DEFAULT); } } SVNFileInfo info = createFileInfo(myCurrentDirectory, path, true); if (inSkippedTree(fullPath) && !isLocallyDeleted) { info.isSkipped = true; return info; } info.isDeleted = isLocallyDeleted; checkIfPathIsUnderRoot(path); SVNAdminArea adminArea = parent.getAdminArea(); SVNFileType fileType = SVNFileType.getType(adminArea.getFile(info.name)); SVNEntry entry = adminArea.getEntry(info.name, false); File victim = alreadyInTreeConflict(fullPath); if (victim != null) { info.isSkipped = true; addSkippedTree(fullPath); SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.FILE, null, myTargetRevision, SVNEventAction.SKIP, SVNEventAction.UPDATE_ADD, null, null); if (entry != null) { event.setPreviousRevision(entry.getRevision()); event.setPreviousURL(entry.getSVNURL()); } myWCAccess.handleEvent(event); return info; } if (fileType == SVNFileType.DIRECTORY || fileType == SVNFileType.UNKNOWN) { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add file ''{0}'': a non-file object of the same name already exists", path); SVNErrorManager.error(error, SVNLogType.WC); } if (entry != null && fileType == SVNFileType.NONE) { if (entry.isDirectory() && entry.isScheduledForAddition()) { // special case of missing not yet versioned directory scheduled // for addition. // remove this entry, no chance to restore anything from the // directory. adminArea.deleteEntry(entry.getName()); adminArea.saveEntries(false); entry = null; } } if (entry == null && (fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK)) { if (myIsUnversionedObstructionsAllowed) { info.isExisted = true; } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Failed to add file ''{0}'': an unversioned file of the same name already exists", path); SVNErrorManager.error(err, SVNLogType.WC); } } if (entry != null) { SVNEntry parentEntry = adminArea.getEntry(adminArea.getThisDirName(), false); if (entry.getUUID() != null && !entry.getUUID().equals(parentEntry.getUUID())) { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "UUID mismatch: existing file ''{0}'' was checked out from a different repository", fullPath); SVNErrorManager.error(error, SVNLogType.WC); } if (mySwitchURL == null) { boolean equals = info.URL == entry.getURL() || (info.URL != null && info.URL.equals(entry.getURL())); if (!equals) { SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "URL ''{0}'' of existing file ''{1}'' does not match expected URL ''{2}''", new Object[] { entry.getURL(), fullPath, info.URL }); SVNErrorManager.error(error, SVNLogType.WC); } } } if (entry != null && (fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK)) { if ((entry.isScheduledForAddition() || entry.isScheduledForReplacement()) && !entry.isCopied()) { info.isAddExisted = true; } else { SVNLog log = parent.getLog(); SVNURL theirURL = SVNURL.parseURIEncoded(info.URL); SVNTreeConflictDescription treeConflict = checkTreeConflict(fullPath, entry, adminArea, log, SVNConflictAction.ADD, SVNNodeKind.FILE, theirURL); if (treeConflict != null) { addSkippedTree(fullPath); info.isSkipped = true; SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.FILE, null, myTargetRevision, SVNEventAction.TREE_CONFLICT, SVNEventAction.UPDATE_ADD, null, null); event.setPreviousRevision(entry.getRevision()); event.setPreviousURL(entry.getSVNURL()); myWCAccess.handleEvent(event); return info; } } } if (copyFromPath != null && !info.isSkipped) { return addFileWithHistory(parent, info, copyFromPath, copyFromRevision); } return info; } private SVNFileInfo addFileWithHistory(SVNDirectoryInfo parent, SVNFileInfo info, String copyFromPath, long copyFromRevision) throws SVNException { info.addedWithHistory = true; SVNAdminArea adminArea = parent.getAdminArea(); SVNEntry pathEntry = adminArea.getEntry(adminArea.getThisDirName(), false); SVNEntry srcEntry = null; try { srcEntry = locateCopyFrom(copyFromPath, copyFromRevision, adminArea.getRoot(), pathEntry); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_COPYFROM_PATH_NOT_FOUND) { throw svne; } } info.copiedBaseText = SVNAdminUtil.createTmpFile(adminArea); SVNProperties baseProperties = null; SVNProperties workingProperties = null; if (srcEntry != null) { SVNAdminArea srcArea = srcEntry.getAdminArea(); String srcTextBasePath = null; if (srcEntry.isScheduledForReplacement() && srcEntry.getCopyFromURL() != null) { srcTextBasePath = SVNAdminUtil.getTextRevertPath(srcEntry.getName(), false); baseProperties = srcArea.getRevertProperties(srcEntry.getName()).asMap(); workingProperties = baseProperties; } else { srcTextBasePath = SVNAdminUtil.getTextBasePath(srcEntry.getName(), false); baseProperties = srcArea.getBaseProperties(srcEntry.getName()).asMap(); workingProperties = srcArea.getProperties(srcEntry.getName()).asMap(); } InputStream srcIS = null; OutputStream copiedBaseOS = null; try { srcIS = SVNFileUtil.openFileForReading(srcArea.getFile(srcTextBasePath)); copiedBaseOS = SVNFileUtil.openFileForWriting(info.copiedBaseText); SVNChecksumOutputStream checksumOS = new SVNChecksumOutputStream(copiedBaseOS, SVNChecksumOutputStream.MD5_ALGORITHM, true); copiedBaseOS = checksumOS; FSRepositoryUtil.copy(srcIS, copiedBaseOS, myWCAccess); info.copiedBaseChecksum = checksumOS.getDigest(); } finally { SVNFileUtil.closeFile(srcIS); SVNFileUtil.closeFile(copiedBaseOS); } if (srcArea.hasTextModifications(srcEntry.getName(), false, true, false)) { info.copiedWorkingText = SVNAdminUtil.createTmpFile(adminArea); SVNFileUtil.copyFile(srcArea.getFile(srcEntry.getName()), info.copiedWorkingText, true); } } else { if (myFileFetcher == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_OP_ON_CWD, "No fetch_func supplied to update_editor."); SVNErrorManager.error(err, SVNLogType.DEFAULT); } baseProperties = new SVNProperties(); OutputStream baseTextOS = null; try { baseTextOS = SVNFileUtil.openFileForWriting(info.copiedBaseText); SVNChecksumOutputStream checksumBaseTextOS = new SVNChecksumOutputStream(baseTextOS, SVNChecksumOutputStream.MD5_ALGORITHM, true); baseTextOS = checksumBaseTextOS; myFileFetcher.fetchFile(copyFromPath, copyFromRevision, baseTextOS, baseProperties); info.copiedBaseChecksum = checksumBaseTextOS.getDigest(); } finally { SVNFileUtil.closeFile(baseTextOS); } workingProperties = baseProperties; } info.copiedBaseProperties = baseProperties.getRegularProperties(); info.copiedWorkingProperties = workingProperties.getRegularProperties(); return info; } private SVNEntry locateCopyFrom(String copyFromPath, long copyFromRevision, File dstDir, SVNEntry dstEntry) throws SVNException { if (dstEntry.getRepositoryRoot() == null || dstEntry.getURL() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_COPYFROM_PATH_NOT_FOUND, "Destination directory of add-with-history is missing a URL"); SVNErrorManager.error(err, SVNLogType.WC); } dstDir = new File(SVNPathUtil.validateFilePath(dstDir.getAbsolutePath())).getAbsoluteFile(); String dstReposPath = SVNPathUtil.getPathAsChild(dstEntry.getRepositoryRootURL().toDecodedString(), dstEntry.getSVNURL().toDecodedString()); if (dstReposPath == null) { if (dstEntry.getURL().equals(dstEntry.getRepositoryRoot())) { dstReposPath = ""; } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_COPYFROM_PATH_NOT_FOUND, "Destination URLs are broken"); SVNErrorManager.error(err, SVNLogType.WC); } } dstReposPath = "/" + dstReposPath; dstReposPath = SVNPathUtil.canonicalizePath(dstReposPath); String copyFromParent = SVNPathUtil.removeTail(copyFromPath); String ancestorPath = SVNPathUtil.getCommonPathAncestor(dstReposPath, copyFromParent); if ("".equals(ancestorPath)) { return null; } int levelsUP = SVNPathUtil.getSegmentsCount(dstReposPath) - SVNPathUtil.getSegmentsCount(ancestorPath); File currentWD = dstDir; for (int i = 0; i < levelsUP && currentWD != null; i++) { currentWD = currentWD.getParentFile(); } if (currentWD == null) { return null; } SVNFileType kind = SVNFileType.getType(currentWD); if (kind != SVNFileType.DIRECTORY) { return null; } SVNWCAccess ancestorAccess = SVNWCAccess.newInstance(null); SVNAdminArea ancestorArea = null; try { ancestorArea = ancestorAccess.open(currentWD, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { ancestorAccess.close(); return null; } throw svne; } SVNEntry ancestorEntry = ancestorArea.getEntry(ancestorArea.getThisDirName(), false); if (dstEntry.getUUID() != null && ancestorEntry.getUUID() != null && !dstEntry.getUUID().equals(ancestorEntry.getUUID())) { ancestorAccess.close(); return null; } SVNURL ancestorURL = dstEntry.getRepositoryRootURL().appendPath(ancestorPath, false); if (!ancestorURL.equals(ancestorEntry.getSVNURL())) { ancestorAccess.close(); return null; } String extraComponents = SVNPathUtil.getPathAsChild(ancestorPath, copyFromPath); currentWD = new File(currentWD, extraComponents); File currentWDParent = currentWD.getParentFile(); kind = SVNFileType.getType(currentWD); if (kind != SVNFileType.FILE) { ancestorAccess.close(); return null; } try { ancestorAccess.close(); ancestorArea = ancestorAccess.open(currentWDParent, false, 0); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { ancestorAccess.close(); return null; } throw svne; } SVNEntry fileEntry = ancestorArea.getEntry(currentWD.getName(), false); if (fileEntry == null) { ancestorAccess.close(); return null; } if (fileEntry.getUUID() != null && dstEntry.getUUID() != null && !fileEntry.getUUID().equals(dstEntry.getUUID())) { ancestorAccess.close(); return null; } SVNURL fileURL = fileEntry.getRepositoryRootURL().appendPath(copyFromPath, false); if (!fileURL.equals(fileEntry.getSVNURL())) { ancestorAccess.close(); return null; } if (!SVNRevision.isValidRevisionNumber(fileEntry.getCommittedRevision()) || !SVNRevision.isValidRevisionNumber(fileEntry.getRevision())) { ancestorAccess.close(); return null; } if (!(fileEntry.getCommittedRevision() <= copyFromRevision && copyFromRevision <= fileEntry.getRevision())) { ancestorAccess.close(); return null; } return fileEntry; } private void changeFileProperty(String name, SVNPropertyValue value, SVNFileInfo fileInfo) { if (!fileInfo.isSkipped) { fileInfo.propertyChanged(name, value); if (myWCAccess.getOptions().isUseCommitTimes() && SVNProperty.COMMITTED_DATE.equals(name)) { fileInfo.commitTime = value.getString(); if (fileInfo.commitTime != null) { fileInfo.commitTime = fileInfo.commitTime.trim(); } } } } public SVNFileInfo openFile(String path, SVNDirectoryInfo parent) throws SVNException { checkIfPathIsUnderRoot(path); File fullPath = myAdminInfo.getAnchor().getFile(path); SVNFileInfo info = createFileInfo(parent, path, false); SVNAdminArea adminArea = parent.getAdminArea(); SVNEntry entry = adminArea.getEntry(info.name, true); if (entry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNVERSIONED_RESOURCE, "File ''{0}'' in directory ''{1}'' is not a versioned resource", new Object[] { info.name, adminArea.getRoot() }); SVNErrorManager.error(err, SVNLogType.WC); } boolean isLocallyDeleted = inDeletedTree(fullPath, true); if (inSkippedTree(fullPath) && !isLocallyDeleted) { info.isSkipped = true; return info; } File victim = alreadyInTreeConflict(fullPath); SVNTreeConflictDescription treeConflict = null; if (victim == null) { //tree conflict didn't exist before SVNLog log = parent.getLog(); SVNURL theirURL = SVNURL.parseURIEncoded(info.URL); treeConflict = checkTreeConflict(fullPath, entry, adminArea, log, SVNConflictAction.EDIT, SVNNodeKind.FILE, theirURL); if (treeConflict != null && treeConflict.getConflictAction() == SVNConflictAction.EDIT) { //now tree conflict is created info.treeConflictCreationAttributes = getTreeConflictCreationAttributes(adminArea, treeConflict); } } String name = SVNPathUtil.tail(path); boolean hasTextConflicts = adminArea.hasTextConflict(name); boolean hasPropConflicts = adminArea.hasPropConflict(name); if (treeConflict != null && treeConflict.getConflictReason() == SVNConflictReason.DELETED && !isLocallyDeleted) { addDeletedTree(fullPath); isLocallyDeleted = true; } info.isDeleted = isLocallyDeleted; if (victim != null || treeConflict != null || hasTextConflicts || hasPropConflicts) { if (!isLocallyDeleted) { info.isSkipped = true; } addSkippedTree(fullPath); if (!inDeletedTree(fullPath, false)) { SVNEventAction eventAction = treeConflict != null ? SVNEventAction.TREE_CONFLICT : SVNEventAction.SKIP; SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.FILE, null, myTargetRevision, hasTextConflicts ? SVNStatusType.CONFLICTED : SVNStatusType.UNKNOWN, hasPropConflicts ? SVNStatusType.CONFLICTED : SVNStatusType.UNKNOWN, SVNStatusType.LOCK_INAPPLICABLE, eventAction, SVNEventAction.UPDATE_UPDATE, null, null); event.setPreviousRevision(entry.getRevision()); event.setURL(entry.getSVNURL()); myWCAccess.handleEvent(event); } } return info; } private SVNProperties getTreeConflictCreationAttributes(SVNAdminArea adminArea, SVNTreeConflictDescription treeConflict) throws SVNException { Map conflicts = new SVNHashMap(); conflicts.put(treeConflict.getPath(), treeConflict); String conflictData = SVNTreeConflictUtil.getTreeConflictData(conflicts); SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, adminArea.getThisDirName()); command.put(SVNLog.DATA_ATTR, conflictData); return command; } private void closeFile(String textChecksum, SVNFileInfo fileInfo, SVNDirectoryInfo dirInfo) throws SVNException { if (fileInfo.isSkipped) { maybeBumpDirInfo(dirInfo); return; } if (!fileInfo.receivedTextDelta && (fileInfo.getChangedProperties() == null || fileInfo.getChangedProperties().isEmpty())) { //only "fake" changes received // a workaround for SVN issue 3525 on the client side if (fileInfo.treeConflictCreationAttributes != null) { dirInfo.getLog().deleteCommandsByNameAndAttributes(SVNLog.ADD_TREE_CONFLICT, fileInfo.treeConflictCreationAttributes, false); } } else { dirInfo.anyChangesInChildren = true; } File fullPath = myAdminInfo.getAnchor().getFile(fileInfo.getPath()); if (fileInfo.addedWithHistory && !fileInfo.receivedTextDelta) { SVNErrorManager.assertionFailure(fileInfo.baseFile == null && fileInfo.newBaseFile == null && fileInfo.copiedBaseText != null, null, SVNLogType.WC); SVNAdminArea adminArea = fileInfo.getAdminArea(); SVNEntry entry = adminArea.getEntry(fileInfo.name, false); boolean replaced = entry != null && entry.isScheduledForReplacement(); boolean useRevertBase = replaced && entry.getCopyFromURL() != null; if (useRevertBase) { fileInfo.baseFile = adminArea.getFile(SVNAdminUtil.getTextRevertPath(fileInfo.name, false)); fileInfo.newBaseFile = adminArea.getFile(SVNAdminUtil.getTextRevertPath(fileInfo.name, true)); } else { fileInfo.baseFile = adminArea.getBaseFile(fileInfo.name, false); fileInfo.newBaseFile = adminArea.getBaseFile(fileInfo.name, true); } SVNFileUtil.copyFile(fileInfo.copiedBaseText, fileInfo.newBaseFile, true); fileInfo.checksum = fileInfo.copiedBaseChecksum; } // check checksum. String checksum = null; boolean isTextUpdated = fileInfo.newBaseFile != null; if (textChecksum != null && isTextUpdated) { if (fileInfo.checksum != null && !textChecksum.equals(fileInfo.checksum)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Checksum mismatch for ''{0}''; expected: ''{1}'', actual: ''{2}''", new Object[] { fileInfo.getPath(), textChecksum, fileInfo.checksum }); SVNErrorManager.error(err, SVNLogType.WC); } checksum = textChecksum; } SVNAdminArea adminArea = fileInfo.getAdminArea(); SVNLog log = dirInfo.getLog(); String name = fileInfo.name; SVNEntry fileEntry = adminArea.getEntry(name, false); if (fileEntry == null && !fileInfo.IsAdded) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNVERSIONED_RESOURCE, "''{0}'' is not under version control", fileInfo.getPath()); SVNErrorManager.error(err, SVNLogType.WC); } long previousRevision = fileEntry != null ? fileEntry.getRevision() : -1; SVNURL previousURL = fileEntry != null ? fileEntry.getSVNURL() : null; // merge props. SVNProperties modifiedWCProps = fileInfo.getChangedWCProperties(); SVNProperties modifiedEntryProps = fileInfo.getChangedEntryProperties(); SVNProperties modifiedProps = fileInfo.getChangedProperties(); String commitTime = fileInfo.commitTime; SVNProperties command = new SVNProperties(); SVNStatusType textStatus = SVNStatusType.UNCHANGED; SVNStatusType lockStatus = SVNStatusType.LOCK_UNCHANGED; if (myAdminInfo.isIncomplete(fileInfo.getPath()) && fileEntry != null) { // delete all props. SVNVersionedProperties oldBaseProps = adminArea.getBaseProperties(fileEntry.getName()); SVNProperties baseMap = oldBaseProps.asMap(); if (modifiedProps == null) { modifiedProps = new SVNProperties(); } for (Iterator names = baseMap.nameSet().iterator(); names.hasNext();) { String propName = (String) names.next(); if (!modifiedProps.containsName(propName)) { modifiedProps.put(propName, SVNPropertyValue.create(null)); } } } boolean magicPropsChanged = false; if (modifiedProps != null && !modifiedProps.isEmpty()) { magicPropsChanged = modifiedProps.containsName(SVNProperty.EXECUTABLE) || modifiedProps.containsName(SVNProperty.NEEDS_LOCK) || modifiedProps.containsName(SVNProperty.KEYWORDS) || modifiedProps.containsName(SVNProperty.EOL_STYLE) || modifiedProps.containsName(SVNProperty.CHARSET) || modifiedProps.containsName(SVNProperty.SPECIAL); } SVNStatusType propStatus = adminArea.mergeProperties(name, null, fileInfo.copiedBaseProperties, fileInfo.copiedWorkingProperties, modifiedProps, null, null, true, false, log); if (modifiedEntryProps != null) { lockStatus = log.logChangedEntryProperties(name, modifiedEntryProps); } if (modifiedWCProps != null) { log.logChangedWCProperties(name, modifiedWCProps); } boolean isLocallyModified = false; if (fileInfo.copiedWorkingText != null) { isLocallyModified = true; } else if (fileEntry != null && fileEntry.getExternalFilePath() != null && fileEntry.isScheduledForAddition()) { isLocallyModified = false; } else if (!fileInfo.isExisted) { isLocallyModified = adminArea.hasTextModifications(name, false, false, false); } else if (isTextUpdated) { isLocallyModified = adminArea.hasVersionedFileTextChanges(adminArea.getFile(name), fileInfo.newBaseFile, false); } boolean isReplaced = fileEntry != null && fileEntry.isScheduledForReplacement(); SVNProperties logAttributes = new SVNProperties(); if (fileInfo.isAddExisted) { logAttributes.put(SVNLog.FORCE_ATTR, "true"); logAttributes.put(SVNProperty.shortPropertyName(SVNProperty.SCHEDULE), ""); } log.logTweakEntry(name, fileInfo.URL, myTargetRevision); String absDirPath = adminArea.getRoot().getAbsolutePath().replace(File.separatorChar, '/'); String basePath = null; if (fileInfo.baseFile != null) { String absBasePath = fileInfo.baseFile.getAbsolutePath().replace(File.separatorChar, '/'); basePath = absBasePath.substring(absDirPath.length()); if (basePath.startsWith("/")) { basePath = basePath.substring(1); } } String tmpBasePath = null; if (fileInfo.newBaseFile != null) { String absTmpBasePath = fileInfo.newBaseFile.getAbsolutePath().replace(File.separatorChar, '/'); tmpBasePath = absTmpBasePath.substring(absDirPath.length()); if (tmpBasePath.startsWith("/")) { tmpBasePath = tmpBasePath.substring(1); } } SVNStatusType mergeOutcome = SVNStatusType.UNCHANGED; File workingFile = adminArea.getFile(name); boolean deletedCopiedBaseText = false; if (tmpBasePath != null) { textStatus = SVNStatusType.CHANGED; // there is a text to replace the working copy with. if (isReplaced) { // do nothing. } else if (!isLocallyModified) { if (!fileInfo.isDeleted) { command.put(SVNLog.NAME_ATTR, tmpBasePath); command.put(SVNLog.DEST_ATTR, name); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } // if (fileEntry == null || !fileEntry.isScheduledForDeletion()) // { // } } else { SVNFileType kind = SVNFileType.getType(workingFile); if (kind == SVNFileType.NONE && !fileInfo.addedWithHistory) { command.put(SVNLog.NAME_ATTR, tmpBasePath); command.put(SVNLog.DEST_ATTR, name); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } else if (!fileInfo.isExisted) { String pathExt = null; if (myExtensionPatterns != null && myExtensionPatterns.length > 0) { int dotInd = name.lastIndexOf('.'); if (dotInd != -1 && dotInd != 0 && dotInd != name.length() - 1) { pathExt = name.substring(dotInd + 1); } if (pathExt != null && !"".equals(pathExt)) { boolean matches = false; for (int i = 0; i < myExtensionPatterns.length; i++) { String extPattern = myExtensionPatterns[i]; matches = DefaultSVNOptions.matches(extPattern, pathExt); if (matches) { break; } } if (!matches) { pathExt = null; } } } boolean deleteLeftMergeFile = false; boolean deleteCopiedBaseText = false; File mergeLeftFile = fileInfo.baseFile; if (fileInfo.isAddExisted && !isReplaced) { deleteLeftMergeFile = true; mergeLeftFile = SVNAdminUtil.createTmpFile(adminArea); } else if (fileInfo.copiedBaseText != null) { deleteLeftMergeFile = deleteCopiedBaseText = true; mergeLeftFile = fileInfo.copiedBaseText; } String absMergeLeftFilePath = mergeLeftFile.getAbsolutePath().replace(File.separatorChar, '/'); String mergeLeftFilePath = absMergeLeftFilePath.substring(absDirPath.length()); if (mergeLeftFilePath.startsWith("/")) { mergeLeftFilePath = mergeLeftFilePath.substring(1); } String leftLabel = null; if (fileInfo.addedWithHistory) { leftLabel = ".copied" + (pathExt != null ? "." + pathExt : ""); } else { leftLabel = ".r" + fileEntry.getRevision() + (pathExt != null ? "." + pathExt : ""); } String rightLabel = ".r" + myTargetRevision + (pathExt != null ? "." + pathExt : ""); String mineLabel = ".mine" + (pathExt != null ? "." + pathExt : ""); // do test merge. mergeOutcome = adminArea.mergeText(name, mergeLeftFile, adminArea.getFile(tmpBasePath), fileInfo.copiedWorkingText, mineLabel, leftLabel, rightLabel, modifiedProps, false, null, log); if (mergeOutcome == SVNStatusType.UNCHANGED) { textStatus = SVNStatusType.MERGED; } if (deleteLeftMergeFile) { command.put(SVNLog.NAME_ATTR, mergeLeftFilePath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); if (deleteCopiedBaseText) { deletedCopiedBaseText = true; } } if (fileInfo.copiedWorkingText != null) { String absCopiedWorkingTextPath = fileInfo.copiedWorkingText.getAbsolutePath().replace(File.separatorChar, '/'); String copiedWorkingTextPath = absCopiedWorkingTextPath.substring(absDirPath.length()); if (copiedWorkingTextPath.startsWith("/")) { copiedWorkingTextPath = copiedWorkingTextPath.substring(1); } command.put(SVNLog.NAME_ATTR, copiedWorkingTextPath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } } } } else { if (magicPropsChanged && (workingFile.exists() || SVNFileType.getType(workingFile) == SVNFileType.SYMLINK)) { // only props were changed, but we have to retranslate file. // only if wc file exists (may be locally deleted), otherwise no // need to retranslate... String tmpPath = SVNAdminUtil.getTextBasePath(name, true); command.put(SVNLog.NAME_ATTR, name); command.put(SVNLog.DEST_ATTR, tmpPath); log.addCommand(SVNLog.COPY_AND_DETRANSLATE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, tmpPath); command.put(SVNLog.DEST_ATTR, name); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } if (lockStatus == SVNStatusType.LOCK_UNLOCKED) { command.put(SVNLog.NAME_ATTR, name); log.addCommand(SVNLog.MAYBE_READONLY, command, false); command.clear(); } } if (tmpBasePath != null) { command.put(SVNLog.NAME_ATTR, tmpBasePath); command.put(SVNLog.DEST_ATTR, basePath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, basePath); log.addCommand(SVNLog.READONLY, command, false); command.clear(); logAttributes.put(SVNProperty.shortPropertyName(SVNProperty.CHECKSUM), checksum); } if (fileInfo.isDeleted && !isReplaced) { logAttributes.put(SVNProperty.shortPropertyName(SVNProperty.SCHEDULE), SVNProperty.SCHEDULE_DELETE); } if (logAttributes.size() > 0) { logAttributes.put(SVNLog.NAME_ATTR, name); log.addCommand(SVNLog.MODIFY_ENTRY, logAttributes, false); } if (!isLocallyModified && (fileInfo.IsAdded || fileEntry.getSchedule() == null)) { if (commitTime != null && !fileInfo.isExisted) { command.put(SVNLog.NAME_ATTR, name); command.put(SVNLog.TIMESTAMP_ATTR, commitTime); log.addCommand(SVNLog.SET_TIMESTAMP, command, false); command.clear(); } if ((tmpBasePath != null || magicPropsChanged) && !fileInfo.isDeleted) { command.put(SVNLog.NAME_ATTR, name); command.put(SVNProperty.shortPropertyName(SVNProperty.TEXT_TIME), SVNLog.WC_TIMESTAMP); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); command.clear(); } command.put(SVNLog.NAME_ATTR, name); command.put(SVNProperty.shortPropertyName(SVNProperty.WORKING_SIZE), SVNLog.WC_WORKING_SIZE); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); command.clear(); } if (fileInfo.copiedBaseText != null && !deletedCopiedBaseText) { String absCopiedBaseTextPath = fileInfo.copiedBaseText.getAbsolutePath().replace(File.separatorChar, '/'); String copiedBaseTextPath = absCopiedBaseTextPath.substring(absDirPath.length()); if (copiedBaseTextPath.startsWith("/")) { copiedBaseTextPath = copiedBaseTextPath.substring(1); } command.put(SVNLog.NAME_ATTR, copiedBaseTextPath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } // bump. maybeBumpDirInfo(dirInfo); if (mergeOutcome == SVNStatusType.CONFLICTED_UNRESOLVED) { textStatus = SVNStatusType.CONFLICTED_UNRESOLVED; } else if (mergeOutcome == SVNStatusType.CONFLICTED) { textStatus = SVNStatusType.CONFLICTED; } else if (fileInfo.newBaseFile != null) { if (isLocallyModified) { textStatus = SVNStatusType.MERGED; } else { textStatus = SVNStatusType.CHANGED; } } // notify. if ((textStatus != SVNStatusType.UNCHANGED || propStatus != SVNStatusType.UNCHANGED || lockStatus != SVNStatusType.LOCK_UNCHANGED || fileInfo.treeConficted) && !inDeletedTree(fullPath, true)) { SVNEventAction action = SVNEventAction.UPDATE_UPDATE; SVNEventAction expectedAction = action; if (fileInfo.treeConficted) { action = SVNEventAction.TREE_CONFLICT; } else if (fileInfo.isExisted || fileInfo.isAddExisted) { if (textStatus != SVNStatusType.CONFLICTED_UNRESOLVED && textStatus != SVNStatusType.CONFLICTED) { action = SVNEventAction.UPDATE_EXISTS; expectedAction = action; } } else if (fileInfo.IsAdded) { action = SVNEventAction.UPDATE_ADD; expectedAction = action; } SVNEvent event = SVNEventFactory.createSVNEvent(fullPath, SVNNodeKind.FILE, null, myTargetRevision, textStatus, propStatus, lockStatus, action, expectedAction, null, null); event.setPreviousRevision(previousRevision); event.setPreviousURL(previousURL); event.setURL(fileInfo.URL != null ? SVNURL.parseURIEncoded(fileInfo.URL) : null); myWCAccess.handleEvent(event); } } private SVNFileInfo createFileInfo(SVNDirectoryInfo parent, String path, boolean added) throws SVNException { SVNFileInfo info = new SVNFileInfo(parent, path); info.IsAdded = added; info.name = SVNPathUtil.tail(path); info.isExisted = false; info.isAddExisted = false; info.isSkipped = false; info.treeConficted = false; info.isDeleted = false; info.baseFile = null; info.newBaseFile = null; SVNAdminArea adminArea = parent.getAdminArea(); SVNEntry entry = adminArea.getEntry(info.name, true); if (mySwitchURL != null || entry == null) { info.URL = SVNPathUtil.append(parent.URL, SVNEncodingUtil.uriEncode(info.name)); } else { info.URL = entry.getURL(); } parent.RefCount++; return info; } private SVNDirectoryInfo createDirectoryInfo(SVNDirectoryInfo parent, String path, boolean added) { SVNDirectoryInfo info = new SVNDirectoryInfo(path); info.Parent = parent; info.IsAdded = added; String name = path != null ? SVNPathUtil.tail(path) : ""; if (mySwitchURL == null) { SVNAdminArea area = null; SVNEntry dirEntry = null; File areaPath = new File(myAdminInfo.getAnchor().getRoot(), info.getPath()); try { area = myWCAccess.getAdminArea(areaPath); if (area != null) { // could be missing. dirEntry = area.getEntry(area.getThisDirName(), false); } } catch (SVNException svne) { // } if (area != null && dirEntry != null) { info.URL = dirEntry.getURL(); } if (info.URL == null && parent != null) { info.URL = SVNPathUtil.append(parent.URL, SVNEncodingUtil.uriEncode(name)); } else if (info.URL == null && parent == null) { info.URL = myTargetURL; } } else { if (parent == null) { info.URL = myTarget == null ? mySwitchURL : SVNPathUtil.removeTail(mySwitchURL); } else { if (myTarget != null && parent.Parent == null) { info.URL = mySwitchURL; } else { info.URL = SVNPathUtil.append(parent.URL, SVNEncodingUtil.uriEncode(name)); } } } info.RefCount = 1; info.isSkipped = false; if (info.Parent != null) { info.Parent.RefCount++; } info.isExisted = false; info.isAddExisted = false; info.log = null; info.myAmbientDepth = SVNDepth.UNKNOWN; info.wasIncomplete = false; return info; } public static SVNUpdateEditor createUpdateEditor(SVNAdminAreaInfo info, String switchURL, boolean allowUnversionedObstructions, boolean depthIsSticky, SVNDepth depth, String[] preservedExtensions, ISVNFileFetcher fileFetcher, boolean lockOnDemand) throws SVNException { if (depth == SVNDepth.UNKNOWN) { depthIsSticky = false; } SVNEntry entry = info.getAnchor().getEntry(info.getAnchor().getThisDirName(), false); if (switchURL != null && entry != null && entry.getRepositoryRoot() != null) { if (!SVNPathUtil.isAncestor(entry.getRepositoryRoot(), switchURL)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SWITCH, "''{0}''\nis not the same repository as\n''{1}''", new Object[] { switchURL, entry.getRepositoryRoot() }); SVNErrorManager.error(err, SVNLogType.WC); } } SVNUpdateEditor editor = new SVNUpdateEditor(info, switchURL, allowUnversionedObstructions, depthIsSticky, depth, preservedExtensions, entry != null ? entry.getURL() : null, entry != null ? entry.getRepositoryRoot() : null, fileFetcher, lockOnDemand); info.getTarget().closeEntries(); return editor; } private class SVNEntryInfo { public String URL; public boolean IsAdded; public boolean isExisted; public boolean isAddExisted; public SVNDirectoryInfo Parent; public boolean isSkipped; public long myPreviousRevision; private String myPath; private SVNProperties myChangedProperties; private SVNProperties myChangedEntryProperties; private SVNProperties myChangedWCProperties; // a workaround for SVN issue 3525 on the client side // in short: because of some bug, all SVN servers of version < 1.6.17 // may send only entry properties change in update editor (instead of sending nothing) // if a client working with such a buggy server, it should ignore those changes // instead of a tree conflict creation // // the variable keeps tree conflict creation command to remove in closeFile protected SVNProperties treeConflictCreationAttributes; protected SVNEntryInfo(String path) { myPath = path; this.treeConflictCreationAttributes = null; } protected String getPath() { return myPath; } public void propertyChanged(String name, SVNPropertyValue value) { if (name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)) { myChangedEntryProperties = myChangedEntryProperties == null ? new SVNProperties() : myChangedEntryProperties; // trim value of svn:entry property if (value != null) { String strValue = value.getString(); if (strValue != null) { strValue = strValue.trim(); value = SVNPropertyValue.create(strValue); } } myChangedEntryProperties.put(name.substring(SVNProperty.SVN_ENTRY_PREFIX.length()), value); } else if (name.startsWith(SVNProperty.SVN_WC_PREFIX)) { myChangedWCProperties = myChangedWCProperties == null ? new SVNProperties() : myChangedWCProperties; myChangedWCProperties.put(name, value); } else { myChangedProperties = myChangedProperties == null ? new SVNProperties() : myChangedProperties; myChangedProperties.put(name, value); } } public SVNProperties getChangedWCProperties() { return myChangedWCProperties; } public SVNProperties getChangedEntryProperties() { return myChangedEntryProperties; } public SVNProperties getChangedProperties() { return myChangedProperties; } } private class SVNFileInfo extends SVNEntryInfo { public String name; public String commitTime; public String checksum; public String expectedSrcChecksum; public String copiedBaseChecksum; public File baseFile; public File newBaseFile; public boolean addedWithHistory; public boolean receivedTextDelta; private SVNProperties copiedBaseProperties; private SVNProperties copiedWorkingProperties; private File copiedBaseText; private File copiedWorkingText; private SVNChecksumInputStream sourceChecksumStream; private boolean treeConficted; // Set if this file is locally deleted or is being added // within a locally deleted tree. private boolean isDeleted; // The checksum for the file located at newBaseFile. // private String actualChecksum; // If this file was added with history, this is the checksum of the // text base (see copied_text_base). May be NULL if unknown. // private String copiedBaseChecksum; public SVNFileInfo(SVNDirectoryInfo parent, String path) { super(path); this.Parent = parent; this.isDeleted = false; } public SVNAdminArea getAdminArea() throws SVNException { return Parent.getAdminArea(); } } private class SVNDirectoryInfo extends SVNEntryInfo implements ISVNCleanupHandler { public int RefCount; private SVNLog log; public int LogCount; public SVNDepth myAmbientDepth; public boolean wasIncomplete; public boolean anyChangesInChildren; public SVNDirectoryInfo(String path) { super(path); this.anyChangesInChildren = false; } public SVNAdminArea getAdminArea() throws SVNException { String path = getPath(); File file = new File(myAdminInfo.getAnchor().getRoot(), path); SVNAdminArea area = myAdminInfo.getWCAccess().retrieve(file); if (myIsLockOnDemand && area != null && !area.isLocked()) { area.lock(false); area = myAdminInfo.getWCAccess().upgrade(file); } return area; } public SVNLog getLog() throws SVNException { if (log == null) { log = getAdminArea().getLog(); LogCount++; } return log; } public void flushLog() throws SVNException { if (log != null) { log.save(); log = null; } } public void runLogs() throws SVNException { LogCount = 0; getAdminArea().runLogs(); } public void cleanup(SVNAdminArea area) throws SVNException { if (area != null && LogCount > 0) { LogCount = 0; area.runLogs(); } } } public void cleanup(SVNAdminArea area) throws SVNException { area.runLogs(); } }