package org.tmatesoft.svn.core.internal.wc2.ng;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.logging.Level;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.util.SVNSkel;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.internal.wc.IOExceptionWrapper;
import org.tmatesoft.svn.core.internal.wc.ISVNFileContentFetcher;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
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.SVNWCDbStatus;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNPropertyData;
import org.tmatesoft.svn.core.wc2.ISvnAddParameters;
import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.core.wc2.hooks.ISvnPropertyValueProvider;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
public class SvnNgPropertiesManager {
public static Collection<String> getGlobalIgnores(ISVNOptions options) {
Collection<String> allPatterns = new HashSet<String>();
String[] ignores = options.getIgnorePatterns();
for (int i = 0; ignores != null && i < ignores.length; i++) {
allPatterns.add(ignores[i]);
}
return allPatterns;
}
public static Collection<String> getEffectiveIgnores(SVNWCContext context, File absPath, Collection<String> globalIgnores) {
Collection<String> allPatterns = new HashSet<String>();
if (globalIgnores != null) {
allPatterns.addAll(globalIgnores);
} else {
allPatterns.addAll(getGlobalIgnores(context.getOptions()));
}
if (context != null && absPath != null) {
try {
String ignoreProperty = context.getProperty(absPath, SVNProperty.IGNORE);
if (ignoreProperty != null) {
for (StringTokenizer tokens = new StringTokenizer(ignoreProperty, "\r\n"); tokens.hasMoreTokens();) {
String token = tokens.nextToken().trim();
if (token.length() > 0) {
allPatterns.add(token);
}
}
}
ignoreProperty = context.getProperty(absPath, SVNProperty.INHERITABLE_IGNORES);
if (ignoreProperty != null) {
for (StringTokenizer tokens = new StringTokenizer(ignoreProperty, "\r\n"); tokens.hasMoreTokens();) {
String token = tokens.nextToken().trim();
if (token.length() > 0) {
allPatterns.add(token);
}
}
}
SVNWCDb db = (SVNWCDb) context.getDb();
SVNWCDb.DirParsedInfo parsed = db.parseDir(absPath, SVNSqlJetDb.Mode.ReadOnly);
List<Structure<StructureFields.InheritedProperties>> inheritedProperties = SvnWcDbProperties.readInheritedProperties(parsed.wcDbDir.getWCRoot(), parsed.localRelPath, SVNProperty.INHERITABLE_IGNORES);
for (Structure<StructureFields.InheritedProperties> inheritedProperty : inheritedProperties) {
SVNProperties properties = inheritedProperty.get(StructureFields.InheritedProperties.properties);
String path = inheritedProperty.get(StructureFields.InheritedProperties.pathOrURL);
ignoreProperty = properties.getStringValue(SVNProperty.INHERITABLE_IGNORES);
if (ignoreProperty != null) {
for (StringTokenizer tokens = new StringTokenizer(ignoreProperty, "\r\n"); tokens.hasMoreTokens();) {
String token = tokens.nextToken().trim();
if (token.length() > 0) {
allPatterns.add(token);
}
}
}
}
} catch (SVNException e) {
}
}
return allPatterns;
}
public static boolean isIgnored(String name, Collection<String> patterns) {
if (patterns == null) {
return false;
}
for (Iterator<String> ps = patterns.iterator(); ps.hasNext();) {
String pattern = (String) ps.next();
if (DefaultSVNOptions.matches(pattern, name)) {
return true;
}
}
return false;
}
public static Map<String, Map<String, String>> parseAutoProperties(SVNPropertyValue autoProperties, Map<String, Map<String, String>> target) {
String autoPropertiesString = SVNPropertyValue.getPropertyAsString(autoProperties);
target = target == null ? new HashMap<String, Map<String, String>>() : target;
if (autoPropertiesString == null) {
return target;
}
String[] lines = autoPropertiesString.split("\n");
for (String line : lines) {
line = line.trim();
if (line.length() == 0) {
continue;
}
int pos = line.indexOf('=');
if (pos < 0) {
continue;
}
String pattern = line.substring(0, pos).trim();
String keyValuePairsString = line.substring(pos + 1).trim();
Map<String, String> properties = target.get(pattern);
if (properties == null) {
properties = new HashMap<String, String>();
target.put(pattern, properties);
}
String[] keyValuePairs = keyValuePairsString.split(";");
for (String keyValuePair : keyValuePairs) {
keyValuePair = keyValuePair.trim();
if (keyValuePair.length() == 0) {
continue;
}
String propertyName;
String propertyValue;
pos = keyValuePair.indexOf('=');
if (pos < 0) {
propertyName = keyValuePair.trim();
propertyValue = "*";
} else {
propertyName = keyValuePair.substring(0, pos).trim();
propertyValue = keyValuePair.substring(pos + 1).trim();
}
properties.put(propertyName, propertyValue);
}
}
return target;
}
public static Map<String, String> getMatchedAutoProperties(String fileName, Map<String, Map<String, String>> autoProperties) {
if (autoProperties == null) {
return Collections.emptyMap();
}
Map<String, String> matchedAutoProperties = new HashMap<String, String>();
for (Map.Entry<String, Map<String, String>> entry : autoProperties.entrySet()) {
String pattern = entry.getKey();
if (DefaultSVNOptions.matches(pattern, fileName)) {
Map<String, String> properties = entry.getValue();
matchedAutoProperties.putAll(properties);
}
}
return matchedAutoProperties;
}
public static void setProperty(final SVNWCContext context, File path, final String propertyName, final SVNPropertyValue propertyValue, SVNDepth depth, final boolean skipChecks,
final ISVNEventHandler eventHandler, Collection<String> changelists) throws SVNException {
setProperty(context, path, propertyName, propertyValue, null, depth, skipChecks, eventHandler, null, changelists);
}
public static void setProperty(final SVNWCContext context, File path, final String propertyName, final SVNPropertyValue propertyValue, SVNDepth depth, final boolean skipChecks,
final ISVNEventHandler eventHandler, final ISvnObjectReceiver<SVNPropertyData> receiver, Collection<String> changelists) throws SVNException {
setProperty(context, path, propertyName, propertyValue, null, depth, skipChecks, eventHandler, receiver, changelists);
}
public static void setProperty(final SVNWCContext context, File path, final String propertyName, final SVNPropertyValue propertyValue, final ISvnPropertyValueProvider pvProvider, SVNDepth depth, final boolean skipChecks,
final ISVNEventHandler eventHandler, final ISvnObjectReceiver<SVNPropertyData> receiver, final Collection<String> changelists) throws SVNException {
if (propertyName != null) {
if (SVNProperty.isEntryProperty(propertyName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_PROP_KIND, "Property ''{0}'' is an entry property", propertyName);
SVNErrorManager.error(err, SVNLogType.WC);
} else if (SVNProperty.isWorkingCopyProperty(propertyName)) {
SVNProperties wcProps = context.getDb().getBaseDavCache(path);
if (wcProps == null && propertyValue != null) {
wcProps = new SVNProperties();
}
if (wcProps != null) {
if (propertyValue != null) {
wcProps.put(propertyName, propertyValue);
} else {
wcProps.remove(propertyName);
}
}
context.getDb().setBaseDavCache(path, wcProps);
return;
}
}
SVNNodeKind kind = context.readKind(path, true);
File dirPath;
if (kind == SVNNodeKind.DIR) {
dirPath = path;
} else {
dirPath = SVNFileUtil.getParentFile(path);
}
context.writeCheck(dirPath);
if (depth == SVNDepth.EMPTY) {
if (!context.matchesChangelist(path, changelists)) {
return;
}
setProperty(context, path, kind, propertyName, propertyValue, pvProvider, skipChecks, eventHandler, receiver);
} else {
context.nodeWalkChildren(path, new SVNWCContext.ISVNWCNodeHandler() {
public void nodeFound(File localAbspath, SVNWCDbKind kind) throws SVNException {
try {
setProperty(context, localAbspath, kind.toNodeKind(), propertyName, propertyValue, pvProvider, skipChecks, eventHandler, receiver);
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() == SVNErrorCode.ILLEGAL_TARGET ||
e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_INVALID_SCHEDULE) {
return;
}
throw e;
}
}
}, false, depth, changelists);
}
}
public static void setProperty(final SVNWCContext context, final File path, SVNNodeKind kind, String propertyName, SVNPropertyValue value,
ISvnPropertyValueProvider pvProvider, boolean skipChecks, ISVNEventHandler eventHandler, ISvnObjectReceiver<SVNPropertyData> receiver) throws SVNException {
Structure<NodeInfo> nodeInfo = context.getDb().readInfo(path, NodeInfo.status);
ISVNWCDb.SVNWCDbStatus status = nodeInfo.get(NodeInfo.status);
if (status != SVNWCDbStatus.Normal && status != SVNWCDbStatus.Added && status != SVNWCDbStatus.Incomplete) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SCHEDULE,
"Can''t set properties on ''{0}'': invalid status for updating properties.",
path);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (pvProvider == null && value != null && SVNProperty.isSVNProperty(propertyName)) {
final ISVNFileContentFetcher fetcher = new ISVNFileContentFetcher() {
public SVNPropertyValue getProperty(String propertyName) throws SVNException {
return context.getPropertyValue(path, propertyName);
}
public boolean fileIsBinary() throws SVNException {
SVNPropertyValue mimeType = context.getPropertyValue(path, SVNProperty.MIME_TYPE);
return mimeType != null && SVNProperty.isBinaryMimeType(mimeType.getString());
}
public void fetchFileContent(OutputStream os) throws SVNException {
InputStream is = null;
try {
is = SVNFileUtil.openFileForReading(path);
SVNTranslator.copy(is, os);
} catch (IOExceptionWrapper ioew) {
throw ioew.getOriginalException();
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
} finally {
SVNFileUtil.closeFile(is);
}
}
};
SVNPropertyValue pv = SVNPropertiesManager.validatePropertyValue(path, kind, propertyName, value, skipChecks, context.getOptions(), fetcher);
value = pv;
}
SVNSkel workItems = null;
if (pvProvider == null && kind == SVNNodeKind.FILE && (SVNProperty.EXECUTABLE.equals(propertyName) || SVNProperty.NEEDS_LOCK.equals(propertyName))) {
workItems = context.wqBuildSyncFileFlags(path);
}
SVNProperties properties = new SVNProperties();
try {
properties = context.getDb().readProperties(path);
} catch (SVNException e) {
SVNErrorMessage err = e.getErrorMessage().wrap("Failed to load current properties");
SVNErrorManager.error(err, e, SVNLogType.WC);
}
if (pvProvider != null) {
SVNPropertyValue oldKeywords = properties.getSVNPropertyValue(SVNProperty.KEYWORDS);
SVNPropertyValue oldEolStyle = properties.getSVNPropertyValue(SVNProperty.EOL_STYLE);
boolean wasExecutable = properties.containsName(SVNProperty.EXECUTABLE);
boolean wasNeedsLock = properties.containsName(SVNProperty.NEEDS_LOCK);
properties = pvProvider.providePropertyValues(path, properties);
if (properties == null) {
properties = new SVNProperties();
}
SVNPropertyValue newKeywords = properties.getSVNPropertyValue(SVNProperty.KEYWORDS);
SVNPropertyValue newEolStyle = properties.getSVNPropertyValue(SVNProperty.EOL_STYLE);
boolean isExecutable = properties.containsName(SVNProperty.EXECUTABLE);
boolean isNeedsLock = properties.containsName(SVNProperty.NEEDS_LOCK);
if (isExecutable != wasExecutable || isNeedsLock != wasNeedsLock) {
workItems = context.wqBuildSyncFileFlags(path);
}
boolean clearRecordedInfo = !equals(oldKeywords, newKeywords) || !equals(oldEolStyle, newEolStyle);
context.getDb().opSetProps(path, properties, null, clearRecordedInfo, workItems);
if (workItems != null) {
context.wqRun(path);
}
return;
}
boolean clearRecordedInfo = false;
if (kind == SVNNodeKind.FILE && SVNProperty.KEYWORDS.equals(propertyName)) {
String oldValue = properties.getStringValue(SVNProperty.KEYWORDS);
Map<String, byte[]> keywords = oldValue != null ? context.getKeyWords(path, oldValue) : new HashMap<String, byte[]>();
Map<String, byte[]> newKeywords = value != null ? context.getKeyWords(path, value.getString()) : new HashMap<String, byte[]>();
if (!keywords.equals(newKeywords)) {
clearRecordedInfo = true;
}
} else if (kind == SVNNodeKind.FILE && SVNProperty.EOL_STYLE.equals(propertyName)) {
String oldValue = properties.getStringValue(SVNProperty.EOL_STYLE);
if (oldValue == null || value == null) {
clearRecordedInfo = (oldValue != null && value == null) || (oldValue == null && value != null);
} else {
clearRecordedInfo = !SVNPropertyValue.create(oldValue).equals(value);
}
}
SVNEventAction action;
if (!properties.containsName(propertyName)) {
action = value == null ? SVNEventAction.PROPERTY_DELETE_NONEXISTENT : SVNEventAction.PROPERTY_ADD;
} else {
action = value == null ? SVNEventAction.PROPERTY_DELETE : SVNEventAction.PROPERTY_MODIFY;
}
if (value != null) {
properties.put(propertyName, value);
} else {
properties.remove(propertyName);
}
context.getDb().opSetProps(path, properties, null, clearRecordedInfo, workItems);
if (workItems != null) {
context.wqRun(path);
}
if (receiver != null) {
receiver.receive(SvnTarget.fromFile(path), new SVNPropertyData(propertyName, value, context.getOptions()));
}
if (eventHandler != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(path, SVNNodeKind.NONE,
null, -1, action, action, null, null, 1, 1, null, propertyName);
eventHandler.handleEvent(event, -1);
}
}
public static void setAutoProperties(final SVNWCContext context, final File path, final SVNProperties properties,
final ISvnAddParameters addParameters, Runnable onValidationError) throws SVNException {
final ISVNFileContentFetcher fetcher = new ISVNFileContentFetcher() {
public SVNPropertyValue getProperty(String propertyName) throws SVNException {
return properties.getSVNPropertyValue(propertyName);
}
public boolean fileIsBinary() throws SVNException {
SVNPropertyValue mimeType = getProperty(SVNProperty.MIME_TYPE);
return mimeType != null && SVNProperty.isBinaryMimeType(mimeType.getString());
}
public void fetchFileContent(OutputStream os) throws SVNException {
InputStream is = null;
try {
is = SVNFileUtil.openFileForReading(path);
SVNTranslator.copy(is, os);
} catch (IOExceptionWrapper ioew) {
throw ioew.getOriginalException();
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(err, SVNLogType.WC);
} finally {
SVNFileUtil.closeFile(is);
}
}
};
final SVNProperties validation = new SVNProperties(properties);
String detectedMimeType = null;
for(String propertyName : validation.nameSet()) {
try {
final SVNPropertyValue pv = SVNPropertiesManager.validatePropertyValue(path,
SVNNodeKind.FILE, propertyName, properties.getSVNPropertyValue(propertyName), false, context.getOptions(), fetcher);
properties.put(propertyName, pv);
} catch (SVNException e) {
if (SVNProperty.EOL_STYLE.equals(propertyName) &&
e.getErrorMessage().getErrorCode() == SVNErrorCode.ILLEGAL_TARGET &&
e.getErrorMessage().getMessage().indexOf("newlines") >= 0) {
ISvnAddParameters.Action action = addParameters.onInconsistentEOLs(path);
if (action == ISvnAddParameters.Action.REPORT_ERROR) {
try {
onValidationError.run();
} catch (Throwable th) {
SVNDebugLog.getDefaultLog().log(SVNLogType.WC, th, Level.INFO);
}
throw e;
} else if (action == ISvnAddParameters.Action.ADD_AS_IS) {
properties.remove(propertyName);
} else if (action == ISvnAddParameters.Action.ADD_AS_BINARY) {
properties.remove(propertyName);
detectedMimeType = SVNFileUtil.BINARY_MIME_TYPE;
}
} else {
try {
onValidationError.run();
} catch (Throwable th) {
SVNDebugLog.getDefaultLog().log(SVNLogType.WC, th, Level.INFO);
}
throw e;
}
}
}
if (detectedMimeType != null) {
properties.put(SVNProperty.MIME_TYPE, detectedMimeType);
}
SVNSkel workItems = null;
if (properties.containsName(SVNProperty.EXECUTABLE) || properties.containsName(SVNProperty.NEEDS_LOCK)) {
workItems = context.wqBuildSyncFileFlags(path);
}
final boolean clearRecordedInfo = properties.containsName(SVNProperty.KEYWORDS) || properties.containsName(SVNProperty.EOL_STYLE);
context.getDb().opSetProps(path, properties, null, clearRecordedInfo, workItems);
if (workItems != null) {
context.wqRun(path);
}
}
private static boolean equals(SVNPropertyValue oldValue, SVNPropertyValue newValue) {
if (oldValue == null || newValue == null) {
return oldValue == newValue;
}
return oldValue.equals(newValue);
}
public static void checkPropertyName(String propertyName, SVNPropertyValue propertyValue) throws SVNException {
if (SVNRevisionProperty.isRevisionProperty(propertyName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Revision property ''{0}'' not allowed in this context", propertyName);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (propertyValue != null && !SVNPropertiesManager.isValidPropertyName(propertyName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "Bad property name: ''{0}''", propertyName);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (SVNProperty.isWorkingCopyProperty(propertyName)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_PROPERTY_NAME, "''{0}'' is a wcprop, thus not accessible to clients", propertyName);
SVNErrorManager.error(err, SVNLogType.WC);
}
}
public static void categorizeProperties(SVNProperties props, SVNProperties regular, SVNProperties entry, SVNProperties working) {
for (String name : props.nameSet()) {
SVNPropertyValue pv = props.getSVNPropertyValue(name);
if (SVNProperty.isEntryProperty(name) && entry != null) {
entry.put(name, pv);
} else if (SVNProperty.isRegularProperty(name) && regular != null) {
regular.put(name, pv);
} else if (SVNProperty.isWorkingCopyProperty(name) && working != null) {
working.put(name, pv);
}
}
}
public static void splitAndAppend(final List<String> patterns, final String ignores) {
if (ignores == null) {
return;
}
for (StringTokenizer tokens = new StringTokenizer(ignores, "\r\n"); tokens.hasMoreTokens();) {
String token = tokens.nextToken().trim();
if (token.length() > 0) {
patterns.add(token);
}
}
}
}