/*
* ====================================================================
* 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.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
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.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNHashSet;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
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.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNStatusFileProvider;
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.SVNTreeConflictDescription;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNStatusEditor {
private SVNWCAccess myWCAccess;
private SVNAdminAreaInfo myAdminInfo;
private boolean myIsReportAll;
private boolean myIsNoIgnore;
private SVNDepth myDepth;
private ISVNStatusHandler myStatusHandler;
private Map myExternalsMap;
private Collection myGlobalIgnores;
protected SVNURL myRepositoryRoot;
private Map myRepositoryLocks;
private long myTargetRevision;
private String myWCRootPath;
private ISVNStatusFileProvider myFileProvider;
private ISVNStatusFileProvider myDefaultFileProvider;
public SVNStatusEditor(ISVNOptions options, SVNWCAccess wcAccess, SVNAdminAreaInfo info, boolean noIgnore, boolean reportAll, SVNDepth depth,
ISVNStatusHandler handler) {
myWCAccess = wcAccess;
myAdminInfo = info;
myIsNoIgnore = noIgnore;
myIsReportAll = reportAll;
myDepth = depth;
myStatusHandler = handler;
myExternalsMap = new SVNHashMap();
myGlobalIgnores = getGlobalIgnores(options);
myTargetRevision = -1;
myDefaultFileProvider = new DefaultSVNStatusFileProvider();
myFileProvider = myDefaultFileProvider;
}
public long getTargetRevision() {
return myTargetRevision;
}
public void targetRevision(long revision) {
myTargetRevision = revision;
}
public SVNCommitInfo closeEdit() throws SVNException {
try {
if (hasTarget()) {
File path = myAdminInfo.getAnchor().getFile(myAdminInfo.getTargetName());
SVNFileType type = SVNFileType.getType(path);
if (type == SVNFileType.DIRECTORY) {
SVNEntry entry = myWCAccess.getEntry(path, false);
if (entry == null) {
getDirStatus(null, myAdminInfo.getAnchor(), myAdminInfo.getTargetName(),
SVNDepth.EMPTY, myIsReportAll, true, null, true, myStatusHandler);
} else {
SVNAdminArea target = myWCAccess.retrieve(path);
getDirStatus(null, target, null,
myDepth, myIsReportAll, myIsNoIgnore, null, false, myStatusHandler);
}
} else {
getDirStatus(null, myAdminInfo.getAnchor(), myAdminInfo.getTargetName(), SVNDepth.EMPTY, myIsReportAll, true, null, true, myStatusHandler);
}
} else {
getDirStatus(null, myAdminInfo.getAnchor(), null,
myDepth, myIsReportAll, myIsNoIgnore, null, false, myStatusHandler);
}
} finally {
cleanup();
}
return null;
}
public void setRepositoryInfo(SVNURL root, Map repositoryLocks) {
myRepositoryRoot = root;
myRepositoryLocks = repositoryLocks;
}
protected void getDirStatus(SVNEntry parentEntry, SVNAdminArea dir, String entryName,
SVNDepth depth, boolean getAll, boolean noIgnore, Collection ignorePatterns, boolean skipThisDir,
ISVNStatusHandler handler) throws SVNException {
myWCAccess.checkCancelled();
depth = depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth;
Map childrenFiles = myFileProvider.getChildrenFiles(dir.getRoot());
SVNEntry dirEntry = myWCAccess.getEntry(dir.getRoot(), false);
String externals = dir.getProperties(dir.getThisDirName()).getStringPropertyValue(SVNProperty.EXTERNALS);
if (externals != null) {
String path = dir.getRelativePath(myAdminInfo.getAnchor());
myAdminInfo.addExternal(path, externals, externals);
myAdminInfo.addDepth(path, dirEntry.getDepth());
SVNExternal[] externalsInfo = SVNExternal.parseExternals(dir.getRelativePath(myAdminInfo.getAnchor()), externals);
for (int i = 0; i < externalsInfo.length; i++) {
SVNExternal external = externalsInfo[i];
myExternalsMap.put(SVNPathUtil.append(path, external.getPath()), external);
}
}
if (entryName != null) {
File file = (File) childrenFiles.get(entryName);
SVNEntry entry = dir.getEntry(entryName, false);
if (entry != null) {
SVNFileType fileType = SVNFileType.getType(file);
boolean special = fileType == SVNFileType.SYMLINK;
SVNNodeKind fileKind = SVNFileType.getNodeKind(fileType);
handleDirEntry(dir, entryName, dirEntry, entry,
fileKind, special, depth, getAll, noIgnore, handler);
} else if (file != null) {
if (ignorePatterns == null) {
ignorePatterns = getIgnorePatterns(dir, myGlobalIgnores);
}
SVNFileType fileType = SVNFileType.getType(file);
boolean special = fileType == SVNFileType.SYMLINK;
SVNNodeKind fileKind = SVNFileType.getNodeKind(fileType);
sendUnversionedStatus(file, entryName, fileKind, special, dir, ignorePatterns, noIgnore, handler);
} else {
SVNTreeConflictDescription treeConflict = myWCAccess.getTreeConflict(dir.getFile(entryName));
if (treeConflict != null) {
if (ignorePatterns == null) {
ignorePatterns = getIgnorePatterns(dir, myGlobalIgnores);
}
sendUnversionedStatus(dir.getFile(entryName), entryName, SVNNodeKind.NONE, false, dir, ignorePatterns, true, handler);
}
}
return;
}
if (!skipThisDir) {
SVNStatus status = assembleStatus(dir.getRoot(), dir, dirEntry, parentEntry,
SVNNodeKind.DIR, false, isReportAll(), false);
if (status != null && handler != null) {
handler.handleStatus(status);
}
}
if (depth == SVNDepth.EMPTY) {
return;
}
// iterate over files.
childrenFiles = new TreeMap(childrenFiles);
for (Iterator files = childrenFiles.keySet().iterator(); files.hasNext();) {
String fileName = (String) files.next();
SVNEntry entry = dir.getEntry(fileName, true);
if (isNameConflict(entry)) {
continue;
}
if ((entry != null && !entry.isHidden()) || SVNFileUtil.getAdminDirectoryName().equals(fileName)) {
continue;
}
File file = (File) childrenFiles.get(fileName);
if (depth == SVNDepth.FILES && file.isDirectory()) {
continue;
}
if (ignorePatterns == null) {
ignorePatterns = getIgnorePatterns(dir, myGlobalIgnores);
}
sendUnversionedStatus(file, fileName, SVNNodeKind.NONE, false, dir, ignorePatterns, noIgnore,
handler);
}
Map treeConflicts = SVNTreeConflictUtil.readTreeConflicts(dir.getRoot(), dirEntry.getTreeConflictData());
for (Iterator treeConflictsIter = treeConflicts.keySet().iterator(); treeConflictsIter.hasNext();) {
File conflictPath = (File) treeConflictsIter.next();
if (childrenFiles.containsKey(conflictPath.getName()) || dir.getEntry(conflictPath.getName(), false) != null) {
continue;
}
if (ignorePatterns == null) {
ignorePatterns = getIgnorePatterns(dir, myGlobalIgnores);
}
sendUnversionedStatus(conflictPath, conflictPath.getName(), SVNNodeKind.NONE, false, dir, ignorePatterns, noIgnore,
handler);
}
for(Iterator entries = dir.entries(true); entries.hasNext();) {
SVNEntry entry = (SVNEntry) entries.next();
if (isNameConflict(entry)) {
SVNStatus status = new SVNStatus(entry.getSVNURL(), dir.getFile(entry.getName()), entry.getKind(),
SVNRevision.create(entry.getRevision()), SVNRevision.create(entry.getCommittedRevision()),
SVNDate.parseDate(entry.getCommittedDate()), entry.getAuthor(),
SVNStatusType.STATUS_NAME_CONFLICT, SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE, SVNStatusType.STATUS_NONE,
false, entry.isCopied(), false, false, null, null, null, null,
entry.getCopyFromURL(), SVNRevision.create(entry.getCopyFromRevision()),
null, null, entry.asMap(), entry.getChangelistName(), dir.getFormatVersion(), null);
status.setDepth(entry.isDirectory() ? entry.getDepth() : SVNDepth.UNKNOWN);
status.setEntry(entry);
status.setRepositoryRootURL(myRepositoryRoot);
handler.handleStatus(status);
continue;
} else if (entry.isHidden()) {
continue;
}
if (dir.getThisDirName().equals(entry.getName())) {
continue;
}
if (depth == SVNDepth.FILES && entry.isDirectory()) {
continue;
}
File file = (File) childrenFiles.get(entry.getName());
SVNFileType fileType = SVNFileType.getType(file);
boolean special = fileType == SVNFileType.SYMLINK;
SVNNodeKind fileKind = SVNFileType.getNodeKind(fileType);
handleDirEntry(dir, entry.getName(), dirEntry, entry,
fileKind, special, depth == SVNDepth.INFINITY ? depth : SVNDepth.EMPTY,
getAll, noIgnore, handler);
}
}
public static boolean isNameConflict(SVNEntry entry) {
return entry != null && entry.isAbsent() && "nameconflict".equals(entry.getChecksum());
}
protected void cleanup() {
if (hasTarget()) {
myAdminInfo.removeExternal("");
myAdminInfo.removeDepth("");
}
}
protected SVNAdminArea getAnchor() {
return myAdminInfo.getAnchor();
}
protected SVNWCAccess getWCAccess() {
return myWCAccess;
}
protected SVNDepth getDepth() {
return myDepth;
}
protected boolean isReportAll() {
return myIsReportAll;
}
protected boolean isNoIgnore() {
return myIsNoIgnore;
}
protected SVNAdminAreaInfo getAdminAreaInfo() {
return myAdminInfo;
}
protected ISVNStatusHandler getDefaultHandler() {
return myStatusHandler;
}
protected boolean hasTarget() {
return myAdminInfo.getTargetName() != null && !"".equals(myAdminInfo.getTargetName());
}
protected SVNLock getLock(SVNURL url) {
return SVNStatusUtil.getLock(myRepositoryLocks, url, myRepositoryRoot);
}
private void handleDirEntry(SVNAdminArea dir, String entryName, SVNEntry dirEntry, SVNEntry entry, SVNNodeKind fileKind, boolean special,
SVNDepth depth, boolean getAll, boolean noIgnore, ISVNStatusHandler handler) throws SVNException {
File path = dir.getFile(entryName);
if (fileKind == SVNNodeKind.DIR) {
SVNEntry fullEntry = entry;
if (entry.getKind() == fileKind) {
fullEntry = myWCAccess.getVersionedEntry(path, false);
}
if (fullEntry != entry && (depth == SVNDepth.UNKNOWN || depth == SVNDepth.IMMEDIATES
|| depth == SVNDepth.INFINITY)) {
SVNAdminArea childDir = myWCAccess.retrieve(path);
getDirStatus(dirEntry, childDir, null, depth, getAll, noIgnore, null, false, handler);
} else if (fullEntry != entry) {
// get correct dir.
SVNAdminArea childDir = myWCAccess.retrieve(path);
SVNStatus status = assembleStatus(path, childDir, fullEntry, dirEntry, fileKind,
special, getAll, false);
if (status != null && handler != null) {
handler.handleStatus(status);
}
} else {
SVNStatus status = assembleStatus(path, dir, fullEntry, dirEntry, fileKind,
special, getAll, false);
if (status != null && handler != null) {
handler.handleStatus(status);
}
}
} else {
SVNStatus status = assembleStatus(path, dir, entry, dirEntry, fileKind, special,
getAll, false);
if (status != null && handler != null) {
handler.handleStatus(status);
}
}
}
private void sendUnversionedStatus(File file, String name, SVNNodeKind fileType, boolean special,
SVNAdminArea dir, Collection ignorePatterns, boolean noIgnore, ISVNStatusHandler handler) throws SVNException {
String path = dir.getRelativePath(myAdminInfo.getAnchor());
path = SVNPathUtil.append(path, name);
boolean isIgnored = isIgnored(ignorePatterns, file, getWCRootRelativePath(ignorePatterns, file));
boolean isExternal = isExternal(path);
SVNStatus status = assembleStatus(file, dir, null, null, fileType, special, true,
isIgnored);
if (status != null) {
if (isExternal) {
status.setContentsStatus(SVNStatusType.STATUS_EXTERNAL);
}
if (handler != null && (noIgnore || !isIgnored || isExternal || status.getRemoteLock() != null)) {
handler.handleStatus(status);
}
}
}
protected SVNStatus assembleStatus(File file, SVNAdminArea dir,
SVNEntry entry, SVNEntry parentEntry, SVNNodeKind fileKind, boolean special,
boolean reportAll, boolean isIgnored) throws SVNException {
return SVNStatusUtil.assembleStatus(file, dir, entry, parentEntry, fileKind, special, reportAll,
isIgnored, myRepositoryLocks, myRepositoryRoot, myWCAccess);
}
protected String getWCRootPath() {
if (myWCRootPath == null) {
try {
File root = SVNWCUtil.getWorkingCopyRoot(myAdminInfo.getAnchor().getRoot(), true);
if (root != null) {
myWCRootPath = root.getAbsolutePath().replace(File.separatorChar, '/');
}
} catch (SVNException e) {
// ignore.
}
}
return myWCRootPath;
}
protected String getWCRootRelativePath(Collection ignorePatterns, File file) {
boolean needToComputeWCRelativePath = false;
for (Iterator patterns = ignorePatterns.iterator(); patterns.hasNext();) {
String pattern = (String) patterns.next();
if (pattern.startsWith("/")) {
needToComputeWCRelativePath = true;
break;
}
}
if (!needToComputeWCRelativePath) {
return null;
}
String rootRelativePath = null;
if (getWCRootPath() != null) {
rootRelativePath = file.getAbsolutePath().replace(File.separatorChar, '/');
rootRelativePath = SVNPathUtil.getPathAsChild(getWCRootPath(), rootRelativePath);
if (rootRelativePath != null && !rootRelativePath.startsWith("/")) {
rootRelativePath = "/" + rootRelativePath;
}
}
return rootRelativePath;
}
private boolean isExternal(String path) {
if (!myExternalsMap.containsKey(path)) {
// check if path is external parent.
for (Iterator paths = myExternalsMap.keySet().iterator(); paths.hasNext();) {
String externalPath = (String) paths.next();
if (externalPath.startsWith(path + "/")) {
return true;
}
}
return false;
}
return true;
}
public static Collection getIgnorePatterns(SVNAdminArea dir, Collection globalIgnores) throws SVNException {
String localIgnores = dir.getProperties("").getStringPropertyValue(SVNProperty.IGNORE);
if (localIgnores != null) {
Collection patterns = new SVNHashSet();
patterns.addAll(globalIgnores);
for(StringTokenizer tokens = new StringTokenizer(localIgnores, "\r\n"); tokens.hasMoreTokens();) {
String token = tokens.nextToken().trim();
if (token.length() > 0) {
patterns.add(token);
}
}
return patterns;
}
return globalIgnores;
}
public static Collection getGlobalIgnores(ISVNOptions options) {
if (options != null) {
String[] ignores = options.getIgnorePatterns();
if (ignores != null) {
Collection patterns = new SVNHashSet();
for (int i = 0; i < ignores.length; i++) {
patterns.add(ignores[i]);
}
return patterns;
}
}
return Collections.EMPTY_SET;
}
public static boolean isIgnored(Collection patterns, File file) {
return isIgnored(patterns, file, null);
}
public static boolean isIgnored(Collection patterns, File file, String relativePath) {
String name = file.getName();
String dirName = null;
boolean isDirectory = SVNFileType.getType(file) == SVNFileType.DIRECTORY;
if (isDirectory) {
dirName = name + "/";
}
for (Iterator ps = patterns.iterator(); ps.hasNext();) {
String pattern = (String) ps.next();
if (pattern.startsWith("/") && relativePath != null) {
if (DefaultSVNOptions.matches(pattern, relativePath) ||
(isDirectory && DefaultSVNOptions.matches(pattern, relativePath + "/"))) {
return true;
}
continue;
}
if (DefaultSVNOptions.matches(pattern, name)) {
return true;
} else if (isDirectory && DefaultSVNOptions.matches(pattern, dirName)) {
return true;
}
}
return false;
}
public void setFileProvider(ISVNStatusFileProvider fileProvider) {
myFileProvider = new WrapperSVNStatusFileProvider(myDefaultFileProvider, fileProvider);
}
private static class WrapperSVNStatusFileProvider implements ISVNStatusFileProvider {
private final ISVNStatusFileProvider myDefault;
private final ISVNStatusFileProvider myDelegate;
private WrapperSVNStatusFileProvider(ISVNStatusFileProvider defaultProvider, ISVNStatusFileProvider delegate) {
myDefault = defaultProvider;
myDelegate = delegate;
}
public Map getChildrenFiles(File parent) {
final Map result = myDelegate.getChildrenFiles(parent);
if (result != null) {
return result;
}
return myDefault.getChildrenFiles(parent);
}
}
private static class DefaultSVNStatusFileProvider implements ISVNStatusFileProvider {
public Map getChildrenFiles(File parent) {
File[] children = SVNFileListUtil.listFiles(parent);
if (children != null) {
Map map = new SVNHashMap();
for (int i = 0; i < children.length; i++) {
map.put(children[i].getName(), children[i]);
}
return map;
}
return Collections.EMPTY_MAP;
}
}
}