/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
* Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.nbgit;
import java.io.IOException;
import org.nbgit.util.GitUtils;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.nbgit.util.GitCommand;
import org.nbgit.util.exclude.Excludes;
import org.netbeans.modules.turbo.CustomProviders;
import org.netbeans.modules.turbo.Turbo;
import org.netbeans.modules.versioning.spi.VCSContext;
import org.netbeans.modules.versioning.spi.VersioningSupport;
import org.netbeans.modules.versioning.util.Utils;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
/**
* Central part of status management, deduces and caches statuses of files under version control.
*
* @author Maros Sandor
*/
public class StatusCache {
/**
* Indicates that status of a file changed and listeners SHOULD check new status
* values if they are interested in this file.
* The New value is a ChangedEvent object (old StatusInfo object may be null)
*/
public static final String PROP_FILE_STATUS_CHANGED = "status.changed"; // NOI18N
/**
* A special map saying that no file inside the folder is managed.
*/
private static final Map<File, StatusInfo> NOT_MANAGED_MAP = new NotManagedMap();
public static final File REPOSITORY_STATUS_UNKNOWN = null; // Constant File objects that can be safely reused
// Files that have a revision number cannot share StatusInfo objects
private static final StatusInfo FILE_STATUS_EXCLUDED = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_EXCLUDED, false);
private static final StatusInfo FILE_STATUS_EXCLUDED_DIRECTORY = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_EXCLUDED, true);
private static final StatusInfo FILE_STATUS_UPTODATE_DIRECTORY = new StatusInfo(StatusInfo.STATUS_VERSIONED_UPTODATE, true);
private static final StatusInfo FILE_STATUS_NOTMANAGED = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_NOTMANAGED, false);
private static final StatusInfo FILE_STATUS_NOTMANAGED_DIRECTORY = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_NOTMANAGED, true);
private static final StatusInfo FILE_STATUS_UNKNOWN = new StatusInfo(StatusInfo.STATUS_UNKNOWN, false);
private static final StatusInfo FILE_STATUS_NEWLOCALLY = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY, false);
private static final StatusInfo FILE_STATUS_CONFLICT = new StatusInfo(StatusInfo.STATUS_VERSIONED_CONFLICT, false);
private static final StatusInfo FILE_STATUS_REMOVEDLOCALLY = new StatusInfo(StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY, false);
private PropertyChangeSupport listenerSupport = new PropertyChangeSupport(this);
/**
* Caches status of files in memory and on disk
*/
private final Turbo turbo;
private final String FILE_STATUS_MAP = DiskMapTurboProvider.ATTR_STATUS_MAP;
private DiskMapTurboProvider cacheProvider;
private Git git;
private Set<FileSystem> filesystemsToRefresh;
StatusCache(Git git) {
this.git = git;
cacheProvider = new DiskMapTurboProvider();
turbo = Turbo.createCustom(new CustomProviders() {
private final Set providers = Collections.singleton(cacheProvider);
public Iterator providers() {
return providers.iterator();
}
}, 200, 5000);
}
// --- Public interface -------------------------------------------------
/**
* Lists <b>modified files</b> and all folders that are known to be inside
* this folder. There are locally modified files present
* plus any files that exist in the folder in the remote repository.
*
* @param dir folder to list
* @return
*/
public File[] listFiles(File dir) {
Set<File> files = getScannedFiles(dir, null).keySet();
return files.toArray(new File[files.size()]);
}
/**
* Lists <b>interesting files</b> that are known to be inside given folders.
* These are locally and remotely modified and ignored files.
*
* <p>This method returns both folders and files.
*
* @param context context to examine
* @param includeStatus limit returned files to those having one of supplied statuses
* @return File [] array of interesting files
*/
public File[] listFiles(VCSContext context, int includeStatus) {
Set<File> set = new HashSet<File>();
Map<File, StatusInfo> allFiles = cacheProvider.getAllModifiedValues();
if (allFiles == null) {
Git.LOG.log(Level.FINE, "StatusCache: listFiles(): allFiles == null"); // NOI18N
return new File[0];
}
for (File file : allFiles.keySet()) {
StatusInfo info = allFiles.get(file);
if ((info.getStatus() & includeStatus) == 0) {
continue;
}
Set<File> roots = context.getRootFiles();
for (File root : roots) {
if (VersioningSupport.isFlat(root)) {
if (file.equals(root) || file.getParentFile().equals(root)) {
set.add(file);
break;
}
} else if (Utils.isAncestorOrEqual(root, file)) {
set.add(file);
break;
}
}
}
if (context.getExclusions().size() > 0) {
for (File excluded : context.getExclusions()) {
for (Iterator<File> j = set.iterator(); j.hasNext();) {
if (Utils.isAncestorOrEqual(excluded, j.next())) {
j.remove();
}
}
}
}
return set.toArray(new File[set.size()]);
}
/**
* Lists <b>interesting files</b> that are known to be inside given folders.
* These are locally and remotely modified and ignored files.
*
* <p>Comparing to CVS this method returns both folders and files.
*
* @param roots context to examine
* @param includeStatus limit returned files to those having one of supplied statuses
* @return File [] array of interesting files
*/
public File[] listFiles(File[] roots, int includeStatus) {
Set<File> set = new HashSet<File>();
Map<File, StatusInfo> allFiles = cacheProvider.getAllModifiedValues();
for (File file : allFiles.keySet()) {
StatusInfo info = allFiles.get(file);
if ((info.getStatus() & includeStatus) == 0) {
continue;
}
for (int j = 0; j < roots.length; j++) {
File root = roots[j];
if (VersioningSupport.isFlat(root)) {
if (file.getParentFile().equals(root)) {
set.add(file);
break;
}
} else if (Utils.isAncestorOrEqual(root, file)) {
set.add(file);
break;
}
}
}
return set.toArray(new File[set.size()]);
}
/**
* Check if this context has at least one file with the passed in status
*
* @param context context to examine
* @param includeStatus file status to check for
* @return boolean true if this context contains at least one file with the includeStatus, false otherwise
*/
public boolean containsFileOfStatus(VCSContext context, int includeStatus) {
Map<File, StatusInfo> allFiles = cacheProvider.getAllModifiedValues();
if (allFiles == null) {
Git.LOG.log(Level.FINE, "containsFileOfStatus(): allFiles == null"); // NOI18N
return false;
}
Set<File> roots = context.getRootFiles();
Set<File> exclusions = context.getExclusions();
Set<File> setAllFiles = allFiles.keySet();
boolean bExclusions = exclusions != null && exclusions.size() > 0;
boolean bContainsFile = false;
for (File file : setAllFiles) {
StatusInfo info = allFiles.get(file);
if ((info.getStatus() & includeStatus) == 0) {
continue;
}
for (File root : roots) {
if (VersioningSupport.isFlat(root)) {
if (file.equals(root) || file.getParentFile().equals(root)) {
bContainsFile = true;
break;
}
} else if (Utils.isAncestorOrEqual(root, file)) {
bContainsFile = true;
break;
}
}
// Check it is not an excluded file
if (bContainsFile && bExclusions) {
for (File excluded : exclusions) {
if (!Utils.isAncestorOrEqual(excluded, file)) {
return true;
}
}
} else if (bContainsFile) {
return true;
}
}
return false;
}
/**
* Determines the versioning status of a file. This method accesses disk and may block for a long period of time.
*
* @param file file to get status for
* @return StatusInfo structure containing the file status
* @see StatusInfo
*/
public StatusInfo getStatus(File file) {
if (file.isDirectory() && excludeFile(file)) {
return StatusCache.FILE_STATUS_EXCLUDED_DIRECTORY;
}
File dir = file.getParentFile();
if (dir == null) {
return StatusCache.FILE_STATUS_NOTMANAGED;
}
Map files = getScannedFiles(dir, null);
if (files == StatusCache.NOT_MANAGED_MAP) {
return StatusCache.FILE_STATUS_NOTMANAGED;
}
StatusInfo fi = (StatusInfo) files.get(file);
if (fi != null) {
return fi;
}
if (!exists(file)) {
return StatusCache.FILE_STATUS_UNKNOWN;
}
if (file.isDirectory()) {
return refresh(file, REPOSITORY_STATUS_UNKNOWN);
} else {
return new StatusInfo(StatusInfo.STATUS_VERSIONED_UPTODATE, false);
}
}
/**
* Looks up cached file status.
*
* @param file file to check
* @return give file's status or null if the file's status is not in cache
*/
@SuppressWarnings("unchecked") // Need to change turbo module to remove warning at source
StatusInfo getCachedStatus( File file, boolean bCheckSharability) {
File parent = file.getParentFile();
if (parent == null) {
return StatusCache.FILE_STATUS_NOTMANAGED_DIRECTORY;
}
Map<File, StatusInfo> files = (Map<File, StatusInfo>) turbo.readEntry(parent, FILE_STATUS_MAP);
StatusInfo fi = files != null ? files.get(file) : null;
if (fi != null) {
return fi;
}
if (file.isDirectory()) {
if (excludeFile(file)) {
return StatusCache.FILE_STATUS_EXCLUDED_DIRECTORY;
} else {
return StatusCache.FILE_STATUS_UPTODATE_DIRECTORY;
}
}
return fi;
}
private StatusInfo refresh(File file, File repoStat, boolean forceChangeEvent) {
Git.LOG.log(Level.FINE, "refresh(): {0}", file); // NOI18N
File dir = file.getParentFile();
if (dir == null) {
return StatusCache.FILE_STATUS_NOTMANAGED;
}
Map<File, StatusInfo> files = getScannedFiles(dir, null); // Has side effect of updating the cache
if (files == StatusCache.NOT_MANAGED_MAP && repoStat == StatusCache.REPOSITORY_STATUS_UNKNOWN) {
return StatusCache.FILE_STATUS_NOTMANAGED;
}
StatusInfo current = files.get(file);
StatusInfo fi = createFileInformation(file, true);
if (StatusInfo.equivalent(fi, current)) {
if (forceChangeEvent) {
fireFileStatusChanged(file, current, fi);
}
return fi;
}
// do not include uptodate files into cache, missing directories must be included
if (current == null && !fi.isDirectory() && fi.getStatus() == StatusInfo.STATUS_VERSIONED_UPTODATE) {
if (forceChangeEvent) {
fireFileStatusChanged(file, current, fi);
}
return fi;
}
file = FileUtil.normalizeFile(file);
dir = FileUtil.normalizeFile(dir);
Map<File, StatusInfo> newFiles = new HashMap<File, StatusInfo>(files);
if (fi.getStatus() == StatusInfo.STATUS_UNKNOWN) {
newFiles.remove(file);
turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
} else if (fi.getStatus() == StatusInfo.STATUS_VERSIONED_UPTODATE && file.isFile()) {
newFiles.remove(file);
} else {
newFiles.put(file, fi);
}
assert newFiles.containsKey(dir) == false;
turbo.writeEntry(dir, FILE_STATUS_MAP, newFiles.size() == 0 ? null : newFiles);
if (file.isDirectory() && needRecursiveRefresh(fi, current)) {
File[] content = listFiles(file); // Has side effect of updating the cache
for (int i = 0; i < content.length; i++) {
refresh(content[i], StatusCache.REPOSITORY_STATUS_UNKNOWN);
}
}
fireFileStatusChanged(file, current, fi);
return fi;
}
private StatusInfo createFileInformation(File file, Boolean callStatus) {
Git.LOG.log(Level.FINE, "createFileInformation(): {0} {1}", new Object[]{file, callStatus}); // NOI18N
if (file == null) {
return FILE_STATUS_UNKNOWN;
}
if (git.isAdministrative(file)) {
return FILE_STATUS_EXCLUDED_DIRECTORY; // Excluded
}
File rootManagedFolder = git.getTopmostManagedParent(file);
if (rootManagedFolder == null) {
return FILE_STATUS_UNKNOWN; // Avoiding returning NOT_MANAGED dir or file
}
if (file.isDirectory()) {
if (Excludes.isIgnored(file)) {
return FILE_STATUS_EXCLUDED_DIRECTORY;
} else {
return FILE_STATUS_UPTODATE_DIRECTORY;
}
}
if (callStatus == false) {
if (Excludes.isIgnored(file)) {
return FILE_STATUS_EXCLUDED;
}
return null;
}
return GitCommand.getSingleStatus(rootManagedFolder, file);
}
/**
* Refreshes the status of the file given the repository status. Repository status is filled
* in when this method is called while processing server output.
*
* <p>Note: it's not necessary if you use Subversion.getClient(), it
* updates the cache automatically using onNotify(). It's not
* fully reliable for removed files.
*
* @param file
* @param repositoryStatus
*/
public StatusInfo refresh(File file, File repositoryStatus) {
return refresh(file, repositoryStatus, false);
}
@SuppressWarnings("unchecked") // Need to change turbo module to remove warning at source
public Map<File, StatusInfo> getScannedFiles(File dir, Map<File, StatusInfo> interestingFiles) {
Map<File, StatusInfo> files;
files = (Map<File, StatusInfo>) turbo.readEntry(dir, FILE_STATUS_MAP);
if (files != null) {
return files;
}
if (!dir.exists() && interestingFiles == null) {
return StatusCache.NOT_MANAGED_MAP;
}
dir = FileUtil.normalizeFile(dir);
files = scanFolder(dir, interestingFiles);
assert files.containsKey(dir) == false;
turbo.writeEntry(dir, FILE_STATUS_MAP, files);
if (interestingFiles == null) {
for (File file : files.keySet()) {
StatusInfo info = files.get(file);
if ((info.getStatus() & (StatusInfo.STATUS_LOCAL_CHANGE | StatusInfo.STATUS_NOTVERSIONED_EXCLUDED)) != 0) {
fireFileStatusChanged(file, null, info);
}
}
}
return files;
}
public void refreshFileStatus(File file, StatusInfo fi, Map<File, StatusInfo> interestingFiles) {
refreshFileStatus(file, fi, interestingFiles, false);
}
public void refreshFileStatus(File file, StatusInfo fi, Map<File, StatusInfo> interestingFiles, boolean alwaysFireEvent) {
if (file == null || fi == null) {
return;
}
File dir = file.getParentFile();
if (dir == null) {
return;
}
Map<File, StatusInfo> files = getScannedFiles(dir, interestingFiles);
if (files == null || files == StatusCache.NOT_MANAGED_MAP) {
return;
}
StatusInfo current = files.get(file);
if (StatusInfo.equivalent(fi, current)) {
if (StatusInfo.equivalent(FILE_STATUS_NEWLOCALLY, fi)) {
if (Excludes.isIgnored(file)) {
Git.LOG.log(Level.FINE, "refreshFileStatus() file: {0} was LocallyNew but is NotSharable", file.getAbsolutePath()); // NOI18N
fi = FILE_STATUS_EXCLUDED;
} else {
return;
}
} else if (!StatusInfo.equivalent(FILE_STATUS_REMOVEDLOCALLY, fi)) {
return;
}
}
if (StatusInfo.equivalent(FILE_STATUS_NEWLOCALLY, fi)) {
if (StatusInfo.equivalent(FILE_STATUS_EXCLUDED, current)) {
Git.LOG.log(Level.FINE, "refreshFileStatus() file: {0} was LocallyNew but is Excluded", file.getAbsolutePath()); // NOI18N
return;
} else if (current == null) {
if (Excludes.isIgnored(file)) {
Git.LOG.log(Level.FINE, "refreshFileStatus() file: {0} was LocallyNew but current is null and is not NotSharable", file.getAbsolutePath()); // NOI18N
fi = FILE_STATUS_EXCLUDED;
}
}
}
file = FileUtil.normalizeFile(file);
dir = FileUtil.normalizeFile(dir);
Map<File, StatusInfo> newFiles = new HashMap<File, StatusInfo>(files);
if (fi.getStatus() == StatusInfo.STATUS_UNKNOWN) {
newFiles.remove(file);
turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
} else if (fi.getStatus() == StatusInfo.STATUS_VERSIONED_UPTODATE && file.isFile()) {
newFiles.remove(file);
} else {
newFiles.put(file, fi);
}
assert files.containsKey(dir) == false;
turbo.writeEntry(dir, FILE_STATUS_MAP, newFiles);
if (interestingFiles == null) {
fireFileStatusChanged(file, current, fi);
} else if (alwaysFireEvent) {
fireFileStatusChanged(file, null, fi);
}
return;
}
private boolean needRecursiveRefresh(StatusInfo fi, StatusInfo current) {
if (fi.getStatus() == StatusInfo.STATUS_NOTVERSIONED_EXCLUDED ||
current != null && current.getStatus() == StatusInfo.STATUS_NOTVERSIONED_EXCLUDED) {
return true;
}
if (fi.getStatus() == StatusInfo.STATUS_NOTVERSIONED_NOTMANAGED ||
current != null && current.getStatus() == StatusInfo.STATUS_NOTVERSIONED_NOTMANAGED) {
return true;
}
return false;
}
/**
* Refreshes information about a given file or directory ONLY if its status is already cached. The
* only exception are non-existing files (new-in-repository) whose statuses are cached in all cases.
*
* @param file
* @param repositoryStatus
*/
public void refreshCached(File file, File repositoryStatus) {
refresh(file, repositoryStatus);
}
/**
* Refreshes status of the specfied file or all files inside the
* specified directory.
*
* @param file
*/
public void refreshCached(File root) {
if (!root.isDirectory()) {
refresh(root, StatusCache.REPOSITORY_STATUS_UNKNOWN);
return;
}
File repository = git.getTopmostManagedParent(root);
if (repository == null) {
return;
}
File roots[] = new File[1];
roots[0] = root;
File[] files = listFiles(roots, ~0);
if (files.length == 0) {
return;
}
Map<File, StatusInfo> allFiles;
try {
allFiles = GitCommand.getAllStatus(repository, root);
for (int i = 0; i < files.length; i++) {
File file = files[i];
StatusInfo fi = allFiles.get(file);
if (fi == null) // We have a file in the cache which seems to have disappeared
{
refresh(file, StatusCache.REPOSITORY_STATUS_UNKNOWN);
} else {
refreshFileStatus(file, fi, null);
}
}
} catch (IOException ex) {
Git.LOG.log(Level.FINE, "refreshCached() file: {0} {1} { 2} ", new Object[]{repository.getAbsolutePath(), root.getAbsolutePath(), ex.toString()}); // NOI18N
}
}
/**
* Refreshes status of all files inside given context.
*
* @param ctx context to refresh
*/
public void refreshCached(VCSContext ctx) {
for (File root : ctx.getRootFiles()) {
refreshCached(root);
}
}
public void addToCache(Set<File> files) {
StatusInfo fi = new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY, null, false);
HashMap<File, Map<File, StatusInfo>> dirMap = new HashMap<File, Map<File, StatusInfo>>(files.size());
for (File file : files) {
File parent = file.getParentFile();
file = FileUtil.normalizeFile(file);
parent = FileUtil.normalizeFile(parent);
Map<File, StatusInfo> currentDirMap = dirMap.get(parent);
if (currentDirMap == null) {
// 20 is a guess at number of files in a directory
currentDirMap = new HashMap<File, StatusInfo>(20);
dirMap.put(parent, currentDirMap);
}
currentDirMap.put(file, fi);
}
for (File dir : dirMap.keySet()) {
dir = FileUtil.normalizeFile(dir);
Map<File, StatusInfo> currentDirMap = dirMap.get(dir);
turbo.writeEntry(dir, FILE_STATUS_MAP, currentDirMap);
}
}
// --- Package private contract ------------------------------------------
Map<File, StatusInfo> getAllModifiedFiles() {
return cacheProvider.getAllModifiedValues();
}
/**
* Refreshes given directory and all subdirectories.
*
* @param dir directory to refresh
*/
void directoryContentChanged(File dir) {
Map originalFiles = (Map) turbo.readEntry(dir, FILE_STATUS_MAP);
if (originalFiles != null) {
for (Iterator i = originalFiles.keySet().iterator(); i.hasNext();) {
File file = (File) i.next();
refresh(file, StatusCache.REPOSITORY_STATUS_UNKNOWN);
}
}
}
// --- Private methods ---------------------------------------------------
private boolean isNotManagedByDefault(File dir) {
return !dir.exists();
}
/**
* Scans all files in the given folder, computes and stores their CVS status.
*
* @param dir directory to scan
* @return Map map to be included in the status cache (File => StatusInfo)
*/
private Map<File, StatusInfo> scanFolder(File dir, Map<File, StatusInfo> interestingFiles) {
File[] files = dir.listFiles();
if (files == null) {
if (interestingFiles == null) {
files = new File[0];
} else {
files = interestingFiles.keySet().toArray(new File[interestingFiles.keySet().size()]);
}
}
Map<File, StatusInfo> folderFiles = new HashMap<File, StatusInfo>(files.length);
Git.LOG.log(Level.FINE, "scanFolder(): {0}", dir); // NOI18N
if (git.isAdministrative(dir)) {
folderFiles.put(dir, FILE_STATUS_EXCLUDED_DIRECTORY); // Excluded dir
return folderFiles;
}
File rootManagedFolder = git.getTopmostManagedParent(dir);
if (rootManagedFolder == null) {
// Only interested in looking for Git managed dirs
for (File file : files) {
if (file.isDirectory() && git.getTopmostManagedParent(file) != null) {
if (excludeFile(file)) {
Git.LOG.log(Level.FINE, "scanFolder NotMng Ignored Dir {0}: exclude SubDir: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
folderFiles.put(file, FILE_STATUS_EXCLUDED_DIRECTORY); // Excluded dir
} else {
Git.LOG.log(Level.FINE, "scanFolder NotMng Dir {0}: up to date Dir: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
folderFiles.put(file, FILE_STATUS_UPTODATE_DIRECTORY);
}
// Do NOT put any unmanaged dir's (FILE_STATUS_NOTMANAGED_DIRECTORY) or
// files (FILE_STATUS_NOTMANAGED) into the folderFiles
}
}
return folderFiles;
}
if (Excludes.isIgnored(dir)) {
for (File file : files) {
if (GitUtils.isPartOfGitMetadata(file)) {
continue;
}
if (file.isDirectory()) {
folderFiles.put(file, FILE_STATUS_EXCLUDED_DIRECTORY); // Excluded dir
Git.LOG.log(Level.FINE, "scanFolder Mng Ignored Dir {0}: exclude SubDir: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
} else {
Git.LOG.log(Level.FINE, "scanFolder Mng Ignored Dir {0}: exclude File: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
folderFiles.put(file, FILE_STATUS_EXCLUDED);
}
}
return folderFiles;
}
if (interestingFiles == null) {
interestingFiles = GitCommand.getInterestingStatus(rootManagedFolder, dir);
}
if (interestingFiles == null) {
return folderFiles;
}
for (File file : files) {
if (GitUtils.isPartOfGitMetadata(file)) {
continue;
}
if (file.isDirectory()) {
if (excludeFile(file)) {
Git.LOG.log(Level.FINE, "scanFolder Mng Dir {0}: exclude Dir: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
folderFiles.put(file, FILE_STATUS_EXCLUDED_DIRECTORY); // Excluded dir
} else {
Git.LOG.log(Level.FINE, "scanFolder Mng Dir {0}: up to date Dir: {1}", // NOI18N
new Object[]{dir.getAbsolutePath(), file.getName()});
folderFiles.put(file, FILE_STATUS_UPTODATE_DIRECTORY);
}
} else {
StatusInfo fi = interestingFiles.get(file);
if (fi == null) // We have removed -i from GitCommand.getInterestingFiles
// so we might have a file we should be ignoring
{
fi = createFileInformation(file, false);
}
if (fi != null && fi.getStatus() != StatusInfo.STATUS_VERSIONED_UPTODATE) {
folderFiles.put(file, fi);
}
}
}
return folderFiles;
}
private boolean exists(File file) {
if (!file.exists()) {
return false;
}
return file.getAbsolutePath().equals(FileUtil.normalizeFile(file).getAbsolutePath());
}
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
listenerSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listenerSupport.removePropertyChangeListener(listener);
}
private void fireFileStatusChanged(File file, StatusInfo oldInfo, StatusInfo newInfo) {
listenerSupport.firePropertyChange(PROP_FILE_STATUS_CHANGED, null, new ChangedEvent(file, oldInfo, newInfo));
}
public void refreshDirtyFileSystems() {
Set<FileSystem> filesystems = getFilesystemsToRefresh();
FileSystem[] fstoRefresh = new FileSystem[filesystems.size()];
synchronized (filesystems) {
fstoRefresh = filesystems.toArray(new FileSystem[filesystems.size()]);
filesystems.clear();
}
for (int i = 0; i < fstoRefresh.length; i++) {
// don't call refresh() in synchronized (filesystems). It may lead to a deadlock.
fstoRefresh[i].refresh(true);
}
}
private Set<FileSystem> getFilesystemsToRefresh() {
if (filesystemsToRefresh == null) {
filesystemsToRefresh = new HashSet<FileSystem>();
}
return filesystemsToRefresh;
}
private boolean excludeFile(File file) {
return git.isAdministrative(file) || Excludes.isIgnored(file);
}
private static final class NotManagedMap extends AbstractMap<File, StatusInfo> {
public Set<Entry<File, StatusInfo>> entrySet() {
return Collections.emptySet();
}
}
public static class ChangedEvent {
private File file;
private StatusInfo oldInfo;
private StatusInfo newInfo;
public ChangedEvent(File file, StatusInfo oldInfo, StatusInfo newInfo) {
this.file = file;
this.oldInfo = oldInfo;
this.newInfo = newInfo;
}
public File getFile() {
return file;
}
public StatusInfo getOldInfo() {
return oldInfo;
}
public StatusInfo getNewInfo() {
return newInfo;
}
}
}