/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.wc;
import java.io.File;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNRemoteStatusEditor extends SVNStatusEditor implements ISVNEditor, ISVNStatusHandler {
private boolean myIsRootOpen;
private SVNStatus myAnchorStatus;
private DirectoryInfo myDirectoryInfo;
private FileInfo myFileInfo;
public SVNRemoteStatusEditor(ISVNOptions options, SVNWCAccess wcAccess, SVNAdminAreaInfo info, boolean noIgnore, boolean reportAll, SVNDepth depth, ISVNStatusHandler handler) throws SVNException {
super(options, wcAccess, info, noIgnore, reportAll, depth, handler);
myAnchorStatus = createStatus(info.getAnchor().getRoot());
}
@Override
public void setRepositoryInfo(SVNURL root, Map repositoryLocks) {
super.setRepositoryInfo(root, repositoryLocks);
if (myAnchorStatus.getRepositoryRootURL() == null) {
myAnchorStatus.setRepositoryRootURL(myRepositoryRoot);
}
if (myAnchorStatus.getURL() != null && myAnchorStatus.getRepositoryRootURL() != null) {
myAnchorStatus.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myAnchorStatus.getRepositoryRootURL(),
myAnchorStatus.getURL(), false));
}
}
public void openRoot(long revision) throws SVNException {
myIsRootOpen = true;
myDirectoryInfo = new DirectoryInfo(null, null);
}
public void deleteEntry(String path, long revision) throws SVNException {
File file = getAnchor().getFile(path);
SVNEntry entry = getWCAccess().getVersionedEntry(file, false);
File dirPath;
String name;
if (entry.getKind() == SVNNodeKind.DIR) {
dirPath = file;
name = "";
} else {
dirPath = file.getParentFile();
name = file.getName();
}
SVNAdminArea dir = null;
try {
dir = getWCAccess().retrieve(dirPath);
} catch (SVNException e) {
SVNFileType type = SVNFileType.getType(file);
if (type == SVNFileType.NONE && e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
return;
}
throw e;
}
if (dir.getEntry(name, false) != null) {
tweakStatusHash(myDirectoryInfo, myDirectoryInfo, file, SVNStatusType.STATUS_DELETED, SVNStatusType.STATUS_NONE, null, SVNRevision.create(revision));
// set entry node kind
SVNStatus status = (SVNStatus) myDirectoryInfo.myChildrenStatuses.get(file);
if (status != null) {
status.setRemoteStatus(null, null, null, dir.getEntry(name, false).getKind());
}
}
if (myDirectoryInfo.myParent != null && !hasTarget()) {
tweakStatusHash(myDirectoryInfo.myParent, myDirectoryInfo, myDirectoryInfo.myPath, SVNStatusType.STATUS_MODIFIED, SVNStatusType.STATUS_NONE, null, null);
} else if (!hasTarget() && myDirectoryInfo.myParent == null) {
myDirectoryInfo.myIsContentsChanged = true;
}
}
public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
myDirectoryInfo.myIsAdded = true;
myDirectoryInfo.myParent.myIsContentsChanged = true;
}
public void openDir(String path, long revision) throws SVNException {
myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
}
public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException {
if (!name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
&& !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
myDirectoryInfo.myIsPropertiesChanged = true;
}
if (SVNProperty.COMMITTED_REVISION.equals(name) && value != null) {
try {
long number = Long.parseLong(value.getString());
myDirectoryInfo.myRemoteRevision = SVNRevision.create(number);
} catch (NumberFormatException nfe) {
myDirectoryInfo.myRemoteRevision = SVNRevision.UNDEFINED;
}
} else if (SVNProperty.COMMITTED_DATE.equals(name) && value != null) {
myDirectoryInfo.myRemoteDate = SVNDate.parseDate(value.getString());
} else if (SVNProperty.LAST_AUTHOR.equals(name) && value != null) {
myDirectoryInfo.myRemoteAuthor = value.getString();
}
}
public void closeDir() throws SVNException {
DirectoryInfo parent = myDirectoryInfo.myParent;
if (myDirectoryInfo.myIsAdded || myDirectoryInfo.myIsPropertiesChanged || myDirectoryInfo.myIsContentsChanged
|| (myDirectoryInfo.myRemoteRevision != null && myDirectoryInfo.myRemoteRevision != SVNRevision.UNDEFINED)) {
SVNStatusType contentsStatus;
SVNStatusType propertiesStatus;
if (myDirectoryInfo.myIsAdded) {
contentsStatus = SVNStatusType.STATUS_ADDED;
propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED : SVNStatusType.STATUS_NONE;
} else {
contentsStatus = myDirectoryInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
}
if (parent != null) {
tweakStatusHash(parent, myDirectoryInfo, myDirectoryInfo.myPath, contentsStatus, propertiesStatus, null, null);
}
}
if (parent != null && myDirectoryInfo.myDepth != SVNDepth.EXCLUDE) {
boolean wasDeleted = false;
SVNStatus dirStatus = (SVNStatus) parent.myChildrenStatuses.get(myDirectoryInfo.myPath);
if (dirStatus != null &&
(dirStatus.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED ||
dirStatus.getRemoteContentsStatus() == SVNStatusType.STATUS_REPLACED)) {
wasDeleted = true;
}
handleStatusHash(dirStatus != null ? dirStatus.getEntry() : null, myDirectoryInfo.myChildrenStatuses, wasDeleted, myDirectoryInfo.myDepth);
if (isSendableStatus(dirStatus)) {
getDefaultHandler().handleStatus(dirStatus);
}
parent.myChildrenStatuses.remove(myDirectoryInfo.myPath);
} else if (parent == null) {
if (hasTarget()) {
File targetPath = getAnchor().getFile(getAdminAreaInfo().getTargetName());
SVNStatus tgtStatus = (SVNStatus) myDirectoryInfo.myChildrenStatuses.get(targetPath);
if (tgtStatus != null) {
if (tgtStatus.getKind() == SVNNodeKind.DIR) {
SVNAdminArea dir = getWCAccess().retrieve(targetPath);
getDirStatus(null, dir, null, getDepth(), isReportAll(), isNoIgnore(), null, true, getDefaultHandler());
}
if (isSendableStatus(tgtStatus)) {
getDefaultHandler().handleStatus(tgtStatus);
}
}
} else {
handleStatusHash(myAnchorStatus.getEntry(), myDirectoryInfo.myChildrenStatuses, false, getDepth());
if (myDirectoryInfo != null && myDirectoryInfo.myParent == null) {
tweakAnchorStatus(myDirectoryInfo);
}
if (isSendableStatus(myAnchorStatus)) {
getDefaultHandler().handleStatus(myAnchorStatus);
}
}
}
myDirectoryInfo = myDirectoryInfo.myParent;
}
public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
myFileInfo = new FileInfo(myDirectoryInfo, path, true);
myDirectoryInfo.myIsContentsChanged = true;
}
public void openFile(String path, long revision) throws SVNException {
myFileInfo = new FileInfo(myDirectoryInfo, path, false);
}
public void changeFileProperty(String path, String name, SVNPropertyValue value) throws SVNException {
if (!name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
&& !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
myFileInfo.myIsPropertiesChanged = true;
}
if (SVNProperty.COMMITTED_REVISION.equals(name) && value != null) {
try {
long number = Long.parseLong(value.getString());
myFileInfo.myRemoteRevision = SVNRevision.create(number);
} catch (NumberFormatException nfe) {
myFileInfo.myRemoteRevision = SVNRevision.UNDEFINED;
}
} else if (SVNProperty.COMMITTED_DATE.equals(name) && value != null) {
myFileInfo.myRemoteDate = SVNDate.parseDate(value.getString());
} else if (SVNProperty.LAST_AUTHOR.equals(name) && value != null) {
myFileInfo.myRemoteAuthor = value.getString();
}
}
public void applyTextDelta(String path, String baseChecksum) throws SVNException {
myFileInfo.myIsContentsChanged = true;
}
public void closeFile(String path, String textChecksum) throws SVNException {
if (!(myFileInfo.myIsAdded || myFileInfo.myIsPropertiesChanged || myFileInfo.myIsContentsChanged)) {
return;
}
SVNStatusType contentsStatus;
SVNStatusType propertiesStatus;
SVNLock remoteLock = null;
if (myFileInfo.myIsAdded) {
contentsStatus = SVNStatusType.STATUS_ADDED;
propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED : SVNStatusType.STATUS_NONE;
} else {
contentsStatus = myFileInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
}
remoteLock = getLock(myFileInfo.myURL);
tweakStatusHash(myFileInfo, myFileInfo.myPath, contentsStatus, propertiesStatus, remoteLock);
myFileInfo = null;
}
public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
return null;
}
public void textDeltaEnd(String path) throws SVNException {
}
public SVNCommitInfo closeEdit() throws SVNException {
if (myIsRootOpen) {
cleanup();
} else {
super.closeEdit();
}
return new SVNCommitInfo(getTargetRevision(), null, null);
}
public void abortEdit() throws SVNException {
}
public void absentDir(String path) throws SVNException {
}
public void absentFile(String path) throws SVNException {
}
private void handleStatusHash(SVNEntry dirEntry, Map hash, boolean deleted, SVNDepth depth) throws SVNException {
ISVNStatusHandler handler = deleted ? this : getDefaultHandler();
for(Iterator paths = hash.keySet().iterator(); paths.hasNext();) {
File path = (File) paths.next();
SVNStatus status = (SVNStatus) hash.get(path);
if (status.getContentsStatus() != SVNStatusType.STATUS_OBSTRUCTED &&
status.getContentsStatus() != SVNStatusType.STATUS_MISSING &&
status.getEntry() != null && status.getKind() == SVNNodeKind.DIR &&
(depth == SVNDepth.UNKNOWN || depth == SVNDepth.IMMEDIATES ||
depth == SVNDepth.INFINITY)) {
SVNAdminArea dir = getWCAccess().retrieve(path);
SVNDepth depthMinusOne = depth;
if (depthMinusOne == SVNDepth.IMMEDIATES) {
depthMinusOne = SVNDepth.EMPTY;
}
getDirStatus(dirEntry, dir, null, depthMinusOne, isReportAll(), isNoIgnore(), null, true, handler);
}
if (deleted) {
status.setRemoteStatus(SVNStatusType.STATUS_DELETED, null, null, null);
}
if (isSendableStatus(status)) {
handler.handleStatus(status);
}
}
}
private void tweakStatusHash(FileInfo fileInfo, File path, SVNStatusType text, SVNStatusType props, SVNLock lock) throws SVNException {
Map hash = fileInfo.myParent.myChildrenStatuses;
SVNStatus status = (SVNStatus) hash.get(fileInfo.myPath);
if (status == null) {
if (text != SVNStatusType.STATUS_ADDED) {
return;
}
status = createStatus(path);
hash.put(fileInfo.myPath, status);
}
if (text == SVNStatusType.STATUS_ADDED && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
text = SVNStatusType.STATUS_REPLACED;
}
status.setRemoteStatus(fileInfo.myURL, text, props, lock, fileInfo.myRemoteKind, fileInfo.myRemoteRevision, fileInfo.myRemoteDate, fileInfo.myRemoteAuthor);
status.setRepositoryRootURL(myRepositoryRoot);
if (status.getRemoteURL() != null) {
status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myRepositoryRoot, status.getRemoteURL(), false));
}
}
private void tweakStatusHash(DirectoryInfo dirInfo, DirectoryInfo childDir, File path, SVNStatusType text, SVNStatusType props, SVNLock lock, SVNRevision revision) throws SVNException {
Map hash = dirInfo.myChildrenStatuses;
SVNStatus status = (SVNStatus) hash.get(path);
if (status == null) {
if (text != SVNStatusType.STATUS_ADDED) {
return;
}
status = createStatus(path);
hash.put(path, status);
}
if (text == SVNStatusType.STATUS_ADDED && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
text = SVNStatusType.STATUS_REPLACED;
}
if (text == SVNStatusType.STATUS_DELETED) {
SVNURL remoteURL = dirInfo.myURL;
if (childDir != null) {
if (childDir.myURL != null) {
remoteURL = childDir.myURL.appendPath(SVNFileUtil.getFileName(path), false);
}
} else {
if (dirInfo.myURL != null) {
remoteURL = dirInfo.myURL.appendPath(SVNFileUtil.getFileName(path), false);
}
}
if (revision == SVNRevision.UNDEFINED) {
revision = dirInfo.myRemoteRevision;
}
status.setRemoteStatus(remoteURL, text, props, lock, SVNNodeKind.NONE, revision, null, null);
} else if (childDir == null) {
status.setRemoteStatus(dirInfo.myURL, text, props, lock, dirInfo.myRemoteKind, dirInfo.myRemoteRevision, dirInfo.myRemoteDate, dirInfo.myRemoteAuthor);
} else {
status.setRemoteStatus(childDir.myURL, text, props, lock, childDir.myRemoteKind, childDir.myRemoteRevision, childDir.myRemoteDate, childDir.myRemoteAuthor);
}
status.setRepositoryRootURL(myRepositoryRoot);
if (status.getRemoteURL() != null) {
status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myRepositoryRoot, status.getRemoteURL(), false));
}
}
private void tweakAnchorStatus(DirectoryInfo anchorInfo) {
if (anchorInfo != null && (anchorInfo.myIsContentsChanged || anchorInfo.myIsPropertiesChanged)) {
SVNStatusType text = anchorInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
SVNStatusType props = anchorInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
myAnchorStatus.setRemoteStatus(myDirectoryInfo.myURL, text, props, null, SVNNodeKind.DIR,
myDirectoryInfo.myRemoteRevision, myDirectoryInfo.myRemoteDate, myDirectoryInfo.myRemoteAuthor);
}
}
private boolean isSendableStatus(SVNStatus status) {
if (status == null) {
return false;
}
if (status.getRemoteContentsStatus() != SVNStatusType.STATUS_NONE) {
return true;
}
if (status.getRemotePropertiesStatus() != SVNStatusType.STATUS_NONE) {
return true;
}
if (status.getRemoteLock() != null) {
return true;
}
if (status.getContentsStatus() == SVNStatusType.STATUS_IGNORED && !isNoIgnore()) {
return false;
}
if (isReportAll()) {
return true;
}
if (status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED) {
return true;
}
if (status.getContentsStatus() != SVNStatusType.STATUS_NONE && status.getContentsStatus() != SVNStatusType.STATUS_NORMAL) {
return true;
}
if (status.getPropertiesStatus() != SVNStatusType.STATUS_NONE && status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL) {
return true;
}
if (status.getTreeConflict() != null) {
return true;
}
return status.isLocked() || status.isSwitched() ||
status.getLocalLock() != null || status.getChangelistName() != null ||
status.isFileExternal();
}
private SVNStatus createStatus(File path) throws SVNException {
SVNEntry entry = getWCAccess().getEntry(path, false);
SVNEntry parentEntry = null;
if (entry != null) {
SVNAdminArea parentDir = getWCAccess().getAdminArea(path.getParentFile());
if (parentDir != null) {
parentEntry = getWCAccess().getEntry(path.getParentFile(), false);
}
}
return assembleStatus(path, entry != null ? getWCAccess().probeRetrieve(path) : null, entry,
parentEntry, SVNNodeKind.UNKNOWN, false, true, false);
}
public void handleStatus(SVNStatus status) throws SVNException {
status.setRemoteStatus(SVNStatusType.STATUS_DELETED, null, null, null);
getDefaultHandler().handleStatus(status);
}
private class DirectoryInfo implements ISVNStatusHandler {
public DirectoryInfo(String path, DirectoryInfo parent) throws SVNException {
myParent = parent;
if (myParent != null) {
myPath = getAnchor().getFile(path);
} else {
myPath = getAnchor().getRoot();
}
myName = path != null ? SVNPathUtil.tail(path) : null;
myChildrenStatuses = new TreeMap();
myURL = computeURL();
myRemoteRevision = SVNRevision.UNDEFINED;
myRemoteKind = SVNNodeKind.DIR;
if (myParent != null) {
if (myParent.myDepth == SVNDepth.IMMEDIATES) {
myDepth = SVNDepth.EMPTY;
} else if (myParent.myDepth == SVNDepth.FILES || myParent.myDepth == SVNDepth.EMPTY) {
myDepth = SVNDepth.EXCLUDE;
} else if (myParent.myDepth == SVNDepth.UNKNOWN) {
myDepth = SVNDepth.UNKNOWN;
} else {
myDepth = SVNDepth.INFINITY;
}
} else {
myDepth = getDepth();
}
// this dir's status in parent.
SVNStatus parentStatus = null;
if (myParent != null) {
parentStatus = (SVNStatus) myParent.myChildrenStatuses.get(myPath);
} else {
parentStatus = myAnchorStatus;
}
if (parentStatus != null) {
SVNStatusType textStatus = parentStatus.getContentsStatus();
if (textStatus != SVNStatusType.STATUS_UNVERSIONED &&
textStatus != SVNStatusType.STATUS_MISSING &&
textStatus != SVNStatusType.STATUS_OBSTRUCTED &&
textStatus != SVNStatusType.STATUS_EXTERNAL &&
textStatus != SVNStatusType.STATUS_IGNORED &&
parentStatus.getKind() == SVNNodeKind.DIR &&
(myDepth == SVNDepth.UNKNOWN ||
myDepth == SVNDepth.FILES ||
myDepth == SVNDepth.IMMEDIATES ||
myDepth == SVNDepth.INFINITY)) {
SVNAdminArea dir = getWCAccess().getAdminArea(myPath);
if (dir != null) {
getDirStatus(null, dir, null, SVNDepth.IMMEDIATES, true, true, null, true, this);
SVNStatus thisDirStatus = (SVNStatus)myChildrenStatuses.get(myPath);
if (thisDirStatus != null && thisDirStatus.getEntry() != null && (
myDepth == SVNDepth.UNKNOWN ||
myDepth.compareTo(parentStatus.getEntry().getDepth()) > 0)) {
myDepth = thisDirStatus.getEntry().getDepth();
}
}
}
}
}
private SVNURL computeURL() throws SVNException {
if (myURL != null) {
return myURL;
}
if (myName == null) {
return myAnchorStatus.getURL();
}
SVNStatus status = (SVNStatus) myParent.myChildrenStatuses.get(myPath);
if (status != null && status.getEntry() != null && status.getEntry().getSVNURL() != null) {
return status.getEntry().getSVNURL();
}
SVNURL url = myParent.computeURL();
return url != null ? url.appendPath(myName, false) : null;
}
public void handleStatus(SVNStatus status) throws SVNException {
myChildrenStatuses.put(status.getFile(), status);
}
public File myPath;
public String myName;
public SVNURL myURL;
public DirectoryInfo myParent;
public SVNDepth myDepth;
public SVNRevision myRemoteRevision;
public Date myRemoteDate;
public String myRemoteAuthor;
public SVNNodeKind myRemoteKind;
public boolean myIsAdded;
public boolean myIsPropertiesChanged;
public boolean myIsContentsChanged;
public Map myChildrenStatuses;
}
private class FileInfo {
public FileInfo(DirectoryInfo parent, String path, boolean added) throws SVNException {
myPath = getAnchor().getFile(path);
myName = myPath.getName();
myParent = parent;
myURL = myParent.computeURL().appendPath(myName, false);
myRemoteRevision = SVNRevision.UNDEFINED;
myRemoteKind = SVNNodeKind.FILE;
myIsAdded = added;
}
public DirectoryInfo myParent;
public File myPath;
public String myName;
public SVNURL myURL;
public boolean myIsAdded;
public boolean myIsContentsChanged;
public boolean myIsPropertiesChanged;
public SVNRevision myRemoteRevision;
public Date myRemoteDate;
public String myRemoteAuthor;
public SVNNodeKind myRemoteKind;
}
}