git4idea: Added reference tracker

This commit is contained in:
Constantine Plotnikov
2010-07-31 04:17:05 +04:00
parent 47fd185624
commit 79fddb1690
4 changed files with 344 additions and 1 deletions
+28
View File
@@ -178,6 +178,10 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
* The dispatcher object for git configuration events
*/
private final EventDispatcher<GitConfigListener> myConfigListeners = EventDispatcher.create(GitConfigListener.class);
/**
* The dispatcher object for git configuration events
*/
private final EventDispatcher<GitReferenceListener> myReferenceListeners = EventDispatcher.create(GitReferenceListener.class);
/**
* Tracker for ignored files
*/
@@ -198,6 +202,10 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
private final TreeDiffProvider myTreeDiffProvider;
private final GitCommitAndPushExecutor myCommitAndPushExecutor;
/**
* The reference tracker
*/
private GitReferenceTracker myReferenceTracker;
public static GitVcs getInstance(@NotNull Project project) {
return (GitVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(NAME);
@@ -230,6 +238,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
myOutgoingChangesProvider = new GitOutgoingChangesProvider(myProject);
myTreeDiffProvider = new GitTreeDiffProvider(myProject);
myCommitAndPushExecutor = new GitCommitAndPushExecutor(gitCheckinEnvironment);
myReferenceTracker = new GitReferenceTracker(myProject, this, myReferenceListeners.getMulticaster());
myTaskQueue = new BackgroundTaskQueue(myProject, GitBundle.getString("task.queue.title"));
}
@@ -274,6 +283,23 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
myConfigListeners.removeListener(listener);
}
/**
* Add listener for git roots
*
* @param listener the listener to add
*/
public void addGitReferenceListener(GitReferenceListener listener) {
myReferenceListeners.addListener(listener);
}
/**
* Remove listener for git roots
*
* @param listener the listener to remove
*/
public void removeGitReferenceListener(GitReferenceListener listener) {
myReferenceListeners.removeListener(listener);
}
/**
* Add listener for git roots
@@ -481,6 +507,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
if (myGitIgnoreTracker == null) {
myGitIgnoreTracker = new GitIgnoreTracker(myProject, this);
}
myReferenceTracker.activate();
GitUsersComponent.getInstance(myProject).activate();
GitProjectLogManager.getInstance(myProject).activate();
}
@@ -506,6 +533,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
myConfigTracker.dispose();
myConfigTracker = null;
}
myReferenceTracker.deactivate();
GitUsersComponent.getInstance(myProject).deactivate();
GitProjectLogManager.getInstance(myProject).deactivate();
}
@@ -233,7 +233,10 @@ public class GitIgnoreTracker {
private static VirtualFile scanParents(VirtualFile root) {
VirtualFile meta = root.findChild(GIT_FOLDER);
if (meta != null) {
root.findFileByRelativePath(LOCAL_EXCLUDE);
final VirtualFile localExclude = root.findFileByRelativePath(LOCAL_EXCLUDE);
if (localExclude != null) {
localExclude.getTimeStamp();
}
return root;
}
else {
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.vfs;
import com.intellij.openapi.vfs.VirtualFile;
import java.util.EventListener;
/**
* The listener for reference changes
*/
public interface GitReferenceListener extends EventListener {
/**
* This method is invoked when some references changed. The events do not describe which ones.
*
* @param root the root for which references might have changed, null if all roots might have been affected
*/
void referencesChanged(VirtualFile root);
}
@@ -0,0 +1,280 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.vfs;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.*;
import com.intellij.util.containers.HashSet;
import git4idea.GitUtil;
import git4idea.GitVcs;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* The tracker for the git references
*/
public class GitReferenceTracker {
/**
* The root references that must be tracked
*/
private static final Set<String> myRootReferences = new HashSet<String>();
static {
myRootReferences.add("HEAD");
myRootReferences.add("refs");
myRootReferences.add("MERGE_HEAD");
// note, they are here intentionally, since their presence changes the way HEAD is interpreted
myRootReferences.add("rebase_merge");
myRootReferences.add("rebase_apply");
}
/**
* The context project
*/
private final Project myProject;
/**
* The vcs instance that requested the tracking
*/
private final GitVcs myVcs;
/**
* The event multicaster
*/
private final GitReferenceListener myListener;
/**
* The listener for vcs roots
*/
private final GitRootsListener myRootsListener;
/**
* The list of git roots
*/
private final AtomicReference<List<VirtualFile>> myGitRoots =
new AtomicReference<List<VirtualFile>>(Collections.<VirtualFile>emptyList());
/**
* The list of git roots
*/
private final AtomicReference<State> myState = new AtomicReference<State>(State.DEACTIVATED);
/**
* File system listener
*/
private final MyVfsListener myVfsListener;
/**
* The reference tracker
*
* @param project the project
* @param vcs the vcs that created tracker
* @param listener the listener to use for notifications (multicaster is expected)
*/
public GitReferenceTracker(Project project, GitVcs vcs, GitReferenceListener listener) {
myProject = project;
myVcs = vcs;
myListener = listener;
myVfsListener = new MyVfsListener();
myRootsListener = new GitRootsListener() {
@Override
public void gitRootsChanged() {
checkRoots();
}
};
}
/**
* Start listening for events
*/
public void activate() {
if (!myState.compareAndSet(State.DEACTIVATED, State.ACTIVATED)) {
return;
}
if (myProject.isDefault()) {
return;
}
checkRoots();
LocalFileSystem.getInstance().addVirtualFileListener(myVfsListener);
myVcs.addGitRootsListener(myRootsListener);
}
/**
* Deactivate service
*/
public void deactivate() {
if (!myState.compareAndSet(State.ACTIVATED, State.DEACTIVATED)) {
return;
}
removeListeners();
}
/**
* Remove listeners from services
*/
private void removeListeners() {
myVcs.removeGitRootsListener(myRootsListener);
LocalFileSystem.getInstance().removeVirtualFileListener(myVfsListener);
}
/**
* Dispose the tracker removing all listeners
*/
public void dispose() {
if (myState.getAndSet(State.DISPOSED) != State.ACTIVATED) {
return;
}
removeListeners();
}
/**
* Visit roots so file listener will be modified if special status changed
*/
private void checkRoots() {
try {
List<VirtualFile> roots = GitUtil.getGitRoots(myProject, myVcs);
myGitRoots.set(roots);
for (VirtualFile root : roots) {
final VirtualFile git = root.findChild(".git");
if (git != null) {
for (String name : myRootReferences) {
final VirtualFile child = git.findChild(name);
if (child != null) {
child.getTimeStamp();
}
}
final VirtualFile infoRefs = root.findFileByRelativePath("info/refs");
if (infoRefs != null) {
infoRefs.getTimeStamp();
}
visitRecursively(git.findChild("refs"));
}
}
notifyChanges(null);
}
catch (VcsException e) {
myGitRoots.set(Collections.<VirtualFile>emptyList());
}
}
private void notifyChanges(VirtualFile root) {
myListener.referencesChanged(root);
}
/**
* Visit recursively
*
* @param child the child directory to visit
*/
private static void visitRecursively(VirtualFile child) {
if (child == null) {
return;
}
child.getTimeStamp();
LinkedList<VirtualFile> toVisit = new LinkedList<VirtualFile>();
toVisit.add(child);
while (!toVisit.isEmpty()) {
VirtualFile file = toVisit.removeLast();
if (file.isValid() && file.isDirectory()) {
toVisit.addAll(Arrays.asList(file.getChildren()));
}
}
}
/**
* The listener for vfs events
*/
class MyVfsListener extends VirtualFileAdapter {
@Override
public void contentsChanged(VirtualFileEvent event) {
checkFile(event.getParent(), event.getFileName());
}
@Override
public void fileCreated(VirtualFileEvent event) {
checkFile(event.getParent(), event.getFileName());
}
@Override
public void fileDeleted(VirtualFileEvent event) {
checkFile(event.getParent(), event.getFileName());
}
@Override
public void fileMoved(VirtualFileMoveEvent event) {
checkFile(event.getNewParent(), event.getFileName());
checkFile(event.getOldParent(), event.getFileName());
}
@Override
public void fileCopied(VirtualFileCopyEvent event) {
checkFile(event.getParent(), event.getFileName());
}
@Override
public void propertyChanged(VirtualFilePropertyEvent event) {
if (event.getPropertyName() == VirtualFile.PROP_NAME) {
checkFile(event.getParent(), (String)event.getOldValue());
checkFile(event.getParent(), (String)event.getNewValue());
}
}
/**
* Check if the file change might affect reference mapping
*
* @param parent the file parent to check
* @param name the name check
*/
private void checkFile(VirtualFile parent, String name) {
VirtualFile git = parent;
while (git != null && !git.getName().equals(".git")) {
git = git.getParent();
}
if (git == null) {
return;
}
if (parent == git && myRootReferences.contains(name)) {
notifyChanges(git.getParent());
}
else if (parent.getParent() == git && name.equals("refs") && parent.getName().equals("info")) {
notifyChanges(git.getParent());
}
else {
while (parent != null && parent.getParent() != git) {
parent = parent.getParent();
}
if (parent != null && parent.isDirectory() && parent.getName().equals("refs")) {
notifyChanges(git.getParent());
}
}
}
}
/**
* The component state
*/
private enum State {
/**
* Initial state
*/
DEACTIVATED,
/**
* Started state
*/
ACTIVATED,
/**
* Disposed
*/
DISPOSED
}
}