package org.tmatesoft.svn.core.internal.wc2.ng;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
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.wc17.SVNStatusEditor17;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.ISVNWCNodeHandler;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.*;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbLock;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus;
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.internal.wc17.db.StructureFields.NodeInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeOriginInfo;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbReader.ReplaceInfo;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.core.wc2.*;
import org.tmatesoft.svn.util.SVNLogType;
public class SvnNgCommitUtil {
private enum NodeCommitStatus {
kind,
added,
deleted,
notPresent,
excluded,
isOpRoot,
isReplaceRoot,
symlink,
reposRelPath,
revision,
originalReposRelPath,
originalRevision,
changelist,
conflicted,
updateRoot,
lockToken, propsMod
}
interface ISvnUrlKindCallback {
public SVNNodeKind getUrlKind(SVNURL url, long revision) throws SVNException;
}
public static SvnCommitPacket harvestCopyCommitables(SVNWCContext context, File path, SVNURL dst, SvnCommitPacket packet, ISvnUrlKindCallback urlKindCallback, ISvnCommitParameters commitParameters, Map<File, String> externalsStorage) throws SVNException {
SVNWCNodeReposInfo reposInfo = context.getNodeReposInfo(path);
File commitRelPath = new File(SVNURLUtil.getRelativeURL(reposInfo.reposRootUrl, dst, false));
harvestCommittables(context, path, packet, null,
reposInfo.reposRootUrl,
commitRelPath,
true, SVNDepth.INFINITY,
false, null, null,
false, false,
urlKindCallback,
commitParameters,
externalsStorage,
context.getEventHandler());
return packet;
}
public static SvnCommitPacket harvestCommittables(SVNWCContext context, SvnCommitPacket packet, Map<SVNURL, String> lockTokens,
File baseDirPath,
Collection<String> targets, int depthEmptyStart,
SVNDepth depth, boolean justLocked, Collection<String> changelists, ISvnUrlKindCallback urlKindCallback, ISvnCommitParameters commitParameters, Map<File, String> externalsStorage) throws SVNException {
Map<File, File> danglers = new HashMap<File, File>();
int i = -1;
for (String target : targets) {
i++;
File targetPath = SVNFileUtil.createFilePath(baseDirPath, target);
SVNNodeKind kind = context.readKind(targetPath, false);
if (kind == SVNNodeKind.NONE) {
try {
Structure<NodeInfo> nodeInfoStructure = context.getDb().readInfo(targetPath, NodeInfo.status);
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' is not under version control", targetPath);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
} else {
throw e;
}
}
SVNTreeConflictDescription tc = context.getTreeConflict(targetPath);
if (tc != null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT,
"Aborting commit: ''{0}'' remains in conflict", targetPath);
SVNErrorManager.error(err, SVNLogType.WC);
} else {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET,
"''{0}'' is not under version control", targetPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
}
SVNWCNodeReposInfo reposInfo = context.getNodeReposInfo(targetPath);
SVNURL repositoryRootUrl = reposInfo.reposRootUrl;
boolean added = context.isNodeAdded(targetPath);
if (added) {
File parentPath = SVNFileUtil.getParentFile(targetPath);
try {
boolean parentIsAdded = context.isNodeAdded(parentPath);
if (parentIsAdded) {
Structure<NodeOriginInfo> origin = context.getNodeOrigin(parentPath, false, NodeOriginInfo.copyRootAbsPath, NodeOriginInfo.isCopy);
if (origin.is(NodeOriginInfo.isCopy)) {
parentPath = origin.get(NodeOriginInfo.copyRootAbsPath);
}
origin.release();
danglers.put(parentPath, targetPath);
}
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT,
"''{0}'' is scheduled for addition within unversioned parent", targetPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
throw e;
}
}
bailOnTreeConflictedAncestor(context, targetPath);
if (i == depthEmptyStart) {
depth = SVNDepth.EMPTY;
}
harvestCommittables(context, targetPath, packet, lockTokens, repositoryRootUrl, null, false, depth, justLocked, changelists, danglers, false, false, urlKindCallback, commitParameters, externalsStorage, context.getEventHandler());
}
for(SVNURL root : packet.getRepositoryRoots()) {
handleDescendants(context, packet, root, new ArrayList<SvnCommitItem>(packet.getItems(root)), urlKindCallback, context.getEventHandler());
}
for(File danglingParent : danglers.keySet()) {
if (!packet.hasItem(danglingParent)) {
// TODO fail no parent event
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET,
"''{0}'' is not known to exist in the repository and is not part of the commit, yet its child ''{1}'' is part of the commit",
danglingParent.getAbsolutePath(), danglers.get(danglingParent).getAbsolutePath());
SVNErrorManager.error(err, SVNLogType.WC);
}
}
return packet;
}
private static void handleDescendants(SVNWCContext context, SvnCommitPacket packet, SVNURL rootUrl, Collection<SvnCommitItem> items, ISvnUrlKindCallback urlKindCallback, ISVNEventHandler eventHandler) throws SVNException {
for (SvnCommitItem item : items) {
if (!item.hasFlag(SvnCommitItem.ADD) || item.getCopyFromUrl() == null) {
continue;
}
if (eventHandler != null) {
eventHandler.checkCancelled();
}
Collection<File> notPresent = SvnWcDbReader.getNotPresentDescendants((SVNWCDb) context.getDb(), item.getPath());
for (File absent : notPresent) {
boolean itemFound = false;
File localAbsPath = SVNFileUtil.createFilePath(item.getPath(), absent);
for (SvnCommitItem i : items) {
if (i.getPath().equals(localAbsPath)) {
itemFound = true;
break;
}
}
if (itemFound) {
continue;
}
SVNURL url = SVNWCUtils.join(item.getCopyFromUrl(), absent);
SVNNodeKind kind = SVNNodeKind.UNKNOWN;
if (urlKindCallback != null) {
kind = urlKindCallback.getUrlKind(url, item.getCopyFromRevision());
if (kind == SVNNodeKind.NONE) {
continue;
}
}
packet.addItem(localAbsPath, rootUrl, kind, SVNWCUtils.join(item.getUrl(), absent), -1, null, -1, SvnCommitItem.DELETE);
}
}
}
public static void harvestCommittables(SVNWCContext context, File localAbsPath, SvnCommitPacket committables,
Map<SVNURL, String> lockTokens, SVNURL repositoryRootUrl, File copyModeRelPath, boolean copyModeRoot,
SVNDepth depth, boolean justLocked, Collection<String> changelists, Map<File, File> danglers, boolean skipFiles, boolean skipDirs,
ISvnUrlKindCallback urlKindCallback, ISvnCommitParameters commitParameters, Map<File, String> externalsStorage, ISVNEventHandler eventHandler) throws SVNException {
CommitStatusWalker commitStatusWalker = new CommitStatusWalker();
commitStatusWalker.rootAbsPath = localAbsPath;
commitStatusWalker.committables = committables;
commitStatusWalker.lockTokens = lockTokens;
commitStatusWalker.commitRelPath = copyModeRelPath;
commitStatusWalker.depth = depth;
commitStatusWalker.justLocked = justLocked;
commitStatusWalker.changeLists = changelists;
commitStatusWalker.danglers = danglers;
commitStatusWalker.checkUrlCallback = urlKindCallback;
commitStatusWalker.context = context;
commitStatusWalker.eventHandler = eventHandler;
commitStatusWalker.externalsStorage = externalsStorage;
SVNStatusEditor17 editor = new SVNStatusEditor17(localAbsPath, context, context.getOptions(), false, copyModeRelPath != null, depth, commitStatusWalker);
editor.walkStatus(localAbsPath, depth, copyModeRelPath != null, false, false, null);
/*
if (committables.hasItem(localAbsPath)) {
return;
}
boolean copyMode = commitRelPath != null;
if (eventHandler != null) {
eventHandler.checkCancelled();
}
Structure<NodeCommitStatus> commitStatus = getNodeCommitStatus(context, localAbsPath);
if ((skipFiles && commitStatus.get(NodeCommitStatus.kind) == SVNNodeKind.FILE) || commitStatus.is(NodeCommitStatus.excluded)) {
commitStatus.release();
return;
}
if (commitStatus.get(NodeCommitStatus.reposRelPath) == null && commitRelPath != null) {
commitStatus.set(NodeCommitStatus.reposRelPath, commitRelPath);
}
CheckSpecialInfo checkSpecial = SVNWCContext.checkSpecialPath(localAbsPath);
SVNNodeKind workingKind = checkSpecial.kind;
boolean isSpecial = checkSpecial.isSpecial;
if ((workingKind != SVNNodeKind.FILE) && (workingKind != SVNNodeKind.DIR) && (workingKind != SVNNodeKind.NONE)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNKNOWN_KIND, "Unknown entry kind for ''{0}''", localAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
boolean matchesChangelists = context.isChangelistMatch(localAbsPath, changelists);
if (workingKind != SVNNodeKind.DIR && workingKind != SVNNodeKind.NONE && !matchesChangelists) {
commitStatus.release();
return;
}
if ((!commitStatus.is(NodeCommitStatus.symlink) && isSpecial ||
(SVNFileUtil.symlinksSupported() && (commitStatus.is(NodeCommitStatus.symlink) && !isSpecial))) &&
workingKind != SVNNodeKind.NONE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, "Entry ''{0}'' has unexpectedly changed special status", localAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (copyMode &&
commitStatus.is(NodeCommitStatus.updateRoot) &&
commitStatus.get(NodeCommitStatus.kind) == SVNNodeKind.FILE) {
if (copyMode) {
commitStatus.release();
return;
}
}
if (commitStatus.is(NodeCommitStatus.conflicted) && matchesChangelists) {
ConflictInfo ci = context.getConflicted(localAbsPath, true, true, true);
if (ci.propConflicted || ci.textConflicted || ci.treeConflicted) {
if (eventHandler != null) {
// TODO failed conflict event.
}
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT, "Aborting commit: ''{0}'' remains in conflict", localAbsPath);
SVNErrorManager.error(error, SVNLogType.WC);
}
}
if (commitStatus.is(NodeCommitStatus.deleted) && !commitStatus.is(NodeCommitStatus.isOpRoot)) {
commitStatus.release();
return;
}
if (commitStatus.get(NodeCommitStatus.reposRelPath) == null) {
File reposRelPath = context.getNodeReposRelPath(localAbsPath);
commitStatus.set(NodeCommitStatus.reposRelPath, reposRelPath);
}
int stateFlags = 0;
if (commitStatus.is(NodeCommitStatus.deleted) || commitStatus.is(NodeCommitStatus.isReplaceRoot)) {
stateFlags |= SvnCommitItem.DELETE;
} else if (commitStatus.is(NodeCommitStatus.notPresent)) {
if (!copyMode) {
commitStatus.release();
return;
}
if (urlKindCallback != null) {
Structure<NodeOriginInfo> originInfo = context.getNodeOrigin(SVNFileUtil.getParentFile(localAbsPath), false, NodeOriginInfo.revision, NodeOriginInfo.reposRelpath);
File reposRelPath = SVNFileUtil.createFilePath(originInfo.<File>get(NodeOriginInfo.reposRelpath), SVNFileUtil.getFileName(localAbsPath));
SVNURL url = SVNWCUtils.join(repositoryRootUrl, reposRelPath);
long revision = originInfo.lng(NodeOriginInfo.revision);
originInfo.release();
if (urlKindCallback.getUrlKind(url, revision) == SVNNodeKind.NONE) {
commitStatus.release();
return;
}
}
stateFlags |= SvnCommitItem.DELETE;
}
File copyFromPath = null;
long copyFromRevision = -1;
if (commitStatus.is(NodeCommitStatus.added) && commitStatus.is(NodeCommitStatus.isOpRoot)) {
stateFlags |= SvnCommitItem.ADD;
if (commitStatus.get(NodeCommitStatus.originalReposRelPath) != null) {
stateFlags |= SvnCommitItem.COPY;
copyFromPath = commitStatus.get(NodeCommitStatus.originalReposRelPath);
copyFromRevision = commitStatus.lng(NodeCommitStatus.originalRevision);
}
}
if (copyMode
&& (!commitStatus.is(NodeCommitStatus.added) || copyModeRoot)
&& !((stateFlags & SvnCommitItem.DELETE) != 0)) {
long dirRevision = 0;
if (!copyModeRoot) {
dirRevision = context.getNodeBaseRev(SVNFileUtil.getParentFile(localAbsPath));
}
if (copyModeRoot || dirRevision != commitStatus.lng(NodeCommitStatus.revision)) {
stateFlags |= SvnCommitItem.ADD;
Structure<NodeOriginInfo> originInfo = context.getNodeOrigin(localAbsPath, false, NodeOriginInfo.revision, NodeOriginInfo.reposRelpath);
copyFromPath = originInfo.get(NodeOriginInfo.reposRelpath);
copyFromRevision = originInfo.lng(NodeOriginInfo.revision);
originInfo.release();
if (copyFromPath != null) {
stateFlags |= SvnCommitItem.COPY;
}
}
}
boolean textModified = false;
if ((stateFlags & SvnCommitItem.ADD) != 0) {
if (workingKind == SVNNodeKind.NONE) {
// TODO failed missing event.
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "''{0}'' is scheduled for addition, but is missing", localAbsPath);
SVNErrorManager.error(error, SVNLogType.WC);
}
if (commitStatus.get(NodeCommitStatus.kind) == SVNNodeKind.FILE) {
if ((stateFlags & SvnCommitItem.COPY) != 0) {
textModified = context.isTextModified(localAbsPath, false);
} else {
textModified = true;
}
}
} else if ((stateFlags & SvnCommitItem.DELETE) == 0) {
if (commitStatus.get(NodeCommitStatus.kind) == SVNNodeKind.FILE) {
textModified = context.isTextModified(localAbsPath, false);
}
}
boolean propsModified = commitStatus.is(NodeCommitStatus.propsMod);
if (textModified) {
stateFlags |= SvnCommitItem.TEXT_MODIFIED;
}
if (propsModified) {
stateFlags |= SvnCommitItem.PROPS_MODIFIED;
}
if (commitStatus.get(NodeCommitStatus.lockToken) != null && lockTokens != null && (stateFlags != 0 || justLocked)) {
stateFlags |= SvnCommitItem.LOCK;
}
if ((stateFlags & ~(SvnCommitItem.LOCK | SvnCommitItem.PROPS_MODIFIED | SvnCommitItem.COPY)) == 0 && matchesChangelists) {
if (workingKind == SVNNodeKind.NONE) {
if (commitParameters != null) {
ISvnCommitParameters.Action action = ISvnCommitParameters.Action.SKIP;
SVNNodeKind nodeKind = commitStatus.get(NodeCommitStatus.kind);
if (nodeKind == SVNNodeKind.DIR) {
action = commitParameters.onMissingDirectory(localAbsPath);
} else if (nodeKind == SVNNodeKind.FILE) {
action = commitParameters.onMissingFile(localAbsPath);
}
if (action == Action.DELETE) {
stateFlags |= SvnCommitItem.DELETE;
// schedule file or dir for deletion!
SvnNgRemove.delete(context, localAbsPath, null, false, false, null);
} else if (action == Action.ERROR) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Working copy {1} ''{0}'' is missing", localAbsPath, nodeKind == SVNNodeKind.DIR ? "directory" : "file");
SVNErrorManager.error(err, SVNLogType.WC);
}
}
}
}
if (stateFlags != 0 && matchesChangelists) {
SvnCommitItem item = committables.addItem(localAbsPath,
commitStatus.<SVNNodeKind>get(NodeCommitStatus.kind),
repositoryRootUrl,
SVNFileUtil.getFilePath(copyMode ? commitRelPath : commitStatus.<File>get(NodeCommitStatus.reposRelPath)),
copyMode ? -1 : commitStatus.lng(NodeCommitStatus.revision),
SVNFileUtil.getFilePath(copyFromPath),
copyFromRevision,
stateFlags);
if (item.hasFlag(SvnCommitItem.LOCK) && lockTokens != null) {
lockTokens.put(item.getUrl(), commitStatus.<String>get(NodeCommitStatus.lockToken));
}
}
if (matchesChangelists) {
if (externalsStorage != null) {
SVNProperties properties = context.getActualProps(localAbsPath);
if (properties != null) {
String externalsProperty = properties.getStringValue(SVNProperty.EXTERNALS);
if (externalsProperty != null) {
externalsStorage.put(localAbsPath, externalsProperty);
}
}
}
}
if (lockTokens != null && (stateFlags & SvnCommitItem.DELETE) != 0) {
collectLocks(context, localAbsPath, lockTokens);
}
try {
if (commitStatus.get(NodeCommitStatus.kind) != SVNNodeKind.DIR || depth.compareTo(SVNDepth.EMPTY) <= 0) {
return;
}
bailOnTreeConflictedChildren(context, localAbsPath, commitStatus.<SVNNodeKind>get(NodeCommitStatus.kind), depth, changelists);
} finally {
commitStatus.release();
}
if ((stateFlags & SvnCommitItem.DELETE) == 0 || (stateFlags & SvnCommitItem.ADD) != 0) {
SVNDepth depthBelowHere = depth;
if (depth.compareTo(SVNDepth.INFINITY) < 0) {
depthBelowHere = SVNDepth.EMPTY;
}
List<File> children = context.getChildrenOfWorkingNode(localAbsPath, copyMode);
for (File child : children) {
String name = SVNFileUtil.getFileName(child);
File childCommitRelPath = null;
if (commitRelPath != null) {
childCommitRelPath = SVNFileUtil.createFilePath(commitRelPath, name);
}
harvestCommittables(context, child, committables, lockTokens, repositoryRootUrl, childCommitRelPath, false, depthBelowHere, justLocked, changelists,
depth.compareTo(SVNDepth.FILES) < 0,
depth.compareTo(SVNDepth.IMMEDIATES) < 0,
urlKindCallback, commitParameters, externalsStorage, eventHandler);
}
}
*/
}
private static class CommitStatusWalker implements ISvnObjectReceiver<SvnStatus> {
public File rootAbsPath;
public File commitRelPath;
public SVNDepth depth;
public boolean justLocked;
public Collection<String> changeLists;
public Map<File, File> danglers;
public ISvnUrlKindCallback checkUrlCallback;
public ISVNEventHandler eventHandler;
public SVNWCContext context;
public File skipBelowAbsPath;
public Map<SVNURL, String> lockTokens;
public Map<File, String> externalsStorage;
public SvnCommitPacket committables;
public void receive(SvnTarget target, SvnStatus status) throws SVNException {
File localAbsPath = target.getFile();
int stateFlags = 0;
boolean isHarvestRoot = rootAbsPath.equals(localAbsPath);
boolean copyModeRoot = commitRelPath != null && isHarvestRoot;
File movedFromAbsPath = null;
File commitRelPath = null;
if (this.commitRelPath != null) {
commitRelPath = SVNFileUtil.createFilePath(this.commitRelPath, SVNFileUtil.skipAncestor(this.rootAbsPath, localAbsPath));
}
boolean copyMode = commitRelPath != null;
if (this.skipBelowAbsPath != null && SVNPathUtil.isAncestor(SVNFileUtil.getFilePath(this.skipBelowAbsPath), SVNFileUtil.getFilePath(localAbsPath))) {
return;
} else {
this.skipBelowAbsPath = null;
}
if (SVNStatusType.STATUS_UNVERSIONED.equals(status.getNodeStatus()) ||
SVNStatusType.STATUS_IGNORED.equals(status.getNodeStatus()) ||
SVNStatusType.STATUS_EXTERNAL.equals(status.getNodeStatus()) ||
SVNStatusType.STATUS_NONE.equals(status.getNodeStatus())) {
if (isHarvestRoot) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "''{0}'' is not under version control", localAbsPath);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
return;
} else if (SVNStatusType.STATUS_NORMAL.equals(status.getNodeStatus())) {
if (!copyMode && !status.isConflicted() && !(this.justLocked && status.getLock() != null)) {
return;
}
}
if (committables.hasItem(localAbsPath)) {
return;
}
assert (copyMode && commitRelPath != null) || (!copyMode && commitRelPath == null);
assert (copyModeRoot && copyMode) || !copyModeRoot;
boolean matchesChangeLists = (changeLists == null) || (status.getChangelist() != null && changeLists.contains(status.getChangelist()));
if (status.getKind() != SVNNodeKind.DIR && !matchesChangeLists) {
return;
}
if (status.isConflicted() && matchesChangeLists) {
if (this.eventHandler != null) {
this.eventHandler.handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.UNKNOWN, null, -1, SVNEventAction.FAILED_CONFLICT, SVNEventAction.FAILED_CONFLICT, null, null), ISVNEventHandler.UNKNOWN);
}
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT, "Aborting commit: ''{0}'' remains in conflict", localAbsPath);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
} else if (status.getNodeStatus() == SVNStatusType.STATUS_OBSTRUCTED) {
if (this.eventHandler != null) {
this.eventHandler.handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.UNKNOWN, null, -1, SVNEventAction.FAILED_OBSTRUCTION, SVNEventAction.FAILED_OBSTRUCTION, null, null), ISVNEventHandler.UNKNOWN);
}
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, "Node ''{0}'' has unexpectedly changed kind", localAbsPath);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
if (this.externalsStorage != null && matchesChangeLists) {
SVNProperties properties = context.getActualProps(localAbsPath);
if (properties != null) {
String externalsProperty = properties.getStringValue(SVNProperty.EXTERNALS);
if (externalsProperty != null) {
externalsStorage.put(localAbsPath, externalsProperty);
}
}
}
if (status.isFileExternal() && !isHarvestRoot) {
return;
}
Structure<NodeCommitStatus> nodeCommitStatus = getNodeCommitStatus(context, localAbsPath);
boolean isAdded = nodeCommitStatus.is(NodeCommitStatus.added);
boolean isDeleted = nodeCommitStatus.is(NodeCommitStatus.deleted);
boolean isReplaced = nodeCommitStatus.is(NodeCommitStatus.isReplaceRoot);
boolean isOpRoot = nodeCommitStatus.is(NodeCommitStatus.isOpRoot);
long nodeRev = nodeCommitStatus.lng(NodeCommitStatus.revision);
long originalRev = nodeCommitStatus.lng(NodeCommitStatus.originalRevision);
File originalRelPath = nodeCommitStatus.get(NodeCommitStatus.originalReposRelPath);
if (status.getNodeStatus() == SVNStatusType.STATUS_MISSING && matchesChangeLists) {
if (isAdded && isOpRoot) {
if (this.eventHandler != null) {
this.eventHandler.handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.UNKNOWN, null, -1, SVNEventAction.FAILED_MISSING, SVNEventAction.FAILED_MISSING, null, null), ISVNEventHandler.UNKNOWN);
}
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "''{0}'' is scheduled for addition, but is missing", localAbsPath);
SVNErrorManager.error(errorMessage, SVNLogType.WC);
}
return;
}
if (isDeleted && !isOpRoot) {
return;
}
if (isDeleted || isReplaced) {
stateFlags |= SvnCommitItem.DELETE;
}
File cfRelPath = null;
long cfRev = -1;
if (isAdded && isOpRoot) {
stateFlags |= SvnCommitItem.ADD;
if (originalRelPath != null) {
stateFlags |= SvnCommitItem.COPY;
cfRelPath = originalRelPath;
cfRev = originalRev;
if (status.getMovedFromPath() != null && !copyMode) {
stateFlags |= SvnCommitItem.MOVED_HERE;
movedFromAbsPath = status.getMovedFromPath();
}
}
} else if (copyMode && ((stateFlags & SvnCommitItem.DELETE) == 0)) {
long dirRev = -1;
if (!copyModeRoot && !status.isSwitched() && !isAdded) {
WCDbBaseInfo nodeBase = context.getNodeBase(SVNFileUtil.getFileDir(localAbsPath), false, false);
dirRev = nodeBase.revision;
}
if (copyModeRoot || status.isSwitched() || nodeRev != dirRev) {
stateFlags |= SvnCommitItem.ADD;
stateFlags |= SvnCommitItem.COPY;
if (status.isCopied()) {
cfRev = originalRev;
cfRelPath = originalRelPath;
} else {
cfRev = status.getRevision();
cfRelPath = SVNFileUtil.createFilePath(status.getRepositoryRelativePath());
}
}
}
if ((((stateFlags & SvnCommitItem.DELETE) == 0) || ((stateFlags & SvnCommitItem.ADD) != 0))) {
boolean textMod = false;
boolean propMod = false;
if (status.getKind() == SVNNodeKind.FILE) {
if (((stateFlags & SvnCommitItem.ADD) != 0) && (stateFlags & SvnCommitItem.COPY) == 0) {
textMod = true;
} else {
textMod = status.getTextStatus() != SVNStatusType.STATUS_NORMAL;
}
}
propMod = (status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL) && (status.getPropertiesStatus() != SVNStatusType.STATUS_NONE);
if (textMod) {
stateFlags |= SvnCommitItem.TEXT_MODIFIED;
}
if (propMod) {
stateFlags |= SvnCommitItem.PROPS_MODIFIED;
}
}
if (status.getLock() != null && lockTokens != null && (stateFlags != 0 || justLocked)) {
stateFlags |= SvnCommitItem.LOCK;
}
if (matchesChangeLists && (stateFlags != 0)) {
File reposRelPath = copyMode ? commitRelPath : SVNFileUtil.createFilePath(status.getRepositoryRelativePath());
long revision = copyMode ? -1 : nodeRev;
Map<SVNURL, String> lockTokens = this.lockTokens;
SVNLock lock = status.getLock();
assert status.getRepositoryRootUrl() != null && reposRelPath != null;
SvnCommitItem item = committables.addItem(localAbsPath, status.getKind(), status.getRepositoryRootUrl(), SVNFileUtil.getFilePath(reposRelPath), revision, SVNFileUtil.getFilePath(cfRelPath), cfRev, movedFromAbsPath, stateFlags);
if (lockTokens != null && lock != null && ((stateFlags & SvnCommitItem.LOCK) != 0)) {
lockTokens.put(item.getUrl(), lock.getID());
}
}
if (matchesChangeLists && ((stateFlags & SvnCommitItem.DELETE) != 0) && !copyMode && SVNRevision.isValidRevisionNumber(nodeRev) && this.lockTokens != null) {
Map<SVNURL, String> localRelPathTokens = context.getDb().getNodeLockTokensRecursive(localAbsPath);
for (Map.Entry<SVNURL, String> entry : localRelPathTokens.entrySet()) {
SVNURL key = entry.getKey();
String value = entry.getValue();
if (value != null) {
this.lockTokens.put(key, value);
}
}
}
if (matchesChangeLists && (isHarvestRoot || this.changeLists != null) && (stateFlags != 0) && isAdded && this.danglers != null) {
Map<File, File> danglers = this.danglers;
File parentAbsPath = SVNFileUtil.getParentFile(localAbsPath);
boolean parentAdded;
if (committables.hasItem(parentAbsPath)) {
parentAdded = false;
} else {
parentAdded = context.isNodeAdded(parentAbsPath);
}
if (parentAdded) {
Structure<NodeOriginInfo> nodeOrigin = context.getNodeOrigin(parentAbsPath, false, NodeOriginInfo.isCopy, NodeOriginInfo.copyRootAbsPath);
boolean parentIsCopy = nodeOrigin.is(NodeOriginInfo.isCopy);
File copyRootAbsPath = nodeOrigin.get(NodeOriginInfo.copyRootAbsPath);
if (parentIsCopy) {
parentAbsPath = copyRootAbsPath;
}
if (!danglers.containsKey(parentAbsPath)) {
danglers.put(parentAbsPath, localAbsPath);
}
}
}
if (isDeleted && !isAdded) {
if (status.getKind() == SVNNodeKind.DIR) {
this.skipBelowAbsPath = localAbsPath;
}
return;
}
if (copyMode && !isAdded && !isDeleted && status.getKind() == SVNNodeKind.DIR) {
harvestNotPresentForCopy(context, localAbsPath, committables, status.getRepositoryRootUrl(), commitRelPath, checkUrlCallback);
}
}
}
private static void harvestNotPresentForCopy(SVNWCContext context, File localAbsPath, SvnCommitPacket committables, SVNURL reposRootUrl, File commitRelPath, ISvnUrlKindCallback urlKindCallback) throws SVNException {
List<File> children = context.getChildrenOfWorkingNode(localAbsPath, true);
for (File thisAbsPath : children) {
String name = SVNFileUtil.getFileName(thisAbsPath);
SVNWCContext.NodePresence nodePresence = context.getNodePresence(thisAbsPath, false);
if (!nodePresence.isNotPresent) {
continue;
}
File thisCommitRelPath;
if (commitRelPath == null) {
thisCommitRelPath = null;
} else {
thisCommitRelPath = SVNFileUtil.createFilePath(commitRelPath, name);
}
SVNNodeKind kind;
if (urlKindCallback != null) {
Structure<NodeOriginInfo> nodeOrigin = context.getNodeOrigin(SVNFileUtil.getParentFile(thisAbsPath), false, NodeOriginInfo.revision, NodeOriginInfo.reposRelpath, NodeOriginInfo.reposRootUrl);
long parentRev = nodeOrigin.lng(NodeOriginInfo.revision);
File parentReposRelPath = nodeOrigin.get(NodeOriginInfo.reposRelpath);
SVNURL parentReposRootUrl = nodeOrigin.get(NodeOriginInfo.reposRootUrl);
SVNURL nodeUrl = parentReposRootUrl.appendPath(SVNFileUtil.getFilePath(parentReposRelPath), false).appendPath(SVNFileUtil.getFileName(thisAbsPath), false);
kind = urlKindCallback.getUrlKind(nodeUrl, parentRev);
if (kind == SVNNodeKind.NONE) {
continue;
}
} else {
kind = context.readKind(thisAbsPath, true);
}
committables.addItem(thisAbsPath, kind, reposRootUrl, SVNFileUtil.getFilePath(thisCommitRelPath), -1, null, -1, null, SvnCommitItem.DELETE);
}
}
private static Structure<NodeCommitStatus> getNodeCommitStatus(SVNWCContext context, File localAbsPath) throws SVNException {
Structure<NodeCommitStatus> result = Structure.obtain(NodeCommitStatus.class);
Structure<NodeInfo> nodeInfo = context.getDb().readInfo(localAbsPath, NodeInfo.status, NodeInfo.kind, NodeInfo.revision,
NodeInfo.reposRelPath, NodeInfo.originalRevision, NodeInfo.originalReposRelpath, NodeInfo.lock,
NodeInfo.changelist, NodeInfo.conflicted, NodeInfo.opRoot, NodeInfo.hadProps,
NodeInfo.propsMod, NodeInfo.haveBase, NodeInfo.haveMoreWork);
if (nodeInfo.get(NodeInfo.kind) == SVNWCDbKind.File) {
result.set(NodeCommitStatus.kind, SVNNodeKind.FILE);
} else if (nodeInfo.get(NodeInfo.kind) == SVNWCDbKind.Dir) {
result.set(NodeCommitStatus.kind, SVNNodeKind.DIR);
} else {
result.set(NodeCommitStatus.kind, SVNNodeKind.UNKNOWN);
}
result.set(NodeCommitStatus.reposRelPath, nodeInfo.get(NodeInfo.reposRelPath));
result.set(NodeCommitStatus.revision, nodeInfo.lng(NodeInfo.revision));
result.set(NodeCommitStatus.originalReposRelPath, nodeInfo.get(NodeInfo.originalReposRelpath));
result.set(NodeCommitStatus.originalRevision, nodeInfo.lng(NodeInfo.originalRevision));
result.set(NodeCommitStatus.changelist, nodeInfo.get(NodeInfo.changelist));
result.set(NodeCommitStatus.propsMod, nodeInfo.is(NodeInfo.propsMod));
SVNWCDbStatus nodeStatus = nodeInfo.get(NodeInfo.status);
result.set(NodeCommitStatus.added, nodeStatus == SVNWCDbStatus.Added);
result.set(NodeCommitStatus.deleted, nodeStatus == SVNWCDbStatus.Deleted);
result.set(NodeCommitStatus.notPresent, nodeStatus == SVNWCDbStatus.NotPresent);
result.set(NodeCommitStatus.excluded, nodeStatus == SVNWCDbStatus.Excluded);
result.set(NodeCommitStatus.isOpRoot, nodeInfo.is(NodeInfo.opRoot));
result.set(NodeCommitStatus.conflicted, nodeInfo.is(NodeInfo.conflicted));
if (nodeStatus == SVNWCDbStatus.Added && nodeInfo.is(NodeInfo.opRoot) &&
(nodeInfo.is(NodeInfo.haveBase) || nodeInfo.is(NodeInfo.haveMoreWork))) {
Structure<ReplaceInfo> replaceInfo = SvnWcDbReader.readNodeReplaceInfo((SVNWCDb) context.getDb(), localAbsPath, ReplaceInfo.replaceRoot);
result.set(NodeCommitStatus.isReplaceRoot, replaceInfo.is(ReplaceInfo.replaceRoot));
replaceInfo.release();
} else {
result.set(NodeCommitStatus.isReplaceRoot, false);
}
if (nodeInfo.get(NodeInfo.kind) == SVNWCDbKind.File && (nodeInfo.is(NodeInfo.hadProps) || nodeInfo.is(NodeInfo.propsMod))) {
SVNProperties properties = context.getDb().readProperties(localAbsPath);
result.set(NodeCommitStatus.symlink, properties.getStringValue(SVNProperty.SPECIAL) != null);
} else {
result.set(NodeCommitStatus.symlink, false);
}
if (nodeInfo.is(NodeInfo.haveBase) && (nodeInfo.lng(NodeInfo.revision) < 0 || nodeStatus == SVNWCDbStatus.Normal)) {
WCDbBaseInfo baseInfo =context.getDb().getBaseInfo(localAbsPath, BaseInfoField.revision, BaseInfoField.updateRoot);
result.set(NodeCommitStatus.revision, baseInfo.revision);
result.set(NodeCommitStatus.updateRoot, baseInfo.updateRoot);
} else {
result.set(NodeCommitStatus.updateRoot, false);
}
SVNWCDbLock lock = nodeInfo.get(NodeInfo.lock);
result.set(NodeCommitStatus.lockToken, lock != null ? lock.token : null);
nodeInfo.release();
return result;
}
private static void bailOnTreeConflictedChildren(SVNWCContext context, File localAbsPath, SVNNodeKind kind, SVNDepth depth, Collection<String> changelistsSet) throws SVNException {
if ((depth == SVNDepth.EMPTY) || (kind != SVNNodeKind.DIR)) {
return;
}
Map<String, SVNTreeConflictDescription> conflicts = context.getDb().opReadAllTreeConflicts(localAbsPath);
if (conflicts == null || conflicts.isEmpty()) {
return;
}
for (SVNTreeConflictDescription conflict : conflicts.values()) {
if ((conflict.getNodeKind() == SVNNodeKind.DIR) && (depth == SVNDepth.FILES)) {
continue;
}
if (!context.isChangelistMatch(localAbsPath, changelistsSet)) {
continue;
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT, "Aborting commit: ''{0}'' remains in conflict", conflict.getPath());
SVNErrorManager.error(err, SVNLogType.WC);
}
}
private static void collectLocks(final SVNWCContext context, File path, final Map<SVNURL, String> lockTokens) throws SVNException {
ISVNWCNodeHandler nodeHandler = new ISVNWCNodeHandler() {
public void nodeFound(File localAbspath, SVNWCDbKind kind) throws SVNException {
SVNWCDbLock nodeLock = context.getNodeLock(localAbspath);
if (nodeLock == null || nodeLock.token == null) {
return;
}
SVNURL url = context.getNodeUrl(localAbspath);
if (url != null) {
lockTokens.put(url, nodeLock.token);
}
}
};
context.nodeWalkChildren(path, nodeHandler, false, SVNDepth.INFINITY, null);
}
private static void bailOnTreeConflictedAncestor(SVNWCContext context, File firstAbspath) throws SVNException {
File localAbspath;
File parentAbspath;
boolean wcRoot;
boolean treeConflicted;
localAbspath = firstAbspath;
while (true) {
wcRoot = context.checkWCRoot(localAbspath, false).wcRoot;
if (wcRoot) {
break;
}
parentAbspath = SVNFileUtil.getFileDir(localAbspath);
treeConflicted = context.getConflicted(parentAbspath, false, false, true).treeConflicted;
if (treeConflicted) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT, "Aborting commit: ''{0}'' remains in tree-conflict", localAbspath);
SVNErrorManager.error(err, SVNLogType.WC);
return;
}
localAbspath = parentAbspath;
}
}
public static SVNURL translateCommitables(Collection<SvnCommitItem> items, Map<String, SvnCommitItem> decodedPaths) throws SVNException {
Map<SVNURL, SvnCommitItem> itemsMap = new HashMap<SVNURL, SvnCommitItem>();
for (SvnCommitItem item : items) {
if (itemsMap.containsKey(item.getUrl())) {
SvnCommitItem oldItem = (SvnCommitItem) itemsMap.get(item.getUrl());
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_DUPLICATE_COMMIT_URL,
"Cannot commit both ''{0}'' and ''{1}'' as they refer to the same URL",
new Object[] {item.getPath(), oldItem.getPath()});
SVNErrorManager.error(err, SVNLogType.WC);
}
itemsMap.put(item.getUrl(), item);
}
Iterator<SVNURL> urls = itemsMap.keySet().iterator();
SVNURL baseURL = urls.next();
while (urls.hasNext()) {
SVNURL url = (SVNURL) urls.next();
baseURL = SVNURLUtil.getCommonURLAncestor(baseURL, url);
}
if (itemsMap.containsKey(baseURL)) {
SvnCommitItem rootItem = (SvnCommitItem) itemsMap.get(baseURL);
if (rootItem.getKind() != SVNNodeKind.DIR) {
baseURL = baseURL.removePathTail();
} else if (rootItem.getKind() == SVNNodeKind.DIR
&& (rootItem.hasFlag(SvnCommitItem.ADD)
|| rootItem.hasFlag(SvnCommitItem.DELETE)
|| rootItem.hasFlag(SvnCommitItem.COPY)
|| rootItem.hasFlag(SvnCommitItem.LOCK))) {
baseURL = baseURL.removePathTail();
}
}
if (baseURL == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_URL,
"Cannot compute base URL for commit operation");
SVNErrorManager.error(err, SVNLogType.WC);
}
for (Iterator<SVNURL> iterator = itemsMap.keySet().iterator(); iterator.hasNext();) {
SVNURL url = iterator.next();
SvnCommitItem item = itemsMap.get(url);
String realPath = url.equals(baseURL) ? "" : SVNPathUtil.getRelativePath(baseURL.getPath(), url.getPath());
decodedPaths.put(realPath, item);
}
return baseURL;
}
public static Map<String, String> translateLockTokens(Map<SVNURL, String> lockTokens, SVNURL baseURL) {
Map<String, String> translatedLocks = new TreeMap<String, String>();
for (Iterator<SVNURL> urls = lockTokens.keySet().iterator(); urls.hasNext();) {
SVNURL url = urls.next();
if (!SVNURLUtil.isAncestor(baseURL, url)) {
continue;
}
String token = lockTokens.get(url);
String path = url.getPath().substring(baseURL.getPath().length());
if (path.startsWith("/")) {
path = path.substring(1);
}
translatedLocks.put(path, token);
}
return translatedLocks;
}
}