package org.tmatesoft.svn.core.internal.wc17.db; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; 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.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.util.SVNSkel; import org.tmatesoft.svn.core.internal.util.SVNURLUtil; import org.tmatesoft.svn.core.internal.wc.*; import org.tmatesoft.svn.core.internal.wc17.SVNUpdateEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.MergeInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.MergePropertiesInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.WritableBaseInfo; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbBaseInfo; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbBaseInfo.BaseInfoField; import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; 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.SvnChecksum; import org.tmatesoft.svn.util.SVNLogType; public class SvnExternalUpdateEditor implements ISVNUpdateEditor { private long targetRevision; private String name; private long originalRevision; private File localAbsPath; private SVNWCContext context; private SvnChecksum originalChecksum; private File wriAbsPath; private SVNDeltaProcessor deltaProcessor; private WritableBaseInfo currentBase; private SVNProperties davPropChanges; private SVNProperties entryPropChanges; private SVNProperties regularPropChanges; private SVNURL url; private SVNURL reposRootUrl; private long changedRev; private String changedAuthor; private SVNDate changedDate; private String[] extPatterns; private boolean useCommitTimes; private String reposUuid; private File recordAncestorAbspath; private File recordedReposRelPath; private long recordedPegRevision; private long recordedRevision; private SvnChecksum newMd5Checksum; private SvnChecksum newSha1Checksum; private boolean fileClosed; private Map<String, SVNProperties> iprops; private boolean added; public static ISVNUpdateEditor createEditor(SVNWCContext context, File localAbsPath, File wriAbsPath, SVNURL url, SVNURL reposRootUrl, String reposUuid, Map<String, SVNProperties> iprops, boolean useCommitTimes, String[] preservedExts, File recordAncestorAbsPath, SVNURL recordedUrl, SVNRevision recordedPegRev, SVNRevision recordedRev) { SvnExternalUpdateEditor editor = new SvnExternalUpdateEditor(); editor.context = context; editor.localAbsPath = localAbsPath; editor.wriAbsPath = wriAbsPath != null ? wriAbsPath : SVNFileUtil.getParentFile(localAbsPath); editor.url = url; editor.reposRootUrl = reposRootUrl; editor.reposUuid = reposUuid; editor.name = SVNFileUtil.getFileName(localAbsPath); editor.useCommitTimes = useCommitTimes; editor.extPatterns = preservedExts; editor.recordAncestorAbspath = recordAncestorAbsPath; editor.recordedReposRelPath = SVNFileUtil.createFilePath(SVNURLUtil.getRelativeURL(reposRootUrl, recordedUrl, false)); editor.recordedPegRevision = recordedPegRev != null ? recordedPegRev.getNumber() : SVNWCContext.INVALID_REVNUM; editor.recordedRevision = recordedRev != null ? recordedRev.getNumber() : SVNWCContext.INVALID_REVNUM; editor.iprops = iprops; return (ISVNUpdateEditor) SVNCancellableEditor.newInstance(editor, context.getEventHandler(), null); } public SvnExternalUpdateEditor() { deltaProcessor = new SVNDeltaProcessor(); } public void targetRevision(long revision) throws SVNException { targetRevision = revision; } public void openRoot(long revision) throws SVNException { } public void deleteEntry(String path, long revision) throws SVNException { } public void absentDir(String path) throws SVNException { } public void absentFile(String path) throws SVNException { } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { } public void openDir(String path, long revision) throws SVNException { } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { } public void closeDir() throws SVNException { } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { String name = SVNPathUtil.tail(path); if (!name.equals(this.name)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "This editor can only update ''{0}''", localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } originalRevision = SVNWCContext.INVALID_REVNUM; this.added = true; } public void openFile(String path, long revision) throws SVNException { String name = SVNPathUtil.tail(path); if (!name.equals(this.name)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "This editor can only update ''{0}''", localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } WCDbBaseInfo baseInfo = context.getDb().getBaseInfo(localAbsPath, BaseInfoField.kind, BaseInfoField.revision, BaseInfoField.changedRev, BaseInfoField.changedAuthor, BaseInfoField.changedDate, BaseInfoField.checksum); originalRevision = baseInfo.revision; changedRev = baseInfo.changedRev; changedDate = baseInfo.changedDate; changedAuthor = baseInfo.changedAuthor; originalChecksum = baseInfo.checksum; if (baseInfo.kind != SVNWCDbKind.File) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS, "Node ''{0}'' is not existing file external", localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } } public void changeFileProperty(String path, String propertyName, SVNPropertyValue propertyValue) throws SVNException { if (SVNProperty.isRegularProperty(propertyName)) { if (regularPropChanges == null) { regularPropChanges = new SVNProperties(); } regularPropChanges.put(propertyName, propertyValue); } else if (SVNProperty.isEntryProperty(propertyName)) { if (entryPropChanges == null) { entryPropChanges = new SVNProperties(); } entryPropChanges.put(propertyName, propertyValue); } else if (SVNProperty.isWorkingCopyProperty(propertyName)) { if (davPropChanges == null) { davPropChanges = new SVNProperties(); } davPropChanges.put(propertyName, propertyValue); } } public void closeFile(String path, String expectedMd5Digest) throws SVNException { fileClosed = true; if (expectedMd5Digest != null) { SvnChecksum expectedMd5Checksum = new SvnChecksum(SvnChecksum.Kind.md5, expectedMd5Digest); SvnChecksum actualMd5Checksum = newMd5Checksum; if (actualMd5Checksum == null) { actualMd5Checksum = originalChecksum; if (actualMd5Checksum != null && actualMd5Checksum.getKind() != SvnChecksum.Kind.md5) { actualMd5Checksum = context.getDb().getPristineMD5(wriAbsPath, actualMd5Checksum); } } if (!expectedMd5Checksum.equals(actualMd5Checksum)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Checksum mismatch for ''{0}'':\n" + " expected: ''{1}''\n" + " actual: ''{2}''\n", localAbsPath, expectedMd5Checksum, actualMd5Checksum); SVNErrorManager.error(err, SVNLogType.WC); } } if (newSha1Checksum != null) { context.getDb().installPristine(currentBase.tempBaseAbspath, currentBase.getSHA1Checksum(), currentBase.getMD5Checksum()); SVNFileUtil.deleteFile(currentBase.tempBaseAbspath); } boolean added = originalRevision == SVNWCContext.INVALID_REVNUM; File reposRelPath = SVNFileUtil.createFilePath(SVNURLUtil.getRelativeURL(reposRootUrl, url, false)); SvnChecksum newChecksum = null; SVNProperties actualProperties = null; SVNProperties baseProperties = new SVNProperties(); SVNSkel allWorkItems = null; SVNSkel conflictSkel = null; if (!added) { newChecksum = originalChecksum; baseProperties = context.getDb().getBaseProps(localAbsPath); actualProperties = context.getDb().readProperties(localAbsPath); } if (actualProperties == null) { actualProperties = new SVNProperties(); } if (newSha1Checksum != null) { newChecksum = newSha1Checksum; } for (String propName : entryPropChanges.nameSet()) { SVNPropertyValue value = entryPropChanges.getSVNPropertyValue(propName); if (value == null) { continue; } if (SVNProperty.COMMITTED_DATE.equals(propName)) { changedDate = SVNDate.parseDate(value.getString()); } else if (SVNProperty.LAST_AUTHOR.equals(propName)) { changedAuthor = value.getString(); } else if (SVNProperty.COMMITTED_REVISION.equals(propName)) { try { changedRev = Long.parseLong(value.getString()); } catch (NumberFormatException nfe) { } } } SVNStatusType contentState = SVNStatusType.UNKNOWN; SVNStatusType propState = SVNStatusType.UNKNOWN; SVNProperties newActualProperties; SVNProperties newPristineProperties; if (regularPropChanges != null) { MergePropertiesInfo mergeInfo = new MergePropertiesInfo(); mergeInfo.newActualProperties = new SVNProperties(); mergeInfo.newBaseProperties = new SVNProperties(); newPristineProperties = new SVNProperties(baseProperties); newPristineProperties.putAll(regularPropChanges); newPristineProperties.removeNullValues(); mergeInfo = context.mergeProperties2(mergeInfo, localAbsPath, SVNWCDbKind.File, null, null, null, baseProperties, actualProperties, regularPropChanges, true, false, context.getOptions().getConflictResolver()); propState = mergeInfo.mergeOutcome; newActualProperties = mergeInfo.newActualProperties; conflictSkel = mergeInfo.conflictSkel; } else { newActualProperties = baseProperties; newPristineProperties = actualProperties; } boolean installPristine = false; boolean obstructed = false; if (newSha1Checksum != null) { SVNFileType fileType = SVNFileType.getType(localAbsPath); SVNNodeKind kind = SVNFileType.getNodeKind(fileType); if (kind == SVNNodeKind.NONE) { installPristine = true; contentState = SVNStatusType.CHANGED; } else if (kind != SVNNodeKind.FILE || (this.added && kind == SVNNodeKind.FILE)) { obstructed = true; contentState = SVNStatusType.UNCHANGED; } else { boolean isModified = context.isTextModified(localAbsPath, false); if (!isModified) { installPristine = true; contentState = SVNStatusType.CHANGED; } else { SVNProperties propChanges = new SVNProperties(); if (regularPropChanges != null) { propChanges.putAll(regularPropChanges); } if (entryPropChanges != null) { propChanges.putAll(entryPropChanges); } if (davPropChanges != null) { propChanges.putAll(davPropChanges); } MergeInfo mergeInfo = new MergeInfo(); mergeInfo.conflictSkel = null; MergeInfo outcome = SVNUpdateEditor17.performFileMerge(mergeInfo, context, localAbsPath, wriAbsPath, newChecksum, originalChecksum, actualProperties, extPatterns, originalRevision, targetRevision, propChanges); if (outcome.workItems != null) { allWorkItems = context.wqMerge(allWorkItems, outcome.workItems); } if (outcome.foundTextConflict) { contentState = SVNStatusType.CONFLICTED; } else { contentState = SVNStatusType.MERGED; } conflictSkel = outcome.conflictSkel; } } if (installPristine) { SVNSkel workItem = context.wqBuildFileInstall(localAbsPath, null, useCommitTimes, true); allWorkItems = context.wqMerge(allWorkItems, workItem); } } else { contentState = SVNStatusType.UNCHANGED; } if (conflictSkel != null) { SvnWcDbConflicts.conflictSkelOpSwitch(conflictSkel, new SVNConflictVersion(reposRootUrl, SVNFileUtil.getFilePath(reposRelPath), this.originalRevision, SVNNodeKind.FILE), new SVNConflictVersion(reposRootUrl, SVNFileUtil.getFilePath(reposRelPath), this.targetRevision, SVNNodeKind.FILE)); SVNSkel workItem = SvnWcDbConflicts.createConflictMarkers(context.getDb(), localAbsPath, conflictSkel); allWorkItems = context.wqMerge(allWorkItems, workItem); } if (davPropChanges != null) { davPropChanges.removeNullValues(); } SvnWcDbExternals.addExternalFile(context, localAbsPath, wriAbsPath, reposRelPath, reposRootUrl, reposUuid, targetRevision, newPristineProperties, iprops, changedRev, changedDate, changedAuthor, newChecksum, davPropChanges, recordAncestorAbspath, recordedReposRelPath, recordedPegRevision, recordedRevision, true, newActualProperties, false, conflictSkel, allWorkItems); this.iprops = null; context.wqRun(wriAbsPath); if (context.getEventHandler() != null) { SVNEventAction action = null; if (!this.added) { action = obstructed ? SVNEventAction.UPDATE_SHADOWED_UPDATE : SVNEventAction.UPDATE_UPDATE; } else { action = obstructed ? SVNEventAction.UPDATE_SHADOWED_ADD : SVNEventAction.UPDATE_ADD; } String mimeType = context.getProperty(localAbsPath, SVNProperty.MIME_TYPE); SVNEvent event = SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.FILE, mimeType, targetRevision, contentState, propState, null, action, null, null, null); event.setPreviousRevision(originalRevision); context.getEventHandler().handleEvent(event, -1); } } public SVNCommitInfo closeEdit() throws SVNException { if (!fileClosed || iprops != null) { Map<File, Map<String, SVNProperties>> wcIprops = iprops != null ? new HashMap<File, Map<String,SVNProperties>>() : null; if (wcIprops != null) { wcIprops.put(localAbsPath, iprops); } context.getDb().opBumpRevisionPostUpdate(localAbsPath, SVNDepth.INFINITY, null, null, null, targetRevision, null, wcIprops, context.getEventHandler()); } return null; } public void abortEdit() throws SVNException { } public void applyTextDelta(String path, String baseChecksumDigest) throws SVNException { InputStream source = SVNFileUtil.DUMMY_IN; if (originalChecksum != null) { if (baseChecksumDigest != null) { SvnChecksum expectedChecksum = new SvnChecksum(SvnChecksum.Kind.md5, baseChecksumDigest); SvnChecksum originalMd5; if (originalChecksum.getKind() != SvnChecksum.Kind.md5) { originalMd5 = context.getDb().getPristineMD5(wriAbsPath, originalChecksum); } else { originalMd5 = originalChecksum; } if (!expectedChecksum.equals(originalMd5)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CHECKSUM_MISMATCH, "Base checksum mismatch for ''{0}'':\n" + " expected: ''{1}''\n" + " actual: ''{2}''\n", localAbsPath, expectedChecksum, originalMd5); SVNErrorManager.error(err, SVNLogType.WC); } } source = context.getDb().readPristine(wriAbsPath, originalChecksum); } currentBase = context.openWritableBase(wriAbsPath, true, true); deltaProcessor.applyTextDelta(source, currentBase.stream, true); } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { if (currentBase == null) { return SVNFileUtil.DUMMY_OUT; } try { deltaProcessor.textDeltaChunk(diffWindow); } catch (SVNException svne) { deltaProcessor.textDeltaEnd(); SVNFileUtil.deleteFile(currentBase.tempBaseAbspath); currentBase.tempBaseAbspath = null; throw svne; } return SVNFileUtil.DUMMY_OUT; } public void textDeltaEnd(String path) throws SVNException { if (currentBase == null) { return; } deltaProcessor.textDeltaEnd(); newSha1Checksum = currentBase.getSHA1Checksum(); newMd5Checksum = currentBase.getMD5Checksum(); } public long getTargetRevision() { return targetRevision; } }