package org.tmatesoft.svn.core.internal.wc2.ng; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.patch.SVNPatch; import org.tmatesoft.svn.core.internal.wc.patch.SVNPatchFileStream; import org.tmatesoft.svn.core.internal.wc.patch.SVNPatchTargetInfo; import org.tmatesoft.svn.core.internal.wc17.SVNStatusEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc2.patch.SvnPatchFile; import org.tmatesoft.svn.core.internal.wc2.patch.SvnPatchTarget; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnPatch; import org.tmatesoft.svn.core.wc2.SvnStatus; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class SvnNgPatch extends SvnNgOperationRunner<Void, SvnPatch> { @Override protected Void run(SVNWCContext context) throws SVNException { if (getOperation().getStripCount() < 0) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.INCORRECT_PARAMS, "strip count must be positive"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } final SvnTarget firstTarget = getOperation().getFirstTarget(); if (firstTarget.isURL()) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' is not a local path", firstTarget); SVNErrorManager.error(errorMessage, SVNLogType.WC); } final File patchFile = getOperation().getPatchFile(); SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(patchFile)); if (kind == SVNNodeKind.NONE) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' does not exist", patchFile); SVNErrorManager.error(errorMessage, SVNLogType.WC); } if (kind != SVNNodeKind.FILE) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' is not a file", patchFile); SVNErrorManager.error(errorMessage, SVNLogType.WC); } final File workingCopyDirectory = firstTarget.getFile(); kind = SVNFileType.getNodeKind(SVNFileType.getType(workingCopyDirectory)); if (kind == SVNNodeKind.NONE) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' does not exist", workingCopyDirectory); SVNErrorManager.error(errorMessage, SVNLogType.WC); } if (kind != SVNNodeKind.DIR) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' is not a directory", workingCopyDirectory); SVNErrorManager.error(errorMessage, SVNLogType.WC); } File lockAbsPath = null; try { lockAbsPath = context.acquireWriteLock(workingCopyDirectory, false, false); applyPatches(patchFile, workingCopyDirectory, getOperation().isDryRun(), getOperation().getStripCount(), getOperation().isReverse(), getOperation().isIgnoreWhitespace(), getOperation().isRemoveTempFiles(), context); } catch (IOException e) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e); SVNErrorManager.error(errorMessage, SVNLogType.WC); } finally { if (lockAbsPath != null) { context.releaseWriteLock(lockAbsPath); } } return null; } private void applyPatches(File patchFile, File workingCopyDirectory, boolean dryRun, int stripCount, boolean reverse, boolean ignoreWhitespace, boolean removeTempFiles, SVNWCContext context) throws SVNException, IOException { final SvnPatchFile svnPatchFile = SvnPatchFile.openReadOnly(patchFile); try { final List<SVNPatchTargetInfo> targetInfos = new ArrayList<SVNPatchTargetInfo>(); //can we use SVNPatchTarget instead of SVNPatchTargetInfo org.tmatesoft.svn.core.internal.wc2.patch.SvnPatch patch; do { checkCancelled(); patch = org.tmatesoft.svn.core.internal.wc2.patch.SvnPatch.parseNextPatch(svnPatchFile, reverse, ignoreWhitespace); if (patch != null) { SvnPatchTarget target = SvnPatchTarget.applyPatch(patch, workingCopyDirectory, stripCount, context, ignoreWhitespace, removeTempFiles); if (!target.isFiltered()) { SVNPatchTargetInfo targetInfo = new SVNPatchTargetInfo(target.getAbsPath(), target.isDeleted()); if (!target.isSkipped()) { targetInfos.add(targetInfo); if (target.hasTextChanges() || target.isAdded() || target.getMoveTargetAbsPath() != null || target.isDeleted()) { target.installPatchedTarget(workingCopyDirectory, dryRun, context); } if (target.hasPropChanges() && !target.isDeleted()) { target.installPatchedPropTarget(dryRun, context); } target.writeOutRejectedHunks(dryRun); } target.sendPatchNotification(context); if (target.isDeleted() && !target.isSkipped()) { checkAncestorDelete(targetInfo.getLocalAbsPath(), targetInfos, workingCopyDirectory, context, dryRun); } // target.getPatch().close(); } } } while (patch != null); } catch (IOException e) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e); SVNErrorManager.error(errorMessage, SVNLogType.WC); } finally { if (svnPatchFile != null) { svnPatchFile.close(); } } } private void checkAncestorDelete(File deletedTarget, List<SVNPatchTargetInfo> targetsInfo, File applyRoot, SVNWCContext context, boolean dryRun) throws SVNException { File dirAbsPath = SVNFileUtil.getFileDir(deletedTarget); Collection<String> globalIgnores = SVNStatusEditor17.getGlobalIgnores(context.getOptions()); while (SVNPathUtil.isAncestor(SVNFileUtil.getFilePath(applyRoot), SVNFileUtil.getFilePath(dirAbsPath)) && !applyRoot.equals(dirAbsPath)) { final CanDeleteBaton statusWalker = new CanDeleteBaton(); statusWalker.localAbsPath = dirAbsPath; statusWalker.mustKeep = false; statusWalker.targetsInfo = targetsInfo; SVNStatusEditor17 editor = new SVNStatusEditor17(dirAbsPath, context, context.getOptions(), false, true, SVNDepth.INFINITY, statusWalker); try { editor.walkStatus(dirAbsPath, SVNDepth.INFINITY, true, false, false, globalIgnores); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.CEASE_INVOCATION) { throw e; } } if (statusWalker.mustKeep) { break; } if (!dryRun) { SvnNgRemove.delete(context, dirAbsPath, null, false, false, null); } SVNPatchTargetInfo targetInfo = new SVNPatchTargetInfo(dirAbsPath, true); targetsInfo.add(targetInfo); final ISVNEventHandler eventHandler = context.getEventHandler(); if (eventHandler != null) { eventHandler.handleEvent(SVNEventFactory.createSVNEvent(dirAbsPath, SVNNodeKind.DIR, null, -1, SVNEventAction.DELETE, SVNEventAction.DELETE, null, null), UNKNOWN); } dirAbsPath = SVNFileUtil.getFileDir(dirAbsPath); } } private class CanDeleteBaton implements ISvnObjectReceiver<SvnStatus> { public File localAbsPath; public boolean mustKeep; public List<SVNPatchTargetInfo> targetsInfo; public void receive(SvnTarget target, SvnStatus status) throws SVNException { if (status.getNodeStatus() == SVNStatusType.STATUS_NONE || status.getNodeStatus() == SVNStatusType.STATUS_DELETED) { //do nothing return; } else { if (localAbsPath.equals(target.getFile())) { //do nothing return; } for (SVNPatchTargetInfo targetInfo : targetsInfo) { if (targetInfo.getLocalAbsPath().equals(target.getFile())) { if (targetInfo.isDeleted()) { return; } break; } } mustKeep = true; SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CEASE_INVOCATION); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } } }