package org.tmatesoft.svn.core.internal.wc2.patch;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.*;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc.patch.*;
import org.tmatesoft.svn.core.internal.wc17.SVNStatusEditor17;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc2.ng.*;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc2.SvnScheduleForAddition;
import org.tmatesoft.svn.core.wc2.SvnStatus;
import org.tmatesoft.svn.util.SVNLogType;
import java.io.*;
import java.util.*;
public class SvnPatchTarget extends SvnTargetContent {
private static final int MAX_FUZZ = 2;
private File absPath;
private File relPath;
private File patchedAbsPath;
private File rejectAbsPath;
private File moveTargetAbsPath;
private boolean filtered;
private boolean skipped;
private boolean hasTextChanges;
private boolean added;
private boolean deleted;
private boolean hasPropChanges;
private Map<String, SvnPropertiesPatchTarget> propTargets;
private boolean special;
private boolean symlink;
private boolean replaced;
private boolean locallyDeleted;
private SVNNodeKind kindOnDisk;
private SVNNodeKind dbKind;
private boolean hasLocalModifications;
private boolean hadRejects;
private boolean hadPropRejects;
private boolean executable;
private File canonPathFromPatchfile;
private String eolStr;
private SVNPatchFileStream stream;
private SVNPatchFileStream patchedStream;
private SVNPatchFileStream rejectStream;
public SvnPatchTarget() {
this.propTargets = new HashMap<String, SvnPropertiesPatchTarget>();
}
public boolean isFiltered() {
return filtered;
}
public boolean isSkipped() {
return skipped;
}
public boolean hasTextChanges() {
return hasTextChanges;
}
public boolean isAdded() {
return added;
}
public File getAbsPath() {
return absPath;
}
public File getMoveTargetAbsPath() {
return moveTargetAbsPath;
}
public boolean isDeleted() {
return deleted;
}
public boolean hasPropChanges() {
return hasPropChanges;
}
public void setSpecial(boolean special) {
this.special = special;
}
public Map<String, SvnPropertiesPatchTarget> getPropTargets() {
return propTargets;
}
public String getEolStr() {
return eolStr;
}
public File getRejectAbsPath() {
return rejectAbsPath;
}
public void setRejectAbsPath(File rejectAbsPath) {
this.rejectAbsPath = rejectAbsPath;
}
public SVNPatchFileStream getStream() {
return stream;
}
public SVNPatchFileStream getPatchedStream() {
return patchedStream;
}
public void setPatchedStream(SVNPatchFileStream patchedStream) {
this.patchedStream = patchedStream;
}
public SVNPatchFileStream getRejectStream() {
return rejectStream;
}
public void setRejectStream(SVNPatchFileStream rejectStream) {
this.rejectStream = rejectStream;
}
public static SvnPatchTarget applyPatch(SvnPatch patch, File workingCopyDirectory, int stripCount, SVNWCContext context, boolean ignoreWhitespace, boolean removeTempFiles) throws SVNException, IOException {
SvnPatchTarget target = initPatchTarget(patch, workingCopyDirectory, stripCount, removeTempFiles, context);
if (target.isSkipped()) {
return target;
}
List<SvnDiffHunk> hunks = patch.getHunks();
for (SvnDiffHunk hunk : hunks) {
SvnHunkInfo hunkInfo;
int fuzz = 0;
do {
hunkInfo = target.getHunkInfo(hunk, target, fuzz, ignoreWhitespace, false);
fuzz++;
} while (hunkInfo.isRejected() && fuzz <= MAX_FUZZ && !hunkInfo.isAlreadyApplied());
target.addHunkInfo(hunkInfo);
}
for (SvnHunkInfo hunkInfo : target.getHunkInfos()) {
if (hunkInfo.isAlreadyApplied()) {
continue;
} else if (hunkInfo.isRejected()) {
rejectHunk(target, hunkInfo.getHunk(), null);
} else {
applyHunk(target, target, hunkInfo, null);
}
}
if (target.getKindOnDisk() == SVNNodeKind.FILE) {
copyLinesToTarget(target, 0);
if (!target.isEof()) {
target.setSkipped(true);
}
}
for (Map.Entry<String, SvnPropertiesPatch> entry : patch.getPropPatches().entrySet()) {
final String propName = entry.getKey();
final SvnPropertiesPatch propPatch = entry.getValue();
if (SVNProperty.SPECIAL.equals(propName)) {
target.setSpecial(true);
}
final Map<String, SvnPropertiesPatchTarget> propTargets = target.getPropTargets();
final SvnPropertiesPatchTarget propTarget = propTargets.get(propName);
final List<SvnDiffHunk> propPatchHunks = propPatch.getHunks();
for (SvnDiffHunk hunk : propPatchHunks) {
SvnHunkInfo hunkInfo;
int fuzz = 0;
do {
hunkInfo = target.getHunkInfo(hunk, propTarget, fuzz, ignoreWhitespace, true);
fuzz++;
} while (hunkInfo.isRejected() && fuzz <= MAX_FUZZ && !hunkInfo.isAlreadyApplied());
propTarget.addHunkInfo(hunkInfo);
}
}
final Map<String, SvnPropertiesPatchTarget> propTargets = target.getPropTargets();
for (Map.Entry<String, SvnPropertiesPatchTarget> entry : propTargets.entrySet()) {
SvnPropertiesPatchTarget propTarget = entry.getValue();
final List<SvnHunkInfo> hunkInfos = propTarget.getHunkInfos();
for (SvnHunkInfo hunkInfo : hunkInfos) {
if (hunkInfo.isAlreadyApplied()) {
continue;
} else if (hunkInfo.isRejected()) {
rejectHunk(target, hunkInfo.getHunk(), propTarget.getName());
} else {
applyHunk(target, propTarget, hunkInfo, propTarget.getName());
}
}
if (propTarget.isExisted()) {
copyLinesToTarget(propTarget, 0);
if (!propTarget.isEof()) {
target.setSkipped(true);
}
}
}
if (!target.isSymlink()) {
if (target.getKindOnDisk() == SVNNodeKind.FILE) {
target.getStream().close();
}
target.getPatchedStream().close();
}
if (!target.isSkipped()) {
long patchedFileSize = SVNFileUtil.getFileLength(target.getPatchedAbsPath());
long workingFileSize;
if (target.getKindOnDisk() == SVNNodeKind.FILE) {
workingFileSize = SVNFileUtil.getFileLength(target.getAbsPath());
} else {
workingFileSize = 0;
}
if (patchedFileSize == 0 && workingFileSize > 0) {
target.setDeleted(target.getDbKind() == SVNNodeKind.FILE);
} else if (patchedFileSize == 0 && workingFileSize == 0) {
if (target.getKindOnDisk() == SVNNodeKind.NONE &&
!target.hasPropChanges() &&
!target.isAdded()) {
target.setSkipped(true);
}
} else if (patchedFileSize > 0 && workingFileSize == 0) {
if (target.isLocallyDeleted()) {
target.setReplaced(true);
} else if (target.getDbKind() == SVNNodeKind.NONE) {
target.setAdded(true);
}
}
}
return target;
}
private static void rejectHunk(SvnPatchTarget target, SvnDiffHunk hunk, String propName) throws SVNException {
try {
String atat;
String textAtat = "@@";
String propAtat = "##";
if (propName != null) {
String propHeader = "Property: " + propName + "\n";
target.getRejectStream().write(propHeader);
atat = propAtat;
} else {
atat = textAtat;
}
String hunkHeader = String.format("%s -%s,%s +%s,%s %s\n",
atat,
hunk.getDirectedOriginalStart(),
hunk.getDirectedOriginalLength(),
hunk.getDirectedModifiedStart(),
hunk.getDirectedModifiedLength(),
atat);
target.getRejectStream().write(hunkHeader);
boolean[] eof = new boolean[1];
String[] eolStr = new String[1];
do {
String hunkLine = hunk.readLineDiffText(eolStr, eof);
if (!eof[0]) {
if (hunkLine.length() >= 1) {
target.getRejectStream().write(hunkLine);
}
if (eolStr[0] != null) {
target.getRejectStream().write(eolStr[0]);
}
}
} while (!eof[0]);
if (propName != null) {
target.setHadPropRejects(true);
} else {
target.setHadRejects(true);
}
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
}
private static void applyHunk(SvnPatchTarget target, SvnTargetContent targetContent, SvnHunkInfo hunkInfo, String propName) throws SVNException {
if (target.getKindOnDisk() == SVNNodeKind.FILE || propName != null) {
copyLinesToTarget(targetContent, hunkInfo.getMatchedLine() + hunkInfo.getFuzz());
int line = targetContent.getCurrentLine() + hunkInfo.getHunk().getDirectedOriginalLength() - (2 * hunkInfo.getFuzz());
targetContent.seekToLine(line);
if (targetContent.getCurrentLine() != line && !targetContent.isEof()) {
hunkInfo.setRejected(true);
rejectHunk(target, hunkInfo.getHunk(), propName);
return;
}
}
int linesRead = 0;
hunkInfo.getHunk().resetModifiedText();
boolean[] eof = new boolean[1];
do {
String[] eolStr = new String[1];
String hunkLine = hunkInfo.getHunk().readLineModifiedText(eolStr, eof);
linesRead++;
if (linesRead > hunkInfo.getFuzz() &&
linesRead <= hunkInfo.getHunk().getDirectedModifiedLength() - hunkInfo.getFuzz()) {
if (hunkLine.length() >= 1) {
targetContent.getWriteCallback().write(target.getWriteBaton(), hunkLine);
}
if (eolStr[0] != null) {
if (targetContent.getEolStyle() != SVNWCContext.SVNEolStyle.None) {
eolStr[0] = targetContent.getEolStr();
}
targetContent.getWriteCallback().write(target.getWriteBaton(), eolStr[0]);
}
}
} while (!eof[0]);
if (propName != null) {
target.setHasPropChanges(true);
} else {
target.setHasTextChanges(true);
}
}
private SvnHunkInfo getHunkInfo(SvnDiffHunk hunk, SvnTargetContent targetContent, int fuzz, boolean ignoreWhitespace, boolean isPropHunk) throws SVNException {
int originalStart = hunk.getDirectedOriginalStart();
boolean alreadyApplied = false;
int matchedLine;
if (originalStart == 0 && fuzz > 0) {
matchedLine = 0;
} else if (originalStart == 0 && !isPropHunk) {
if (getKindOnDisk() == SVNNodeKind.FILE) {
SVNFileType kind = SVNFileType.getType(getAbsPath());
boolean special = kind == SVNFileType.SYMLINK;
long fileLength = SVNFileUtil.getFileLength(getAbsPath());
if (kind == SVNFileType.FILE && !special && fileLength == 0) {
matchedLine = 1;
} else {
if (getDbKind() == SVNNodeKind.FILE) {
boolean fileMatches = targetContent.matchExistingTarget(hunk);
if (fileMatches) {
matchedLine = 1;
alreadyApplied = true;
} else {
matchedLine = 0;
}
} else {
matchedLine = 0;
}
}
} else {
matchedLine = 1;
}
} else if (originalStart == 0 && isPropHunk) {
if (targetContent.isExisted()) {
boolean propMatches = targetContent.matchExistingTarget(hunk);
if (propMatches) {
matchedLine = 1;
alreadyApplied = true;
} else {
matchedLine = 0;
}
} else {
matchedLine = 1;
}
} else if (originalStart > 0 && targetContent.isExisted()) {
int savedLine = targetContent.getCurrentLine();
targetContent.seekToLine(originalStart);
if (targetContent.getCurrentLine() != originalStart) {
matchedLine = 0;
} else {
matchedLine = targetContent.scanForMatch(hunk, true, originalStart + 1, fuzz, ignoreWhitespace, false, null);
}
if (matchedLine != originalStart) {
if (fuzz == 0) {
int modifiedStart = hunk.getDirectedModifiedStart();
if (modifiedStart == 0) {
alreadyApplied = isLocallyDeleted();
} else {
targetContent.seekToLine(modifiedStart);
matchedLine = targetContent.scanForMatch(hunk, true, modifiedStart + 1, fuzz, ignoreWhitespace, true, null);
alreadyApplied = (matchedLine == modifiedStart);
}
} else {
alreadyApplied = false;
}
if (!alreadyApplied) {
targetContent.seekToLine(1);
matchedLine = targetContent.scanForMatch(hunk, false, originalStart, fuzz, ignoreWhitespace, false, null);
if (matchedLine == 0) {
matchedLine = targetContent.scanForMatch(hunk, true, 0, fuzz, ignoreWhitespace, false, null);
}
}
}
targetContent.seekToLine(savedLine);
} else {
matchedLine = 0;
}
return new SvnHunkInfo(hunk, matchedLine, matchedLine == 0, alreadyApplied, fuzz);
}
private static void copyLinesToTarget(SvnTargetContent target, int line) throws SVNException {
while ((target.getCurrentLine() < line || line == 0) && !target.isEof()) {
String targetLine = target.readLine();
if (!target.isEof()) {
targetLine = targetLine + target.getEolStr();
}
target.getWriteCallback().write(target.getWriteBaton(), targetLine);
}
}
private static SvnPatchTarget initPatchTarget(SvnPatch patch, File workingCopyDirectory, int stripCount, boolean removeTempFiles, SVNWCContext context) throws SVNException, IOException {
boolean hasPropChanges = false;
boolean propChangesOnly = false;
for (Map.Entry<String, SvnPropertiesPatch> entry : patch.getPropPatches().entrySet()) {
final SvnPropertiesPatch propPatch = entry.getValue();
if (!hasPropChanges) {
hasPropChanges = propPatch.getHunks().size() > 0;
} else {
break;
}
}
propChangesOnly = hasPropChanges && patch.getHunks().size() == 0;
SvnPatchTarget target = new SvnPatchTarget();//empty lists are created in the constructor
target.setCurrentLine(1);
target.setEolStyle(SVNWCContext.SVNEolStyle.None);
target.setDbKind(SVNNodeKind.NONE);
target.setKindOnDisk(SVNNodeKind.NONE);
target.resolveTargetPath(chooseTargetFilename(patch), workingCopyDirectory, stripCount, propChangesOnly, context);
if (!target.isSkipped()) {
if (target.isSymlink()) {
target.setExisted(true);
target.setReadBaton(new SymlinkReadBaton(target.getAbsPath()));
final SymlinkCallbacks symlinkCallbacks = new SymlinkCallbacks(workingCopyDirectory, context);
target.setReadLineCallback(symlinkCallbacks);
target.setTellCallback(symlinkCallbacks);
target.setSeekCallback(symlinkCallbacks);
} else if (target.getKindOnDisk() == SVNNodeKind.FILE) {
target.setHasLocalModifications(context.isTextModified(target.getAbsPath(), false));
target.setExecutable(SVNFileUtil.isExecutable(target.getAbsPath()));
final Map<String, byte[]> keywords = new HashMap<String, byte[]>();
SVNWCContext.SVNEolStyle[] eolStyle = (SVNWCContext.SVNEolStyle[]) new SVNWCContext.SVNEolStyle[1];
String[] eolStr = new String[1];
if (target.getKeywords() != null) {
keywords.putAll(target.getKeywords());
}
eolStyle[0] = target.getEolStyle();
eolStr[0] = target.getEolStr();
obtainEolAndKeywordsForFile(keywords, eolStyle, eolStr, context, target.getAbsPath());
target.setKeywords(keywords);
target.setEolStyle(eolStyle[0]);
target.setEolStr(eolStr[0]);
RegularCallbacks regularCallbacks = new RegularCallbacks();
target.setExisted(true);
target.setReadLineCallback(regularCallbacks);
target.setSeekCallback(regularCallbacks);
target.setTellCallback(regularCallbacks);
target.setStream(SVNPatchFileStream.openReadOnly(target.getAbsPath()));
target.setReadBaton(target.getStream());
}
if (patch.getOperation() == SvnDiffCallback.OperationKind.Added) {
target.setAdded(true);
} else if (patch.getOperation() == SvnDiffCallback.OperationKind.Deleted) {
target.setDeleted(true);
} else if (patch.getOperation() == SvnDiffCallback.OperationKind.Moved) {
File moveTargetPath = patch.getNewFileName();
if (stripCount > 0) {
moveTargetPath = SVNPatchTarget.stripPath(moveTargetPath, stripCount);
}
final File moveTargetRelPath;
if (SVNFileUtil.isAbsolute(moveTargetPath)) {
moveTargetRelPath = SVNFileUtil.createFilePath(SVNPathUtil.getPathAsChild(SVNFileUtil.getFilePath(workingCopyDirectory),
SVNFileUtil.getFilePath(moveTargetPath)));
if (moveTargetRelPath == null) {
target.setSkipped(true);
target.setAbsPath(null);
return null;
}
} else {
moveTargetRelPath = moveTargetPath;
}
boolean underRoot = isUnderRoot(workingCopyDirectory, moveTargetRelPath);
if (!underRoot) {
target.setSkipped(true);
target.setAbsPath(null);
return target;
} else {
target.setAbsPath(SVNFileUtil.createFilePath(workingCopyDirectory, moveTargetRelPath));
}
SVNNodeKind kindOnDisk = SVNFileType.getNodeKind(SVNFileType.getType(target.getMoveTargetAbsPath()));
SVNNodeKind wcKind = context.readKind(target.getMoveTargetAbsPath(), false);
if (kindOnDisk != SVNNodeKind.NONE || wcKind != SVNNodeKind.NONE) {
target.setSkipped(true);
target.setAbsPath(null);
return target;
}
}
if (!target.isSymlink()) {
File uniqueFile = createTempFile(workingCopyDirectory, context); //TODO: remove temp files
target.setPatchedAbsPath(uniqueFile);
target.setPatchedStream(SVNPatchFileStream.openForWrite(uniqueFile));
target.setWriteBaton(target.getPatchedStream());
target.setWriteCallback(new RegularWriteCallback());
} else {
File uniqueFile = createTempFile(workingCopyDirectory, context);
target.setPatchedAbsPath(uniqueFile);
target.setWriteBaton(uniqueFile);
target.setWriteCallback(new SymlinkCallbacks(workingCopyDirectory, context));
}
target.setRejectAbsPath(createTempFile(workingCopyDirectory, context));
target.setRejectStream(SVNPatchFileStream.openForWrite(target.getRejectAbsPath()));
String diffHeader = "--- " + target.getCanonPathFromPatchfile() + "\n" + "+++ " + target.getCanonPathFromPatchfile() + "\n";
SVNFileUtil.writeToFile(target.getRejectAbsPath(), diffHeader, "UTF-8");
target.getRejectStream().write(diffHeader);
if (!target.isSkipped()) {
Map<String, SvnPropertiesPatch> propPatches = patch.getPropPatches();
for (Map.Entry<String, SvnPropertiesPatch> entry : propPatches.entrySet()) {
String propName = entry.getKey();
SvnPropertiesPatch propPatch = entry.getValue();
SvnPropertiesPatchTarget propTarget = SvnPropertiesPatchTarget.initPropTarget(propName, propPatch.getOperation(), context, target.getAbsPath());
target.putPropTarget(propName, propTarget);
}
}
}
return target;
}
private static void obtainEolAndKeywordsForFile(Map<String, byte[]> keywords,
SVNWCContext.SVNEolStyle[] eolStyle,
String[] eolStr,
SVNWCContext context, File localAbsPath) throws SVNException {
final SVNProperties actualProps = context.getActualProps(localAbsPath);
SVNPropertyValue keywordsVal = actualProps.getSVNPropertyValue(SVNProperty.KEYWORDS);
if (keywordsVal != null) {
ISVNWCDb.WCDbInfo nodeChangedInfo = context.getNodeChangedInfo(localAbsPath);
long changedRev = nodeChangedInfo.changedRev;
SVNDate changedDate = nodeChangedInfo.changedDate;
String changedAuthor = nodeChangedInfo.changedAuthor;
SVNURL url = context.getNodeUrl(localAbsPath);
SVNWCContext.SVNWCNodeReposInfo nodeReposInfo = context.getNodeReposInfo(localAbsPath);
SVNURL reposRootUrl = nodeReposInfo.reposRootUrl;
if (keywords != null) {
keywords.putAll(SVNTranslator.computeKeywords(SVNPropertyValue.getPropertyAsString(keywordsVal), url == null ? null : url.toString(), reposRootUrl == null ? null : reposRootUrl.toString(), changedAuthor, changedDate.format(), String.valueOf(changedRev), null));
}
}
SVNPropertyValue eolStyleVal = actualProps.getSVNPropertyValue(SVNProperty.EOL_STYLE);
if (eolStyleVal != null) {
String eolStyleValString = SVNPropertyValue.getPropertyAsString(eolStyleVal);
SVNWCContext.SVNEolStyleInfo eolStyleInfo = SVNWCContext.SVNEolStyleInfo.fromValue(eolStyleValString);
if (eolStr != null) {
eolStr[0] = new String(eolStyleInfo.eolStr);
}
if (eolStyle != null) {
eolStyle[0] = eolStyleInfo.eolStyle;
}
}
}
private void resolveTargetPath(File pathFromPatchFile, File workingCopyDirectory, int stripCount, boolean propChangesOnly, SVNWCContext context) throws SVNException, IOException {
final File canonPathFromPatchfile = pathFromPatchFile;
setCanonPathFromPatchfile(canonPathFromPatchfile);
if (!propChangesOnly && SVNFileUtil.getFilePath(canonPathFromPatchfile).length() == 0) {
setSkipped(true);
setAbsPath(null);
setRelPath(SVNFileUtil.createFilePath(""));
return;
}
File strippedPath;
if (stripCount > 0) {
strippedPath = SVNPatchTarget.stripPath(canonPathFromPatchfile, stripCount);
} else {
strippedPath = canonPathFromPatchfile;
}
if (SVNFileUtil.isAbsolute(strippedPath)) {
setRelPath(SVNFileUtil.createFilePath(SVNPathUtil.getPathAsChild(SVNFileUtil.getFilePath(workingCopyDirectory), SVNFileUtil.getFilePath(strippedPath))));
if (getRelPath() == null) {
setSkipped(true);
setAbsPath(null);
setRelPath(strippedPath);
return;
}
} else {
setRelPath(strippedPath);
}
boolean isUnderRoot = isUnderRoot(workingCopyDirectory, getRelPath());
if (!isUnderRoot) {
setSkipped(true);
setAbsPath(null);
return;
} else {
setAbsPath(SVNFileUtil.createFilePath(workingCopyDirectory, getRelPath()));
}
SvnStatus status;
try {
status = SVNStatusEditor17.internalStatus(context, getAbsPath());
if (status.getNodeStatus() == SVNStatusType.STATUS_IGNORED ||
status.getNodeStatus() == SVNStatusType.STATUS_UNVERSIONED ||
status.getNodeStatus() == SVNStatusType.MISSING ||
status.getNodeStatus() == SVNStatusType.OBSTRUCTED ||
status.isConflicted()) {
setSkipped(true);
return;
} else if (status.getNodeStatus() == SVNStatusType.STATUS_DELETED) {
setLocallyDeleted(true);
}
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) {
throw e;
}
setLocallyDeleted(true);
setDbKind(SVNNodeKind.NONE);
status = null;
}
if (status != null && status.getKind() != SVNNodeKind.UNKNOWN) {
setDbKind(status.getKind());
} else {
setDbKind(SVNNodeKind.NONE);
}
SVNFileType fileType = SVNFileType.getType(getAbsPath());
setSymlink(fileType == SVNFileType.SYMLINK);
setKindOnDisk(SVNFileType.getNodeKind(fileType));
if (isLocallyDeleted()) {
SVNWCContext.NodeMovedAway nodeMovedAway = context.nodeWasMovedAway(getAbsPath());
if (nodeMovedAway != null && nodeMovedAway.movedToAbsPath != null) {
setAbsPath(nodeMovedAway.movedToAbsPath);
setRelPath(SVNFileUtil.skipAncestor(workingCopyDirectory, nodeMovedAway.movedToAbsPath));
assert getRelPath() != null && getRelPath().getPath().length() > 0;
setLocallyDeleted(false);
fileType = SVNFileType.getType(getAbsPath());
setSymlink(fileType == SVNFileType.SYMLINK);
setKindOnDisk(SVNFileType.getNodeKind(fileType));
} else if (getKindOnDisk() != SVNNodeKind.NONE) {
setSkipped(true);
return;
}
}
}
private static boolean isUnderRoot(File workingCopyDirectory, File relPath) throws SVNException {
File fullPath = SVNFileUtil.createFilePath(workingCopyDirectory, relPath);
try {
String workingCopyDirectoryPath = SVNFileUtil.getFilePath(workingCopyDirectory.getCanonicalFile());
String canonicalFullPath = fullPath.getCanonicalPath();
return canonicalFullPath.equals(workingCopyDirectoryPath) || SVNPathUtil.isAncestor(workingCopyDirectoryPath, canonicalFullPath);
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
return false;
}
private static File chooseTargetFilename(SvnPatch patch) {
if (patch.getOldFileName().getPath().equals("/dev/null")) {
return patch.getNewFileName();
}
if (patch.getNewFileName().getPath().equals("/dev/null")) {
return patch.getOldFileName();
}
if (patch.getOperation() == SvnDiffCallback.OperationKind.Moved) {
return patch.getOldFileName();
}
int oldCount = SVNPathUtil.getSegmentsCount(SVNFileUtil.getFilePath(patch.getOldFileName()));
int newCount = SVNPathUtil.getSegmentsCount(SVNFileUtil.getFilePath(patch.getNewFileName()));
if (oldCount == newCount) {
oldCount = SVNPathUtil.tail(SVNFileUtil.getFilePath(patch.getOldFileName())).length();
newCount = SVNPathUtil.tail(SVNFileUtil.getFilePath(patch.getNewFileName())).length();
if (oldCount == newCount) {
oldCount = SVNFileUtil.getFilePath(patch.getOldFileName()).length();
newCount = SVNFileUtil.getFilePath(patch.getNewFileName()).length();
}
}
return (oldCount < newCount) ? patch.getOldFileName() : patch.getNewFileName();
}
private void putPropTarget(String propName, SvnPropertiesPatchTarget propTarget) {
propTargets.put(propName, propTarget);
}
private static File createTempFile(File workingCopyDirectory, SVNWCContext context) throws SVNException {
return SVNFileUtil.createUniqueFile(context.getDb().getWCRootTempDir(workingCopyDirectory), "", "", true);
}
public void installPatchedTarget(File workingCopyDirectory, boolean dryRun, SVNWCContext context) throws SVNException {
if (isDeleted()) {
if (!dryRun) {
SvnNgRemove.delete(context, getAbsPath(), null, false, false, null);
}
} else {
if (isAdded() || isReplaced()) {
File parentAbsPath = SVNFileUtil.getParentFile(getAbsPath());
SVNNodeKind parentDbKind = context.readKind(parentAbsPath, false);
if (parentDbKind == SVNNodeKind.DIR || parentDbKind == SVNNodeKind.FILE) {
if (parentDbKind != SVNNodeKind.DIR) {
setSkipped(true);
} else {
if (SVNFileType.getType(parentAbsPath) != SVNFileType.DIRECTORY) {
setSkipped(true);
}
}
} else {
createMissingParents(workingCopyDirectory, context, dryRun);
}
} else {
SVNNodeKind wcKind = context.readKind(getAbsPath(), false);
if (getKindOnDisk() == SVNNodeKind.NONE || wcKind != getKindOnDisk()) {
setSkipped(true);
}
}
if (!dryRun && !isSkipped()) {
if (isSpecial()) {
//setPatchedStream(SVNFileUtil.openFileForReading(getPatchedAbsPath()));
String linkName = SVNFileUtil.readFile(getPatchedAbsPath());
if (linkName.startsWith("link ")) {
linkName = linkName.substring("link ".length());
}
if (linkName.endsWith("\n")) {
linkName = linkName.substring(0, linkName.length() - "\n".length());
}
if (linkName.endsWith("\r")) {
linkName = linkName.substring(0, linkName.length() - "\r".length());
}
SVNFileUtil.createSymlink(getAbsPath(), linkName);
} else {
//TODO: a special method for special files? atomicity?
File dst = getMoveTargetAbsPath() != null ? getMoveTargetAbsPath() : getAbsPath();
if (SVNFileType.getType(getPatchedAbsPath()) == SVNFileType.SYMLINK) {
SVNFileUtil.deleteFile(dst);
SVNFileUtil.copySymlink(getPatchedAbsPath(), dst);
} else {
boolean repairEol = getEolStyle() == SVNWCContext.SVNEolStyle.Fixed || getEolStyle() == SVNWCContext.SVNEolStyle.Native;
SVNTranslator.translate(getPatchedAbsPath(), dst, null, getEolStr() == null ? null : getEolStr().getBytes(), getKeywords(), false, true);
}
}
if (isAdded() || isReplaced()) {
SvnNgAdd add = new SvnNgAdd();
add.setWcContext(context);
add.addFromDisk(getAbsPath(), null, false);
}
SVNFileUtil.setExecutable(getMoveTargetAbsPath() != null ? getMoveTargetAbsPath() : getAbsPath(), isExecutable());
if (getMoveTargetAbsPath() != null) {
SvnNgWcToWcCopy svnNgWcToWcCopy = new SvnNgWcToWcCopy();
svnNgWcToWcCopy.setWcContext(context);
svnNgWcToWcCopy.move(context, getAbsPath(), getMoveTargetAbsPath(), true);
SVNFileUtil.deleteFile(getAbsPath());
}
}
}
}
private void createMissingParents(File workingCopyDirectory, SVNWCContext context, boolean dryRun) throws SVNException {
File localAbsPath = workingCopyDirectory;
File relPath = getRelPath();
String relPathString = SVNFileUtil.getFilePath(relPath);
String[] components = relPathString.split("/");
int presentComponents = 0;
for (String component : components) {
localAbsPath = SVNFileUtil.createFilePath(localAbsPath, component);
SVNNodeKind wcKind = context.readKind(localAbsPath, true);
SVNNodeKind diskKind = SVNFileType.getNodeKind(SVNFileType.getType(localAbsPath));
if (diskKind == SVNNodeKind.FILE || wcKind == SVNNodeKind.FILE) {
setSkipped(true);
break;
} else if (diskKind == SVNNodeKind.DIR) {
if (wcKind == SVNNodeKind.DIR) {
presentComponents++;
} else {
setSkipped(true);
break;
}
} else if (wcKind != SVNNodeKind.NONE) {
setSkipped(true);
break;
} else {
break;
}
}
if (!isSkipped()) {
localAbsPath = workingCopyDirectory;
for (int i = 0; i < presentComponents; i++) {
String component = components[i];
localAbsPath = SVNFileUtil.createFilePath(localAbsPath, component);
}
if (!dryRun && presentComponents < components.length - 1) {
SVNFileUtil.ensureDirectoryExists(SVNFileUtil.createFilePath(workingCopyDirectory, SVNFileUtil.getFileDir(getRelPath())));
}
for (int i = presentComponents; i < components.length - 1; i++) {
String component = components[i];
localAbsPath = SVNFileUtil.createFilePath(localAbsPath, component);
if (dryRun) {
ISVNEventHandler eventHandler = context.getEventHandler();
if (eventHandler != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.DIR, null, -1, SVNEventAction.ADD, SVNEventAction.ADD, null, null);
eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN);
}
} else {
ISVNCanceller canceller = context.getEventHandler();
if (canceller != null) {
canceller.checkCancelled();
}
SvnNgAdd add = new SvnNgAdd();
add.setWcContext(context);
add.addFromDisk(localAbsPath, null, true);
}
}
}
}
public void installPatchedPropTarget(boolean dryRun, SVNWCContext context) throws SVNException {
final Map<String, SvnPropertiesPatchTarget> propTargets = getPropTargets();
for (Map.Entry<String, SvnPropertiesPatchTarget> entry : propTargets.entrySet()) {
SvnPropertiesPatchTarget propTarget = entry.getValue();
ISVNCanceller canceller = context.getEventHandler();
if (canceller != null) {
canceller.checkCancelled();
}
if (propTarget.getOperation() == SvnDiffCallback.OperationKind.Deleted) {
if (!dryRun) {
SvnNgPropertiesManager.setProperty(context, getAbsPath(), propTarget.getName(), null, SVNDepth.EMPTY, true, null, null);
}
continue;
}
if (!hasTextChanges() && getKindOnDisk() == SVNNodeKind.NONE && !isAdded()) {
if (!dryRun) {
SVNFileUtil.createEmptyFile(getAbsPath());
SvnNgAdd add = new SvnNgAdd();
add.setWcContext(context);
add.addFromDisk(getAbsPath(), null, false);
}
setAdded(true);
}
SVNPropertyValue propVal;
if (propTarget.getValue() != null && SVNPropertyValue.getPropertyAsBytes(propTarget.getValue()).length != 0 &&
propTarget.getPatchedValue() != null && SVNPropertyValue.getPropertyAsBytes(propTarget.getPatchedValue()).length == 0) {
propVal = null;
} else {
propVal = propTarget.getPatchedValue();
}
try {
if (dryRun) {
SVNPropertyValue canonicalPropertyValue = SVNPropertiesManager.validatePropertyValue(getAbsPath(), getDbKind(), propTarget.getName(), propVal, true, null, null);
} else {
SvnNgPropertiesManager.setProperty(context, getAbsPath(), propTarget.getName(), propVal, SVNDepth.EMPTY, true, null, null);
}
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() == SVNErrorCode.ILLEGAL_TARGET ||
e.getErrorMessage().getErrorCode() == SVNErrorCode.NODE_UNEXPECTED_KIND ||
e.getErrorMessage().getErrorCode() == SVNErrorCode.IO_UNKNOWN_EOL ||
e.getErrorMessage().getErrorCode() == SVNErrorCode.BAD_MIME_TYPE ||
e.getErrorMessage().getErrorCode() == SVNErrorCode.CLIENT_INVALID_EXTERNALS_DESCRIPTION) {
for (SvnHunkInfo hunkInfo : propTarget.getHunkInfos()) {
hunkInfo.setRejected(true);
rejectHunk(this, hunkInfo.getHunk(), propTarget.getName());
}
} else {
throw e;
}
}
}
}
public void writeOutRejectedHunks(boolean dryRun) throws SVNException {
try {
getRejectStream().close();
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
if (!dryRun && (hadRejects() || hadPropRejects())) {
SVNFileUtil.copyFile(getRejectAbsPath(), SVNFileUtil.createFilePath(SVNFileUtil.getFilePath(getAbsPath()) + ".svnpatch.rej"), false);
}
}
public void sendPatchNotification(SVNWCContext context) throws SVNException {
ISVNEventHandler eventHandler = context.getEventHandler();
if (eventHandler == null) {
return;
}
SVNEventAction action;
if (isSkipped()) {
action = SVNEventAction.SKIP;
} else if (isDeleted()) {
action = SVNEventAction.DELETE;
} else if (isAdded() || isReplaced() || getMoveTargetAbsPath() != null) {
action = SVNEventAction.ADD;
} else {
action = SVNEventAction.PATCH;
}
File eventPath;
if (getMoveTargetAbsPath() != null) {
eventPath = getMoveTargetAbsPath();
} else {
eventPath = getAbsPath() != null ? getAbsPath() : getRelPath();
}
SVNStatusType contentState = SVNStatusType.UNKNOWN;
SVNStatusType propState = SVNStatusType.UNKNOWN;
if (action == SVNEventAction.SKIP) {
if (getDbKind() == SVNNodeKind.NONE || getDbKind() == SVNNodeKind.UNKNOWN) {
contentState = SVNStatusType.MISSING;
} else if (getDbKind() == SVNNodeKind.DIR) {
contentState = SVNStatusType.OBSTRUCTED;
} else {
contentState = SVNStatusType.UNKNOWN;
}
} else {
if (hadRejects()) {
contentState = SVNStatusType.CONFLICTED;
} else if (hasLocalModifications()) {
contentState = SVNStatusType.MERGED;
} else if (hasTextChanges()) {
contentState = SVNStatusType.CHANGED;
}
if (hadPropRejects()) {
propState = SVNStatusType.CONFLICTED;
} else if (hasPropChanges()) {
propState = SVNStatusType.CHANGED;
}
}
SVNEvent event = SVNEventFactory.createSVNEvent(eventPath, SVNNodeKind.FILE, null, -1, contentState, propState, null, action, action, null, null);
eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN);
if (action == SVNEventAction.PATCH) {
for (SvnHunkInfo hunkInfo : getHunkInfos()) {
sendHunkNotification(hunkInfo, null, context);
}
final Map<String, SvnPropertiesPatchTarget> propTargets = getPropTargets();
for (Map.Entry<String, SvnPropertiesPatchTarget> entry : propTargets.entrySet()) {
SvnPropertiesPatchTarget propTarget = entry.getValue();
List<SvnHunkInfo> hunks = propTarget.getHunkInfos();
for (SvnHunkInfo hunkInfo : hunks) {
if (propTarget.getOperation() != SvnDiffCallback.OperationKind.Added &&
propTarget.getOperation() != SvnDiffCallback.OperationKind.Deleted) {
sendHunkNotification(hunkInfo, propTarget.getName(), context);
}
}
}
}
if (getMoveTargetAbsPath() != null) {
event = SVNEventFactory.createSVNEvent(getAbsPath(), SVNNodeKind.FILE, null, -1, SVNEventAction.DELETE, SVNEventAction.DELETE, null, null);
eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN);
}
}
private void sendHunkNotification(SvnHunkInfo hunkInfo, String propName, SVNWCContext context) throws SVNException {
SVNEventAction action;
if (hunkInfo.isAlreadyApplied()) {
action = SVNEventAction.PATCH_HUNK_ALREADY_APPLIED;
} else if (hunkInfo.isRejected()) {
action = SVNEventAction.PATCH_REJECTED_HUNK;
} else {
action = SVNEventAction.PATCH_APPLIED_HUNK;
}
SVNEvent event = SVNEventFactory.createSVNEvent(getAbsPath() != null ? getAbsPath() : getRelPath(), SVNNodeKind.UNKNOWN, null, -1, action, action, null, null);
event.setInfo(hunkInfo);
event.setPropertyName(propName);
ISVNEventHandler eventHandler = context.getEventHandler();
if (eventHandler != null) {
eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN);
}
}
private boolean hasLocalModifications() {
return hasLocalModifications;
}
private boolean hadRejects() {
return hadRejects;
}
public void setHadRejects(boolean hadRejects) {
this.hadRejects = hadRejects;
}
private boolean hadPropRejects() {
return hadPropRejects;
}
public void setHadPropRejects(boolean hadPropRejects) {
this.hadPropRejects = hadPropRejects;
}
public void setSkipped(boolean skipped) {
this.skipped = skipped;
}
public boolean isSymlink() {
return symlink;
}
public void setAdded(boolean added) {
this.added = added;
}
public void setReplaced(boolean replaced) {
this.replaced = replaced;
}
public boolean isLocallyDeleted() {
return locallyDeleted;
}
public SVNNodeKind getKindOnDisk() {
return kindOnDisk;
}
public SVNNodeKind getDbKind() {
return dbKind;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public void setDbKind(SVNNodeKind dbKind) {
this.dbKind = dbKind;
}
public void setKindOnDisk(SVNNodeKind kindOnDisk) {
this.kindOnDisk = kindOnDisk;
}
public void setExisted(boolean existed) {
this.existed = existed;
}
public void setCurrentLine(int currentLine) {
this.currentLine = currentLine;
}
public void setHasLocalModifications(boolean hasLocalModifications) {
this.hasLocalModifications = hasLocalModifications;
}
public void setExecutable(boolean executable) {
this.executable = executable;
}
public void setAbsPath(File absPath) {
this.absPath = absPath;
}
public void setRelPath(File relPath) {
this.relPath = relPath;
}
public File getCanonPathFromPatchfile() {
return canonPathFromPatchfile;
}
public void setCanonPathFromPatchfile(File canonPathFromPatchfile) {
this.canonPathFromPatchfile = canonPathFromPatchfile;
}
public File getRelPath() {
return relPath;
}
public void setLocallyDeleted(boolean locallyDeleted) {
this.locallyDeleted = locallyDeleted;
}
public void setSymlink(boolean symlink) {
this.symlink = symlink;
}
public boolean isExisted() {
return existed;
}
public int getCurrentLine() {
return currentLine;
}
public void setEolStr(String eolStr) {
this.eolStr = eolStr;
}
public boolean isReplaced() {
return replaced;
}
public boolean isSpecial() {
return special;
}
public boolean isExecutable() {
return executable;
}
public File getPatchedAbsPath() {
return patchedAbsPath;
}
public void setPatchedAbsPath(File patchedAbsPath) {
this.patchedAbsPath = patchedAbsPath;
}
public void setHasPropChanges(boolean hasPropChanges) {
this.hasPropChanges = hasPropChanges;
}
public void setHasTextChanges(boolean hasTextChanges) {
this.hasTextChanges = hasTextChanges;
}
public void setStream(SVNPatchFileStream stream) {
this.stream = stream;
}
private static class RegularWriteCallback implements IWriteCallback {
public void write(Object writeBaton, String s) throws SVNException {
SVNPatchFileStream outputStream = (SVNPatchFileStream) writeBaton;
try {
outputStream.write(s);
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
}
}
private static class SymlinkCallbacks implements IWriteCallback, IRealLineCallback, ISeekCallback, ITellCallback {
private File workingCopyDirectory;
private SVNWCContext context;
public SymlinkCallbacks(File workingCopyDirectory, SVNWCContext context) {
this.workingCopyDirectory = workingCopyDirectory;
this.context = context;
}
public void write(Object writeBaton, String s) throws SVNException {
if (!s.startsWith("link ")) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, "Invalid link representation");
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
s = s.substring("link ".length());
File targetAbsPath = (File) writeBaton;
if (SVNFileType.getType(targetAbsPath) == SVNFileType.FILE) {
SVNFileUtil.deleteFile(targetAbsPath);
}
SVNFileUtil.createSymlink(targetAbsPath, s);
}
public String readLine(Object baton, String[] eolStr, boolean[] eof) throws SVNException {
if (eof != null) {
eof[0] = true;
}
if (eolStr != null) {
eolStr[0] = null;
}
SymlinkReadBaton symlinkReadBaton = (SymlinkReadBaton) baton;
if (symlinkReadBaton.isAtEof()) {
return null;
} else {
String symlinkName = SVNFileUtil.getSymlinkName(symlinkReadBaton.getAbsPath());
String symlinkContent = "link " + symlinkName;
return symlinkContent;
}
}
public void seek(Object readBaton, long offset) {
SymlinkReadBaton symlinkReadBaton = (SymlinkReadBaton) readBaton;
symlinkReadBaton.atEof = offset != 0;
}
public long tell(Object readBaton) {
SymlinkReadBaton symlinkReadBaton = (SymlinkReadBaton) readBaton;
return symlinkReadBaton.isAtEof() ? 1 : 0;
}
}
private static class SymlinkReadBaton {
private final File absPath;
private boolean atEof;
public SymlinkReadBaton(File absPath) {
this.absPath = absPath;
}
private File getAbsPath() {
return absPath;
}
private boolean isAtEof() {
return atEof;
}
}
private static class RegularCallbacks implements IRealLineCallback, ISeekCallback, ITellCallback{
public String readLine(Object baton, String[] eolStr, boolean[] eof) throws SVNException {
try {
SVNPatchFileStream inputStream = (SVNPatchFileStream) baton;
StringBuffer lineBuffer = new StringBuffer();
StringBuffer eolStrBuffer = new StringBuffer();
boolean isEof = inputStream.readLineWithEol(lineBuffer, eolStrBuffer);
if (eof != null) {
eof[0] = isEof;
}
if (eolStr != null) {
eolStr[0] = eolStrBuffer.length() == 0 ? null : eolStrBuffer.toString();
}
return lineBuffer.toString();
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
return null;
}
@Override
public void seek(Object baton, long offset) throws SVNException {
SVNPatchFileStream inputStream = (SVNPatchFileStream) baton;
try {
inputStream.setSeekPosition(offset);
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
}
public long tell(Object baton) throws SVNException {
SVNPatchFileStream inputStream = (SVNPatchFileStream) baton;
try {
return inputStream.getSeekPosition();
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
return -1;
}
}
}