in source/com.microsoft.tfs.core/src/com/microsoft/tfs/core/clients/versioncontrol/offline/OfflineSynchronizer.java [461:594]
private void scanLocal(final File file, final int depth) throws Exception {
if (taskMonitor.isCanceled()) {
throw new CanceledException();
}
// Check symlink attribute on the non-canonicalized name
final FileSystemUtils util = FileSystemUtils.getInstance();
final FileSystemAttributes attrs = util.getAttributes(file);
String path = file.getAbsolutePath();
if (!attrs.isSymbolicLink()) {
path = canonicalPath(file);
}
// symlink not supported for previous versions, directly skip and return
if (workspace.getClient().getServiceLevel().getValue() < WebServiceLevel.TFS_2012_2.getValue()
&& attrs.isSymbolicLink()) {
serverFiles.remove(path);
return;
}
final boolean exists = serverFiles.containsKey(path);
final byte[] hashCode = serverFiles.remove(path);
final ItemType serverItemType = (hashCode == null || hashCode.length == 0) ? ItemType.FOLDER : ItemType.FILE;
/*
* The file may not actually exist locally. This may happen if this is
* the root and was based on user input. For example, the user can
* select "Go Online" for a single file in Explorer that's pended for a
* delete. In this case, we push the hashCode back into the serverFiles
* list.
*/
if (!attrs.isSymbolicLink() && !file.exists()) {
serverFiles.put(path, hashCode);
} else if (file.isFile() || attrs.isSymbolicLink()) {
OfflineChangeType type = null;
OfflineChangeType propertyType = null;
// if the file does not exist on the server, we should pend an add
if (!exists && detectAdded) {
type = OfflineChangeType.ADD;
if (attrs.isSymbolicLink()) {
propertyType = OfflineChangeType.SYMLINK;
} else if (attrs.isExecutable()) {
propertyType = OfflineChangeType.EXEC;
}
} else if (exists) {
/**
* For symbolic link, compute the MD5 hash based on the targeted
* link and compare with server hash
*/
if (attrs.isSymbolicLink()) {
final String targetLink = util.getSymbolicLink(file.getPath());
final byte[] localHashByLink = HashUtils.hashString(targetLink, null, HashUtils.ALGORITHM_MD5);
if (!Arrays.equals(localHashByLink, hashCode)) {
type = OfflineChangeType.EDIT;
}
} else if (isChanged(file, hashCode)) {
type = OfflineChangeType.EDIT;
}
if (workspace.getClient().getServiceLevel().getValue() >= WebServiceLevel.TFS_2012.getValue()) {
// query server for symlink and exec property, detect
// property change on local disk
boolean symlinkOnServer = false;
boolean executable = false;
final ItemSet[] items = workspace.getClient().getItems(
new ItemSpec[] {
new ItemSpec(path, RecursionType.NONE)
},
LatestVersionSpec.INSTANCE,
DeletedState.ANY,
ItemType.ANY,
GetItemsOptions.NONE,
PropertyConstants.QUERY_ALL_PROPERTIES_FILTERS);
if (items != null
&& items.length > 0
&& items[0].getItems() != null
&& items[0].getItems().length > 0) {
final PropertyValue[] propertyValues = items[0].getItems()[0].getPropertyValues();
symlinkOnServer = PropertyConstants.IS_SYMLINK.equals(
PropertyUtils.selectMatching(propertyValues, PropertyConstants.SYMBOLIC_KEY));
executable = PropertyConstants.EXECUTABLE_ENABLED_VALUE.equals(
PropertyUtils.selectMatching(propertyValues, PropertyConstants.EXECUTABLE_KEY));
}
if (symlinkOnServer != attrs.isSymbolicLink()) {
propertyType =
attrs.isSymbolicLink() ? OfflineChangeType.SYMLINK : OfflineChangeType.NOT_SYMLINK;
} else if (!attrs.isSymbolicLink() && executable != attrs.isExecutable()) {
propertyType = attrs.isExecutable() ? OfflineChangeType.EXEC : OfflineChangeType.NOT_EXEC;
}
}
}
OfflineChange newChange = null;
if (type != null && filter.shouldPend(file, type, serverItemType)) {
newChange = new OfflineChange(path, type, serverItemType);
if (propertyType != null) {
newChange.addChangeType(propertyType);
}
} else if (propertyType != null && filter.shouldPend(file, propertyType, serverItemType)) {
newChange = new OfflineChange(path, propertyType, serverItemType);
}
if (newChange != null) {
offlineChanges.add(newChange);
}
} else if (file.isDirectory()) {
// pend an add if this directory didn't exist
if (!exists && filter.shouldPend(file, OfflineChangeType.ADD, serverItemType)) {
offlineChanges.add(new OfflineChange(path, OfflineChangeType.ADD, serverItemType));
}
if (filter.shouldRecurse(file)) {
// recurse into this directory further
if (shouldRecurse(file, depth)) {
final String[] contents = file.list();
for (int i = 0; i < contents.length; i++) {
final File child = new File(path + File.separatorChar + contents[i]);
scanLocal(child, depth + 1);
}
}
}
// this directory was excluded - mark it as such so that we don
// pend deletes against it later (since we're not scanning it
// locally)
else {
excludes.add(path);
}
}
}