diff options
author | Jens Baumgart | 2011-09-21 08:45:34 +0000 |
---|---|---|
committer | Jens Baumgart | 2011-09-21 08:45:34 +0000 |
commit | 821f8eb1e4cb16d304622480b19a4db519b28db9 (patch) | |
tree | c178ed245b688dddc2069f578b47a5d9f1bf7a1d /org.eclipse.egit.core/src/org/eclipse/egit/core/internal | |
parent | a140b164de5530123ec376c01603b5249e34cbac (diff) | |
download | egit-821f8eb1e4cb16d304622480b19a4db519b28db9.tar.gz egit-821f8eb1e4cb16d304622480b19a4db519b28db9.tar.xz egit-821f8eb1e4cb16d304622480b19a4db519b28db9.zip |
Implement an IndexDiff cache
The index diff cache caches the current IndexDiff for git repositories.
Clients can register listeners to receive notifications if an index
diff changes. Index diffs are updated based on index changed events
of the repository and on resource change events.
Change-Id: I3c7745e9a9a3af20374a944e615b3742ba6e5938
Signed-off-by: Jens Baumgart <jens.baumgart@sap.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.egit.core/src/org/eclipse/egit/core/internal')
5 files changed, 634 insertions, 1 deletions
diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCache.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCache.java new file mode 100644 index 0000000000..e14bfde90e --- /dev/null +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCache.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.egit.core.internal.indexdiff; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.lib.IndexDiff; +import org.eclipse.jgit.lib.Repository; + +/** + * This class provides access to a cached {@link IndexDiff} for a given + * repository + */ +public class IndexDiffCache { + + private Map<Repository, IndexDiffCacheEntry> entries = new HashMap<Repository, IndexDiffCacheEntry>(); + + private Set<IndexDiffChangedListener> listeners = new HashSet<IndexDiffChangedListener>(); + + private IndexDiffChangedListener globalListener; + + /** + * constructor + */ + public IndexDiffCache() { + createGlobalListener(); + } + + /** + * @param repository + * @return cache entry + */ + public IndexDiffCacheEntry getIndexDiffCacheEntry(Repository repository) { + IndexDiffCacheEntry entry; + synchronized (entries) { + entry = entries.get(repository); + if (entry != null) + return entry; + entry = new IndexDiffCacheEntry(repository); + entries.put(repository, entry); + } + entry.addIndexDiffChangedListener(globalListener); + return entry; + } + + /** + * Adds a listener for IndexDiff changes. Note that only caches are + * available for those repositories for which getIndexDiffCacheEntry was + * called. + * + * @param listener + */ + public void addIndexDiffChangedListener(IndexDiffChangedListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + /** + * @param listener + */ + public void removeIndexDiffChangedListener(IndexDiffChangedListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private void createGlobalListener() { + globalListener = new IndexDiffChangedListener() { + public void indexDiffChanged(Repository repository, + IndexDiffData indexDiffData) { + notifyListeners(repository, indexDiffData); + } + }; + } + + private void notifyListeners(Repository repository, + IndexDiffData indexDiffData) { + IndexDiffChangedListener[] tmpListeners; + synchronized (listeners) { + tmpListeners = listeners + .toArray(new IndexDiffChangedListener[listeners.size()]); + } + for (int i = 0; i < tmpListeners.length; i++) + tmpListeners[i].indexDiffChanged(repository, indexDiffData); + } + +} diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCacheEntry.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCacheEntry.java new file mode 100644 index 0000000000..bb9fa4b7bf --- /dev/null +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffCacheEntry.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.egit.core.internal.indexdiff; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.egit.core.Activator; +import org.eclipse.egit.core.CoreText; +import org.eclipse.egit.core.EclipseGitProgressTransformer; +import org.eclipse.egit.core.IteratorService; +import org.eclipse.egit.core.internal.trace.GitTraceLocation; +import org.eclipse.egit.core.project.RepositoryMapping; +import org.eclipse.jgit.events.IndexChangedEvent; +import org.eclipse.jgit.events.IndexChangedListener; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.IndexDiff; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.team.core.Team; + +/** + * This class caches the {@link IndexDiff} for a given repository. The cache + * listens for changes in the related repository and notifies listeners about + * changes. + * + */ +public class IndexDiffCacheEntry { + + private Repository repository; + + private volatile IndexDiffData indexDiffData; + + private Job reloadJob; + + // used to serialize index diff update jobs + private ReentrantLock lock = new ReentrantLock(true); + + private Set<IndexDiffChangedListener> listeners = new HashSet<IndexDiffChangedListener>(); + + private IResourceChangeListener resourceChangeListener; + + /** + * Bit-mask describing interesting changes for IResourceChangeListener + * events + */ + private static int INTERESTING_CHANGES = IResourceDelta.CONTENT + | IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO + | IResourceDelta.OPEN | IResourceDelta.REPLACED + | IResourceDelta.TYPE; + + /** + * @param repository + */ + public IndexDiffCacheEntry(Repository repository) { + this.repository = repository; + repository.getListenerList().addIndexChangedListener( + new IndexChangedListener() { + public void onIndexChanged(IndexChangedEvent event) { + scheduleReloadJob(); + } + }); + scheduleReloadJob(); + createResourceChangeListener(); + } + + /** + * Use this method to register an {@link IndexDiffChangedListener}. The + * listener is notified when a new index diff is available. + * + * @param listener + */ + public void addIndexDiffChangedListener(IndexDiffChangedListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + /** + * @param listener + */ + public void removeIndexDiffChangedListener(IndexDiffChangedListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + /** + * Trigger a new index diff calculation manually + */ + public void refresh() { + scheduleReloadJob(); + } + + /** + * The method returns the current index diff or null. Null is returned if + * the first index diff calculation has not completed yet. + * + * @return index diff + */ + public IndexDiffData getIndexDiff() { + return indexDiffData; + } + + private void scheduleReloadJob() { + if (reloadJob != null) + reloadJob.cancel(); + reloadJob = new Job(getReloadJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + lock.lock(); + try { + IndexDiff result = calcIndexDiff(monitor, getName()); + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + indexDiffData = new IndexDiffData(result); + if (GitTraceLocation.INDEXDIFFCACHE.isActive()) + GitTraceLocation + .getTrace() + .trace(GitTraceLocation.INDEXDIFFCACHE + .getLocation(), + "Updated IndexDiffData\n" + indexDiffData.toString()); //$NON-NLS-1$ + notifyListeners(); + return Status.OK_STATUS; + } finally { + lock.unlock(); + } + } + }; + reloadJob.schedule(); + } + + private void scheduleUpdateJob(final Collection<String> filesToUpdate, + final Collection<IFile> fileResourcesToUpdate) { + Job job = new Job(getReloadJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + lock.lock(); + try { + IndexDiffData result = calcIndexDiffData(monitor, + getName(), filesToUpdate, fileResourcesToUpdate); + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + indexDiffData = result; + if (GitTraceLocation.INDEXDIFFCACHE.isActive()) + GitTraceLocation + .getTrace() + .trace(GitTraceLocation.INDEXDIFFCACHE + .getLocation(), + "Updated IndexDiffData based on resource list \n" + indexDiffData.toString()); //$NON-NLS-1$ + notifyListeners(); + return Status.OK_STATUS; + } finally { + lock.unlock(); + } + } + }; + job.schedule(); + } + + private IndexDiffData calcIndexDiffData(IProgressMonitor monitor, + String jobName, Collection<String> filesToUpdate, + Collection<IFile> fileResourcesToUpdate) { + EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer( + monitor); + final IndexDiff diffForChangedResources; + try { + WorkingTreeIterator iterator = IteratorService + .createInitialIterator(repository); + diffForChangedResources = new IndexDiff(repository, Constants.HEAD, + iterator); + diffForChangedResources.setFilter(PathFilterGroup + .createFromStrings(filesToUpdate)); + diffForChangedResources.diff(jgitMonitor, 0, 0, jobName); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new IndexDiffData(indexDiffData, filesToUpdate, + fileResourcesToUpdate, diffForChangedResources); + } + + private void notifyListeners() { + IndexDiffChangedListener[] tmpListeners; + synchronized (listeners) { + tmpListeners = listeners + .toArray(new IndexDiffChangedListener[listeners.size()]); + } + for (int i = 0; i < tmpListeners.length; i++) + tmpListeners[i].indexDiffChanged(repository, indexDiffData); + } + + private IndexDiff calcIndexDiff(IProgressMonitor monitor, String jobName) { + EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer( + monitor); + + IndexDiff newIndexDiff; + try { + WorkingTreeIterator iterator = IteratorService + .createInitialIterator(repository); + newIndexDiff = new IndexDiff(repository, Constants.HEAD, iterator); + newIndexDiff.diff(jgitMonitor, 0, 0, jobName); + } catch (IOException e) { + throw new RuntimeException(e); + } + return newIndexDiff; + } + + private String getReloadJobName() { + String repoName = Activator.getDefault().getRepositoryUtil() + .getRepositoryName(repository); + return MessageFormat.format(CoreText.IndexDiffCacheEntry_reindexing, repoName); + } + + private void createResourceChangeListener() { + resourceChangeListener = new IResourceChangeListener() { + public void resourceChanged(IResourceChangeEvent event) { + final Collection<String> filesToUpdate = new HashSet<String>(); + final Collection<IFile> fileResourcesToUpdate = new HashSet<IFile>(); + + try { + event.getDelta().accept(new IResourceDeltaVisitor() { + public boolean visit(IResourceDelta delta) + throws CoreException { + // If the file has changed but not in a way that we + // care about (e.g. marker changes to files) then + // ignore + if (delta.getKind() == IResourceDelta.CHANGED + && (delta.getFlags() & INTERESTING_CHANGES) == 0) + return true; + + final IResource resource = delta.getResource(); + + // skip any non-FILE resources + if (resource.getType() != IResource.FILE) + return true; + + // If the resource is not part of a project under + // Git revision control + final RepositoryMapping mapping = RepositoryMapping + .getMapping(resource); + if (mapping == null + || mapping.getRepository() != repository) + // Ignore the change + return true; + + // Don't include ignored resources + if (Team.isIgnoredHint(resource)) + return false; + + String repoRelativePath = mapping + .getRepoRelativePath(resource); + filesToUpdate.add(repoRelativePath); + fileResourcesToUpdate.add((IFile) resource); + + return true; + } + }); + } catch (CoreException e) { + Activator.logError(e.getMessage(), e); + return; + } + + if (!filesToUpdate.isEmpty()) + scheduleUpdateJob(filesToUpdate, fileResourcesToUpdate); + } + + }; + ResourcesPlugin.getWorkspace().addResourceChangeListener( + resourceChangeListener, IResourceChangeEvent.POST_CHANGE); + } + +} diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffChangedListener.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffChangedListener.java new file mode 100644 index 0000000000..0535dfbda4 --- /dev/null +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffChangedListener.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.egit.core.internal.indexdiff; + +import org.eclipse.jgit.lib.Repository; + +/** + * This interface is used to notify clients about changes in index diffs. See + * also: {@link IndexDiffCache} + */ +public interface IndexDiffChangedListener { + + /** + * @param repository + * @param indexDiffData + */ + void indexDiffChanged(Repository repository, IndexDiffData indexDiffData); +} diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffData.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffData.java new file mode 100644 index 0000000000..3d3a9a9135 --- /dev/null +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/indexdiff/IndexDiffData.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.egit.core.internal.indexdiff; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jgit.lib.IndexDiff; + +/** + * This immutable class is used to store the data of an {@link IndexDiff} + * object. + * + */ +public class IndexDiffData { + + private static final String NEW_LINE = "\n"; //$NON-NLS-1$ + + private final Set<String> added; + + private final Set<String> changed; + + private final Set<String> removed; + + private final Set<String> missing; + + private final Set<String> modified; + + private final Set<String> untracked; + + private final Set<String> conflicts; + + private final Collection<IFile> changedFileResources; + + /** + * @param indexDiff + */ + public IndexDiffData(IndexDiff indexDiff) { + added = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getAdded())); + changed = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getChanged())); + removed = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getRemoved())); + missing = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getMissing())); + modified = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getModified())); + untracked = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getUntracked())); + conflicts = Collections.unmodifiableSet(new HashSet<String>(indexDiff + .getConflicting())); + changedFileResources = null; + } + + /** + * This constructor merges the existing IndexDiffData object baseDiff with a + * new IndexDiffData object that was calculated for a subset of files + * (changedFiles). + * + * @param baseDiff + * @param changedFiles + * @param changedFileResources + * @param diffForChangedFiles + */ + public IndexDiffData(IndexDiffData baseDiff, + Collection<String> changedFiles, + Collection<IFile> changedFileResources, + IndexDiff diffForChangedFiles) { + this.changedFileResources = Collections + .unmodifiableCollection(new HashSet<IFile>(changedFileResources)); + Set<String> added2 = new HashSet<String>(baseDiff.getAdded()); + Set<String> changed2 = new HashSet<String>(baseDiff.getChanged()); + Set<String> removed2 = new HashSet<String>(baseDiff.getRemoved()); + Set<String> missing2 = new HashSet<String>(baseDiff.getMissing()); + Set<String> modified2 = new HashSet<String>(baseDiff.getModified()); + Set<String> untracked2 = new HashSet<String>(baseDiff.getUntracked()); + Set<String> conflicts2 = new HashSet<String>(baseDiff.getConflicting()); + + mergeList(added2, changedFiles, diffForChangedFiles.getAdded()); + mergeList(changed2, changedFiles, diffForChangedFiles.getChanged()); + mergeList(removed2, changedFiles, diffForChangedFiles.getRemoved()); + mergeList(missing2, changedFiles, diffForChangedFiles.getMissing()); + mergeList(modified2, changedFiles, diffForChangedFiles.getModified()); + mergeList(untracked2, changedFiles, diffForChangedFiles.getUntracked()); + mergeList(conflicts2, changedFiles, + diffForChangedFiles.getConflicting()); + + added = Collections.unmodifiableSet(added2); + changed = Collections.unmodifiableSet(changed2); + removed = Collections.unmodifiableSet(removed2); + missing = Collections.unmodifiableSet(missing2); + modified = Collections.unmodifiableSet(modified2); + untracked = Collections.unmodifiableSet(untracked2); + conflicts = Collections.unmodifiableSet(conflicts2); + } + + private void mergeList(Set<String> baseList, + Collection<String> changedFiles, Set<String> listForChangedFiles) { + for (String file : changedFiles) { + if (baseList.contains(file)) { + if (!listForChangedFiles.contains(file)) + baseList.remove(file); + } else { + if (listForChangedFiles.contains(file)) + baseList.add(file); + } + } + } + + /** + * @return list of files added to the index, not in the tree + */ + public Set<String> getAdded() { + return Collections.unmodifiableSet(added); + } + + /** + * @return list of files changed from tree to index + */ + public Set<String> getChanged() { + return changed; + } + + /** + * @return list of files removed from index, but in tree + */ + public Set<String> getRemoved() { + return removed; + } + + /** + * @return list of files in index, but not filesystem + */ + public Set<String> getMissing() { + return missing; + } + + /** + * @return list of files modified on disk relative to the index + */ + public Set<String> getModified() { + return modified; + } + + /** + * @return list of files that are not ignored, and not in the index. + */ + public Set<String> getUntracked() { + return untracked; + } + + /** + * @return list of files that are in conflict + */ + public Set<String> getConflicting() { + return conflicts; + } + + /** + * @return the changed files + */ + public Collection<IFile> getChangedFileResources() { + return changedFileResources; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + dumpList(builder, "added", added); //$NON-NLS-1$ + dumpList(builder, "changed", changed); //$NON-NLS-1$ + dumpList(builder, "removed", removed); //$NON-NLS-1$ + dumpList(builder, "missing", missing); //$NON-NLS-1$ + dumpList(builder, "modified", modified); //$NON-NLS-1$ + dumpList(builder, "untracked", untracked); //$NON-NLS-1$ + dumpList(builder, "conflicts", conflicts); //$NON-NLS-1$ + dumpFileResourceList(builder, + "changedFileResources", changedFileResources); //$NON-NLS-1$ + return builder.toString(); + } + + private void dumpList(StringBuilder builder, String listName, + Set<String> list) { + builder.append(listName); + builder.append(NEW_LINE); + for (String entry : list) { + builder.append(entry); + builder.append(NEW_LINE); + } + builder.append(NEW_LINE); + } + + private void dumpFileResourceList(StringBuilder builder, String listName, + Collection<IFile> list) { + if (list == null) + return; + builder.append(listName); + builder.append(NEW_LINE); + for (IFile file : list) { + builder.append(file.getFullPath().toOSString()); + builder.append(NEW_LINE); + } + builder.append(NEW_LINE); + } + +} diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/trace/GitTraceLocation.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/trace/GitTraceLocation.java index f2413ae99b..d1929af2ab 100644 --- a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/trace/GitTraceLocation.java +++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/trace/GitTraceLocation.java @@ -20,7 +20,9 @@ import org.eclipse.osgi.service.debug.DebugTrace; */ public enum GitTraceLocation implements ITraceLocation { /** Core */ - CORE("/debug/core"); //$NON-NLS-1$ + CORE("/debug/core"), //$NON-NLS-1$ + /** IndexDiffCache */ + INDEXDIFFCACHE("/debug/core/indexdiffcache"); //$NON-NLS-1$ /** * Initialize the locations |