package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; 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.SVNURL; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.util.SVNSkel; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.SVNStatusEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.PristineContentsInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbRepositoryInfo; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbRepositoryInfo.RepositoryInfoField; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDbDir; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDbRoot; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.AdditionInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.ExternalNodeInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo; import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbExternals; import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbReader; import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbShared; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNConflictDescription; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnChecksum; import org.tmatesoft.svn.core.wc2.SvnCopy; import org.tmatesoft.svn.core.wc2.SvnCopySource; import org.tmatesoft.svn.core.wc2.SvnGetStatus; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; import org.tmatesoft.svn.core.wc2.SvnScheduleForAddition; import org.tmatesoft.svn.core.wc2.SvnStatus; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgWcToWcCopy extends SvnNgOperationRunner<Void, SvnCopy> { @Override public boolean isApplicable(SvnCopy operation, SvnWcGeneration wcGeneration) throws SVNException { return areAllSourcesLocal(operation) && operation.getFirstTarget().isLocal(); } private boolean areAllSourcesLocal(SvnCopy operation) { for(SvnCopySource source : operation.getSources()) { if (source.getSource().isFile()) { if (operation.isMove()) { continue; } if (isLocalRevision(source.getRevision()) && isLocalRevision(source.getSource().getResolvedPegRevision())) { continue; } } return false; } return true; } private boolean isLocalRevision(SVNRevision revision) { return revision == SVNRevision.WORKING || revision == SVNRevision.UNDEFINED; } @Override protected Void run(SVNWCContext context) throws SVNException { Collection<SvnCopySource> sources = getOperation().getSources(); try { return tryRun(context, sources, getFirstTarget()); } catch (SVNException e) { SVNErrorCode code = e.getErrorMessage().getErrorCode(); if (!getOperation().isFailWhenDstExists() && getOperation().getSources().size() == 1 && (code == SVNErrorCode.ENTRY_EXISTS || code == SVNErrorCode.FS_ALREADY_EXISTS)) { SvnCopySource source = sources.iterator().next(); return tryRun(context, sources, new File(getFirstTarget(), source.getSource().getFile().getName())); } throw e; } finally { sleepForTimestamp(); } } protected Void tryRun(SVNWCContext context, Collection<SvnCopySource> sources, File target) throws SVNException { if (getOperation().isDisjoint()) { return disjointCopy(context, target); } else { return copy(context, sources, target); } } /** * The method performs "disjoint" copy (see SVNCopyClient#doCopy(File)) * The algorithm is: * 1. Create a fake working copy * 2. Move wc.db from the nested working copy to the fake * 3. Move all pristine files to the parent working copy * 4. Perform metadata copying * @param context * @param nestedWC * @return * @throws SVNException */ private Void disjointCopy(SVNWCContext context, File nestedWC) throws SVNException { nestedWC = new File(nestedWC.getAbsolutePath().replace(File.separatorChar, '/')); final File nestedWCParent = nestedWC.getParentFile(); checkForDisjointCopyPossibility(context, nestedWC, nestedWCParent); context.getDb().close(); final File wcRoot = context.getDb().getWCRoot(nestedWCParent); final File tempDirectory = new File(getAdminDirectory(wcRoot), "tmp"); SVNFileUtil.ensureDirectoryExists(tempDirectory); final File fakeWorkingCopyDirectory = SVNFileUtil.createUniqueDir(tempDirectory, "disjoint-copy", "tmp", true); moveWcDb(nestedWC, fakeWorkingCopyDirectory); copyPristineFiles(nestedWC, wcRoot, true); SVNFileUtil.deleteAll(getAdminDirectory(nestedWC), true); context.getDb().forgetDirectoryTemp(nestedWC); File lockRoot = null; try { lockRoot = context.acquireWriteLock(wcRoot, true, true); copy(context, fakeWorkingCopyDirectory, nestedWC, true); } finally { if (lockRoot != null) { context.releaseWriteLock(lockRoot); } } return null; } private void checkForDisjointCopyPossibility(SVNWCContext context, File nestedWC, File nestedWCParent) throws SVNException { SVNFileType nestedWCType = SVNFileType.getType(nestedWC); if (nestedWCType != SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "This kind of copy can be run on a root of a disjoint wc directory only"); SVNErrorManager.error(err, SVNLogType.WC); } if (nestedWCParent == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "{0} seems to be not a disjoint wc since it has no parent", nestedWC); SVNErrorManager.error(err, SVNLogType.WC); } if (!(context.getDb() instanceof SVNWCDb)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Unsupported working copy format", nestedWC); SVNErrorManager.error(err, SVNLogType.WC); } SVNWCDb wcdb = (SVNWCDb) context.getDb(); if (hasMetadataInParentWc(wcdb, nestedWC, nestedWCParent)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Entry ''{0}'' already exists in parent directory", nestedWC.getName()); SVNErrorManager.error(err, SVNLogType.WC); } final ISVNWCDb.WCDbBaseInfo baseInfo = context.getDb().getBaseInfo(nestedWC, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposRootUrl, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposRelPath); final ISVNWCDb.WCDbBaseInfo parentBaseInfo = context.getDb().getBaseInfo(nestedWCParent, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposRootUrl, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposRelPath); if (baseInfo.reposRootUrl != null && parentBaseInfo.reposRootUrl != null && !baseInfo.reposRootUrl.equals(parentBaseInfo.reposRootUrl)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy to ''{0}'', as it is not from repository ''{1}''; it is from ''{2}''", new Object[] { nestedWCParent, baseInfo.reposRootUrl, baseInfo.reposRootUrl }); SVNErrorManager.error(err, SVNLogType.WC); } SVNWCContext.ScheduleInternalInfo parentSchedule = context.getNodeScheduleInternal(nestedWCParent, true, true); if (parentSchedule.schedule == SVNWCContext.SVNWCSchedule.delete) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy to ''{0}'', as it is scheduled for deletion", nestedWCParent); SVNErrorManager.error(err, SVNLogType.WC); } SVNWCContext.ScheduleInternalInfo schedule = context.getNodeScheduleInternal(nestedWC, true, true); if (schedule.schedule == SVNWCContext.SVNWCSchedule.delete) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy ''{0}'', as it is scheduled for deletion", nestedWC); SVNErrorManager.error(err, SVNLogType.WC); } File relativePath = baseInfo.reposRelPath; File parentRelativePath = parentBaseInfo.reposRelPath; if (relativePath == null || parentRelativePath == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy ''{0}'': cannot resolve its relative path, perhaps it is obstructed", nestedWC); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNPathUtil.isAncestor(relativePath.getPath(), parentRelativePath.getPath())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot copy path ''{0}'' into its own child ''{1}", new Object[] { nestedWC, nestedWCParent }); SVNErrorManager.error(err, SVNLogType.WC); } if ((schedule.schedule == SVNWCContext.SVNWCSchedule.add && !schedule.copied) || context.getNodeUrl(nestedWC) == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Cannot copy or move ''{0}'': it is not in repository yet; " + "try committing first", nestedWC); SVNErrorManager.error(err, SVNLogType.WC); } } private void moveWcDb(File sourceWc, File targetWc) throws SVNException { final File sourceWcDbFile = getWCDbFile(sourceWc); final File targetWcDbFile = getWCDbFile(targetWc); SVNFileUtil.ensureDirectoryExists(targetWcDbFile.getParentFile()); SVNFileUtil.rename(sourceWcDbFile, targetWcDbFile); } private void copyPristineFiles(File sourceWc, File targetWc, boolean move) throws SVNException { final File sourcePristineDirectory = getPristineDirectory(sourceWc); final File targetPristineDirectory = getPristineDirectory(targetWc); final File[] sourceDirectories = SVNFileListUtil.listFiles(sourcePristineDirectory); if (sourceDirectories != null) { for (File sourceDirectory : sourceDirectories) { final File targetDirectory = new File(targetPristineDirectory, sourceDirectory.getName()); SVNFileUtil.ensureDirectoryExists(targetDirectory); final File[] sourcePristineFiles = SVNFileListUtil.listFiles(sourceDirectory); if (sourcePristineFiles != null) { for (File sourcePristineFile : sourcePristineFiles) { final File targetPristineFile = new File(targetDirectory, sourcePristineFile.getName()); if (!targetPristineFile.exists()) { if (move) { SVNFileUtil.rename(sourcePristineFile, targetPristineFile); } else { SVNFileUtil.copyFile(sourcePristineFile, targetPristineFile, false); } } } } } } SVNFileUtil.deleteAll(sourcePristineDirectory, true); } private File getPristineDirectory(File workingCopyDirectory) { return new File(getAdminDirectory(workingCopyDirectory), "pristine"); } private File getWCDbFile(File nestedWC) { return new File(getAdminDirectory(nestedWC), "wc.db"); } private File getAdminDirectory(File parentWC) { final String adminDirectoryName = SVNFileUtil.getAdminDirectoryName(); return new File(parentWC, adminDirectoryName); } private boolean hasMetadataInParentWc(SVNWCDb wcdb, File nestedWC, File nestedWCParent) throws SVNException { SVNWCDb.DirParsedInfo parsedInfo = wcdb.obtainWcRoot(nestedWCParent); SVNWCDbDir wcDbDir = parsedInfo == null ? null : parsedInfo.wcDbDir; SVNWCDbRoot wcdbRoot = wcDbDir == null ? null : wcDbDir.getWCRoot(); if (wcdbRoot == null) { return false; } try { wcdb.readInfo(wcdbRoot, new File(SVNPathUtil.getRelativePath(nestedWCParent.getPath(),nestedWC.getPath()))); return true; } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.ENTRY_NOT_FOUND || e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) { return false; } else { throw e; } } } private Void copy(SVNWCContext context, Collection<SvnCopySource> sources, File target) throws SVNException { Collection<SvnCopyPair> copyPairs = new ArrayList<SvnCopyPair>(); if (sources.size() > 1) { if (getOperation().isFailWhenDstExists()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_MULTIPLE_SOURCES_DISALLOWED); SVNErrorManager.error(err, SVNLogType.DEFAULT); } for (SvnCopySource copySource : sources) { SvnCopyPair copyPair = new SvnCopyPair(); copyPair.source = copySource.getSource().getFile(); String baseName = copyPair.source.getName(); copyPair.dst = new File(target, baseName); copyPairs.add(copyPair); } } else if (sources.size() == 1) { SvnCopyPair copyPair = new SvnCopyPair(); SvnCopySource source = sources.iterator().next(); copyPair.source = new File(SVNPathUtil.validateFilePath(source.getSource().getFile().getAbsolutePath())); copyPair.dst = target; copyPairs.add(copyPair); } for (SvnCopyPair pair : copyPairs) { File src = pair.source; File dst = pair.dst; if (getOperation().isMove() && src.getAbsolutePath().equals(dst.getAbsolutePath())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot move path ''{0}'' into itself", src); SVNErrorManager.error(err, SVNLogType.WC); } if (SVNWCUtils.isChild(src, dst)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot copy path ''{0}'' into its own child ''{1}''", src, dst); SVNErrorManager.error(err, SVNLogType.WC); } } if (getOperation().isMove()) { for (SvnCopyPair pair : copyPairs) { File src = pair.source; try { Structure<ExternalNodeInfo> externalInfo = SvnWcDbExternals.readExternal(context, src, src, ExternalNodeInfo.kind); if (externalInfo.hasValue(ExternalNodeInfo.kind) && externalInfo.get(ExternalNodeInfo.kind) != SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CANNOT_MOVE_FILE_EXTERNAL, "Cannot move the external at ''{0}''; please edit the svn:externals property on ''{1}''.", src); SVNErrorManager.error(err, SVNLogType.WC); } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) { throw e; } } } } verifyPaths(copyPairs, getOperation().isMakeParents(), getOperation().isMove()); if (getOperation().isMove()) { move(copyPairs); } else { File dstAncestor = null; if (copyPairs.size() >= 1) { dstAncestor = copyPairs.iterator().next().dst; } if (copyPairs.size() > 1 && dstAncestor != null) { dstAncestor = SVNFileUtil.getParentFile(dstAncestor); } dstAncestor = dstAncestor.getAbsoluteFile(); dstAncestor = context.acquireWriteLock(dstAncestor, false, true); try { for (SvnCopyPair copyPair : copyPairs) { checkCancelled(); File dstPath = SVNFileUtil.createFilePath(copyPair.dstParent, copyPair.baseName); copy(context, copyPair.source, dstPath, getOperation().isVirtual()); } } finally { context.releaseWriteLock(dstAncestor); } } return null; } private void move(Collection<SvnCopyPair> pairs) throws SVNException { for (SvnCopyPair copyPair : pairs) { Collection<File> lockPaths = new HashSet<File>(); Collection<File> lockedPaths = new HashSet<File>(); checkCancelled(); File sourceParent = new File(SVNPathUtil.validateFilePath(SVNFileUtil.getParentFile(copyPair.source).getAbsolutePath())); if (sourceParent.equals(copyPair.dstParent) || SVNWCUtils.isChild(sourceParent, copyPair.dstParent)) { lockPaths.add(sourceParent); } else if (SVNWCUtils.isChild(copyPair.dstParent, sourceParent)) { lockPaths.add(copyPair.dstParent); } else { lockPaths.add(sourceParent); lockPaths.add(copyPair.dstParent); } try { for (File file : lockPaths) { lockedPaths.add(getWcContext().acquireWriteLock(file, false, true)); } move(getWcContext(), copyPair.source, SVNFileUtil.createFilePath(copyPair.dstParent, copyPair.baseName), getOperation().isVirtual()); } finally { for (File file : lockedPaths) { getWcContext().releaseWriteLock(file); } } } } private void verifyPaths(Collection<SvnCopyPair> copyPairs, boolean makeParents, boolean move) throws SVNException { for (SvnCopyPair copyPair : copyPairs) { SVNFileType srcType = SVNFileType.getType(copyPair.source); SVNFileType dstType = SVNFileType.getType(copyPair.dst); if (getOperation().isVirtual()) { verifyPathsExistenceForVirtualCopy(copyPair.source, copyPair.dst, srcType, dstType, copyPair, move); } else { final boolean caseOnlyRename = verifyPaths(srcType, dstType, copyPair, copyPairs.size(), move); if (caseOnlyRename) { return; } } copyPair.dstParent = new File(SVNPathUtil.validateFilePath(SVNFileUtil.getParentFile(copyPair.dst).getAbsolutePath())); copyPair.baseName = SVNFileUtil.getFileName(copyPair.dst); final SVNFileType dstParentType = SVNFileType.getType(copyPair.dstParent); if (makeParents && (dstParentType == SVNFileType.NONE || getOperation().isVirtual())) { SVNFileUtil.ensureDirectoryExists(copyPair.dstParent); SvnScheduleForAddition add = getOperation().getOperationFactory().createScheduleForAddition(); add.setSingleTarget(SvnTarget.fromFile(copyPair.dstParent)); add.setDepth(getOperation().isVirtual() ? SVNDepth.EMPTY : SVNDepth.INFINITY); add.setIncludeIgnored(true); add.setForce(false); add.setAddParents(true); add.setSleepForTimestamp(false); try { add.run(); } catch (SVNException e) { if (dstParentType == SVNFileType.NONE) { SVNFileUtil.deleteAll(copyPair.dstParent, true); } throw e; } } else if (dstParentType != SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_WORKING_COPY, "Path ''{0}'' is not a directory", copyPair.dstParent); SVNErrorManager.error(err, SVNLogType.WC); } } } private boolean verifyPaths(SVNFileType srcType, SVNFileType dstType, SvnCopyPair copyPair, int copyPairsCount, boolean move) throws SVNException { if (srcType == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Path ''{0}'' does not exist", copyPair.source); SVNErrorManager.error(err, SVNLogType.WC); } if (dstType != SVNFileType.NONE) { if (move && copyPairsCount == 1) { File srcDir = SVNFileUtil.getFileDir(copyPair.source); File dstDir = SVNFileUtil.getFileDir(copyPair.dst); if (srcDir.equals(dstDir)) { // check if it is case-only rename if (copyPair.source.getName().equalsIgnoreCase(copyPair.dst.getName())) { copyPair.dstParent = new File(SVNPathUtil.validateFilePath(dstDir.getAbsolutePath())); copyPair.baseName = SVNFileUtil.getFileName(copyPair.dst); return true; } } } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Path ''{0}'' already exists", copyPair.dst); SVNErrorManager.error(err, SVNLogType.WC); } return false; } private void verifyPathsExistenceForVirtualCopy(File source, File dst, SVNFileType srcType, SVNFileType dstType, SvnCopyPair copyPair, boolean move) throws SVNException { String opName = move ? "move" : "copy"; if (move && srcType != SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "Cannot perform 'virtual' {0}: ''{1}'' still exists", new Object[] { opName, copyPair.source }); SVNErrorManager.error(err, SVNLogType.WC); } if (dstType == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "Cannot perform 'virtual' {0}: ''{1}'' does not exist", new Object[] { opName, copyPair.dst }); SVNErrorManager.error(err, SVNLogType.WC); } if (dstType == SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot perform 'virtual' {0}: ''{1}'' is a directory", new Object[] { opName, copyPair.dst }); SVNErrorManager.error(err, SVNLogType.WC); } if (!move && srcType == SVNFileType.DIRECTORY) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot perform 'virtual' {0}: ''{1}'' is a directory", new Object[] { opName, copyPair.source }); SVNErrorManager.error(err, SVNLogType.WC); } final SvnStatus dstStatus = getStatus(dst); if (dstStatus != null && (dstStatus.getNodeStatus() != SVNStatusType.STATUS_UNVERSIONED && dstStatus.getNodeStatus() != SVNStatusType.STATUS_ADDED && dstStatus.getNodeStatus() != SVNStatusType.STATUS_REPLACED)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_ATTRIBUTE_INVALID, "Cannot perform 'virtual' {0}: ''{1}'' is scheduled neither for addition nor for replacement", new Object[] { opName, dst }); SVNErrorManager.error(err, SVNLogType.WC); } final SvnStatus sourceStatus = getStatus(source); if (sourceStatus == null || sourceStatus.getNodeStatus() == SVNStatusType.STATUS_UNVERSIONED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", source); SVNErrorManager.error(err, SVNLogType.WC); } } private SvnStatus getStatus(File interestingFile) throws SVNException { final String interestingPath = SVNFileUtil.getFilePath(interestingFile); final SvnStatus[] status2 = new SvnStatus[1]; final SvnOperationFactory operationFactory = getOperation().getOperationFactory(); final SvnGetStatus status = operationFactory.createGetStatus(); status.setDepth(SVNDepth.INFINITY); status.setRemote(false); status.setReportAll(true); status.setReportIgnored(true); status.setReportExternals(false); status.setApplicalbeChangelists(null); status.addTarget(SvnTarget.fromFile(interestingFile.getParentFile())); status.setReceiver(new ISvnObjectReceiver<SvnStatus>() { public void receive(SvnTarget target, SvnStatus svnStatus) throws SVNException { if (svnStatus == null) { return; } final File path = svnStatus.getPath(); if (path == null) { return; } final String statusPath = SVNFileUtil.getFilePath(path); if (statusPath.equals(interestingPath)) { status2[0] = svnStatus; } } }); status.run(); return status2[0]; } protected void move(SVNWCContext context, File source, File dst, boolean metadataOnly) throws SVNException { copy(context, source, dst, true); if (!metadataOnly) { SVNFileUtil.rename(source, dst); } Structure<NodeInfo> nodeInfo = context.getDb().readInfo(source, NodeInfo.kind, NodeInfo.conflicted); if (nodeInfo.get(NodeInfo.kind) == SVNWCDbKind.Dir) { // TODO remove conflict markers } if (nodeInfo.is(NodeInfo.conflicted)) { // TODO remove conflict markers } SvnNgRemove.delete(getWcContext(), source, true, false, this); } protected void copy(SVNWCContext context, File source, File dst, boolean metadataOnly) throws SVNException { File dstDirectory = SVNFileUtil.getParentFile(dst); Structure<NodeInfo> srcInfo = null; try { srcInfo = context.getDb().readInfo(source, NodeInfo.status, NodeInfo.kind, NodeInfo.reposRootUrl, NodeInfo.reposUuid, NodeInfo.checksum, NodeInfo.conflicted); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", source); SVNErrorManager.error(err, SVNLogType.WC); } throw e; } ISVNWCDb.SVNWCDbStatus srcStatus = srcInfo.get(NodeInfo.status); switch (srcStatus) { case Deleted: if (!metadataOnly) { SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS, "Deleted node ''{0}'' can''t be copied.", source); SVNErrorManager.error(err1, SVNLogType.WC); } break; case Excluded: case ServerExcluded: case NotPresent: SVNErrorMessage err2 = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "The node ''{0}'' was not found.", source); SVNErrorManager.error(err2, SVNLogType.WC); default: break; } Structure<NodeInfo> dstDirInfo = context.getDb(). readInfo(dstDirectory, NodeInfo.status, NodeInfo.reposRootUrl, NodeInfo.reposUuid); SVNURL dstReposRootUrl = dstDirInfo.get(NodeInfo.reposRootUrl); String dstReposUuid = dstDirInfo.get(NodeInfo.reposUuid); SVNWCDbStatus dstDirStatus = dstDirInfo.get(NodeInfo.status); dstDirInfo.release(); SVNURL srcReposRootUrl = srcInfo.get(NodeInfo.reposRootUrl); String srcReposUuid = srcInfo.get(NodeInfo.reposUuid); ISVNWCDb.SVNWCDbKind srcKind = srcInfo.get(NodeInfo.kind); SvnChecksum srcChecksum = srcInfo.get(NodeInfo.checksum); boolean srcConflicted = srcInfo.is(NodeInfo.conflicted); if (srcReposRootUrl == null) { if (srcStatus == SVNWCDbStatus.Added) { Structure<AdditionInfo> additionInfo = SvnWcDbShared.scanAddition((SVNWCDb) context.getDb(), source); srcReposRootUrl = additionInfo.get(AdditionInfo.reposRootUrl); srcReposUuid = additionInfo.get(AdditionInfo.reposUuid); additionInfo.release(); } else { WCDbRepositoryInfo reposInfo = context.getDb().scanBaseRepository(source, RepositoryInfoField.rootUrl, RepositoryInfoField.uuid); srcReposRootUrl = reposInfo.rootUrl; srcReposUuid = reposInfo.uuid; } } if (dstReposRootUrl == null) { if (dstDirStatus == SVNWCDbStatus.Added) { Structure<AdditionInfo> additionInfo = SvnWcDbShared.scanAddition((SVNWCDb) context.getDb(), dstDirectory); dstReposRootUrl = additionInfo.get(AdditionInfo.reposRootUrl); dstReposUuid = additionInfo.get(AdditionInfo.reposUuid); additionInfo.release(); } else { WCDbRepositoryInfo reposInfo = context.getDb().scanBaseRepository(dstDirectory, RepositoryInfoField.rootUrl, RepositoryInfoField.uuid); dstReposRootUrl = reposInfo.rootUrl; dstReposUuid = reposInfo.uuid; } } if ((srcReposRootUrl != null && dstReposRootUrl != null && !srcReposRootUrl.equals(dstReposRootUrl)) || (srcReposUuid != null && dstReposUuid != null && !srcReposUuid.equals(dstReposUuid))) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy to ''{0}'', as it is not from repository ''{1}''; it is from ''{2}''", dst, srcReposRootUrl, dstReposRootUrl); SVNErrorManager.error(err, SVNLogType.WC); } if (dstDirStatus == SVNWCDbStatus.Deleted) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE, "Cannot copy to ''{0}'', as it is scheduled for deletion", dst); SVNErrorManager.error(err, SVNLogType.WC); } try { Structure<NodeInfo> dstInfo = context.getDb().readInfo(dst, NodeInfo.status); SVNWCDbStatus dstStatus = dstInfo.get(NodeInfo.status); switch (dstStatus) { case Excluded: SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "''{0}'' is already under version control but is excluded.", dst); SVNErrorManager.error(err1, SVNLogType.WC); break; case ServerExcluded: SVNErrorMessage err2 = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "''{0}'' is already under version control", dst); SVNErrorManager.error(err2, SVNLogType.WC); break; case Deleted: case NotPresent: break; default: if (!metadataOnly) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "There is already a versioned item ''{0}''", dst); SVNErrorManager.error(err, SVNLogType.WC); break; } } dstInfo.release(); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) { throw e; } } if (!metadataOnly) { SVNFileType dstType = SVNFileType.getType(dst); if (dstType != SVNFileType.NONE) { SVNErrorMessage err2 = SVNErrorMessage.create(SVNErrorCode.ENTRY_EXISTS, "''{0}'' already exists and is in the way", dst); SVNErrorManager.error(err2, SVNLogType.WC); } } File tmpDir = context.getDb().getWCRootTempDir(dst); if (srcKind == SVNWCDbKind.File || srcKind == SVNWCDbKind.Symlink) { final boolean shouldCopyBaseData = shouldCopyBaseData(context, source, metadataOnly, srcStatus); if (shouldCopyBaseData) { copyBaseDataOfFile(context, source, dst); } else { copyVersionedFile(context, source, dst, dst, tmpDir, srcChecksum, metadataOnly, srcConflicted, true); } } else { if (srcStatus == SVNWCDbStatus.Deleted && metadataOnly) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot perform 'virtual' {0}: ''{1}'' is a directory", new Object[] { getOperation().isMove() ? "move" : "copy", source }); SVNErrorManager.error(err, SVNLogType.WC); } else { copyVersionedDirectory(context, source, dst, dst, tmpDir, metadataOnly, true); } } } private boolean shouldCopyBaseData(SVNWCContext context, File source, boolean metadataOnly, SVNWCDbStatus srcStatus) throws SVNException { if (!metadataOnly) { return false; } if (srcStatus == SVNWCDbStatus.Deleted) { return true; } final SvnStatus svnStatus = SVNStatusEditor17.internalStatus(context, source); return svnStatus != null && svnStatus.getNodeStatus() == SVNStatusType.STATUS_REPLACED; } private void copyBaseDataOfFile(SVNWCContext context, File source, File dst) throws SVNException { final SVNProperties pristineProps = context.getPristineProps(source); final ISVNWCDb.WCDbBaseInfo baseInfo = context.getDb().getBaseInfo(source, ISVNWCDb.WCDbBaseInfo.BaseInfoField.changedAuthor, ISVNWCDb.WCDbBaseInfo.BaseInfoField.changedDate, ISVNWCDb.WCDbBaseInfo.BaseInfoField.changedRev, ISVNWCDb.WCDbBaseInfo.BaseInfoField.checksum, ISVNWCDb.WCDbBaseInfo.BaseInfoField.revision, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposRootUrl, ISVNWCDb.WCDbBaseInfo.BaseInfoField.reposUuid); final String changedAuthor = baseInfo.changedAuthor; final SVNDate changedDate = baseInfo.changedDate; final long changedRev = baseInfo.changedRev; final SvnChecksum checksum = baseInfo.checksum; final long revision = baseInfo.revision; final SVNURL reposRootUrl = baseInfo.reposRootUrl; final String reposUuid = baseInfo.reposUuid; final String relativePath = SVNPathUtil.getRelativePath(context.getDb().getWCRoot(source).getAbsolutePath(), source.getAbsolutePath()); context.getDb().opCopyFile(dst, pristineProps, changedRev, changedDate, changedAuthor, new File(relativePath), reposRootUrl, reposUuid, revision, checksum, null, null); final SVNEvent event = SVNEventFactory.createSVNEvent(dst, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.COPY, null, null, null); handleEvent(event); } private void copyVersionedDirectory(SVNWCContext wcContext, File source, File dst, File dstOpRoot, File tmpDir, boolean metadataOnly, boolean notify) throws SVNException { SVNSkel workItems = null; SVNFileType srcType = null; if (!metadataOnly) { srcType = SVNFileType.getType(source); File tmpDst = copyToTmpDir(source, tmpDir, false); if (tmpDst != null) { SVNSkel workItem = wcContext.wqBuildFileMove(tmpDst, dst); workItems = wcContext.wqMerge(workItems, workItem); } } wcContext.getDb().opCopy(source, dst, workItems); wcContext.wqRun(SVNFileUtil.getParentFile(dst)); if (notify) { SVNEvent event = SVNEventFactory.createSVNEvent(dst, SVNNodeKind.DIR, null, -1, SVNEventAction.COPY, SVNEventAction.COPY, null, null, 1, 1); handleEvent(event); } Set<String> diskChildren = null; if (!metadataOnly && srcType == SVNFileType.DIRECTORY) { File[] children = SVNFileListUtil.listFiles(source); diskChildren = new HashSet<String>(); for (int i = 0; children != null && i < children.length; i++) { String name = SVNFileUtil.getFileName(children[i]); if (!name.equals(SVNFileUtil.getAdminDirectoryName())) { diskChildren.add(name); } } } Set<String> versionedChildren = wcContext.getDb().readChildren(source); for (String childName : versionedChildren) { checkCancelled(); File childSrcPath = SVNFileUtil.createFilePath(source, childName); File childDstPath = SVNFileUtil.createFilePath(dst, childName); Structure<NodeInfo> childInfo = wcContext.getDb().readInfo(childSrcPath, NodeInfo.status, NodeInfo.kind, NodeInfo.checksum, NodeInfo.conflicted, NodeInfo.opRoot); if (childInfo.is(NodeInfo.opRoot)) { wcContext.getDb().opCopyShadowedLayer(childSrcPath, childDstPath); } SVNWCDbStatus childStatus = childInfo.get(NodeInfo.status); SVNWCDbKind childKind = childInfo.get(NodeInfo.kind); if (childStatus == SVNWCDbStatus.Normal || childStatus == SVNWCDbStatus.Added || childStatus == SVNWCDbStatus.Incomplete) { if (childKind == SVNWCDbKind.File) { boolean skip = false; if (childStatus == SVNWCDbStatus.Normal) { Structure<NodeInfo> baseChildInfo = SvnWcDbReader.getBaseInfo((SVNWCDb) wcContext.getDb(), childSrcPath, NodeInfo.updateRoot); skip = baseChildInfo.is(NodeInfo.updateRoot); baseChildInfo.release(); } if (!skip) { SvnChecksum checksum = childInfo.get(NodeInfo.checksum); boolean conflicted = childInfo.is(NodeInfo.conflicted); copyVersionedFile(wcContext, childSrcPath, childDstPath, dstOpRoot, tmpDir, checksum, metadataOnly, conflicted, false); } } else if (childKind == SVNWCDbKind.Dir) { copyVersionedDirectory(wcContext, childSrcPath, childDstPath, dstOpRoot, tmpDir, metadataOnly, false); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, "cannot handle node kind for ''{0}''", childSrcPath); SVNErrorManager.error(err, SVNLogType.WC); } } else if (childStatus == SVNWCDbStatus.Deleted || childStatus == SVNWCDbStatus.NotPresent || childStatus == SVNWCDbStatus.Excluded) { wcContext.getDb().opCopy(childSrcPath, childDstPath, null); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS, "Cannot copy ''{0}'' excluded by server", childSrcPath); SVNErrorManager.error(err, SVNLogType.WC); } childInfo.release(); if (diskChildren != null && (childStatus == SVNWCDbStatus.Normal || childStatus == SVNWCDbStatus.Added)) { diskChildren.remove(childName); } } if (diskChildren != null && !diskChildren.isEmpty()) { // TODO get and skip conflict markers. for (String childName : diskChildren) { checkCancelled(); File childSrcPath = SVNFileUtil.createFilePath(source, childName); File childDstPath = SVNFileUtil.createFilePath(dst, childName); File tmp = copyToTmpDir(childSrcPath, tmpDir, true); if (tmp != null) { SVNSkel moveItem = wcContext.wqBuildFileMove(SVNFileUtil.getParentFile(dst), tmp, childDstPath); getWcContext().getDb().addWorkQueue(dst, moveItem); } } getWcContext().wqRun(dst); } } private void copyVersionedFile(SVNWCContext wcContext, File source, File dst, File dstOpRoot, File tmpDir, SvnChecksum srcChecksum, boolean metadataOnly, boolean conflicted, boolean notify) throws SVNException { if (srcChecksum != null) { if (!wcContext.getDb().checkPristine(dst, srcChecksum)) { SvnChecksum md5 = wcContext.getDb().getPristineMD5(source, srcChecksum); PristineContentsInfo pristine = wcContext.getPristineContents(source, false, true); File tempFile = SVNFileUtil.createUniqueFile(tmpDir, dst.getName(), ".tmp", false); SVNFileUtil.copyFile(pristine.path, tempFile, false); wcContext.getDb().installPristine(tempFile, srcChecksum, md5); } } SVNSkel workItems = null; File toCopy = source; if (!metadataOnly) { if (conflicted) { File conflictWorking = null; List<SVNConflictDescription> conflicts = wcContext.getDb().readConflicts(source); for (SVNConflictDescription conflictDescription : conflicts) { if (conflictDescription.isTextConflict()) { conflictWorking = conflictDescription.getPath(); break; } } if (conflictWorking != null) { if (SVNFileType.getType(conflictWorking) == SVNFileType.FILE) { toCopy = conflictWorking; } } } File tmpDst = copyToTmpDir(toCopy, tmpDir, true); if (tmpDst != null) { boolean needsLock = wcContext.getProperty(source, SVNProperty.NEEDS_LOCK) != null; if (needsLock) { SVNFileUtil.setReadonly(tmpDst, false); } SVNSkel workItem = wcContext.wqBuildFileMove(tmpDst, dst); workItems = wcContext.wqMerge(workItems, workItem); SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(tmpDst)); if (kind == SVNNodeKind.FILE) { if (!wcContext.isTextModified(source, false)) { SVNSkel workItem2 = wcContext.wqBuildRecordFileinfo(dst, null); workItems = wcContext.wqMerge(workItems, workItem2); } } } } wcContext.getDb().opCopy(source, dst, workItems); wcContext.wqRun(SVNFileUtil.getParentFile(dst)); if (notify) { SVNEvent event = SVNEventFactory.createSVNEvent(dst, SVNNodeKind.FILE, null, -1, SVNEventAction.COPY, SVNEventAction.COPY, null, null, 1, 1); handleEvent(event, -1); } } private File copyToTmpDir(File source, File tmpDir, boolean recursive) throws SVNException { SVNFileType sourceType = SVNFileType.getType(source); boolean special = sourceType == SVNFileType.SYMLINK; if (sourceType == SVNFileType.NONE) { return null; } else if (sourceType == SVNFileType.UNKNOWN) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, "Source ''{0}'' is unexpected kind", source); SVNErrorManager.error(err, SVNLogType.WC); } File dstPath = SVNFileUtil.createUniqueFile(tmpDir, source.getName(), ".tmp", false); if (sourceType == SVNFileType.DIRECTORY || special) { SVNFileUtil.deleteFile(dstPath); } if (sourceType == SVNFileType.DIRECTORY) { if (recursive) { SVNFileUtil.copyDirectory(source, dstPath, true, getOperation().getEventHandler()); } else { SVNFileUtil.ensureDirectoryExists(dstPath); } } else if (!special) { SVNFileUtil.copyFile(source, dstPath, false, false); } else { SVNFileUtil.createSymlink(dstPath, SVNFileUtil.getSymlinkName(source)); } return dstPath; } private static class SvnCopyPair { File source; File dst; File dstParent; String baseName; } }