From 69d44d63bdc4962ae6c7d7ffccf00ebb6b2f1c16 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Wed, 17 Nov 2010 18:25:43 +0300 Subject: [PATCH] Fixed IDEA-59641 "Git: externally commited file isn't unmarked until explicit refresh" Introduced GitIndexChangeListener to listen to .git/index of all repositories in the project. To avoid marking dirty, when IDEA touches .git/index (while committing, adding or else) notify GitIndexChangeListener when IDEA's operation starts and when it ends. Notification is made from GitHandler.start; several GitCommands are flagged as index modifiers. --- .../src/git4idea/GitIndexChangeListener.java | 112 ++++++++++++++++++ plugins/git4idea/src/git4idea/GitVcs.java | 10 ++ .../src/git4idea/commands/GitCommand.java | 31 +++-- .../src/git4idea/commands/GitHandler.java | 12 ++ 4 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/GitIndexChangeListener.java diff --git a/plugins/git4idea/src/git4idea/GitIndexChangeListener.java b/plugins/git4idea/src/git4idea/GitIndexChangeListener.java new file mode 100644 index 000000000000..ca1f4c00b7c0 --- /dev/null +++ b/plugins/git4idea/src/git4idea/GitIndexChangeListener.java @@ -0,0 +1,112 @@ +/* + * 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; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsListener; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.*; +import com.intellij.util.messages.MessageBusConnection; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Listens to .dir/index and marks everything dirty if this file changes (for example, when external commit happens). + * To avoid marking everything dirty in case of commit from IDEA (or other action), there are methods + * {@link #internalIndexChangeStarted()} and {@link #internalIndexChangeEnded()}. + * @author Kirill Likhodedov + */ +public class GitIndexChangeListener extends VirtualFileAdapter implements VcsListener { + private final Project myProject; + private final AtomicBoolean myInternalIndexChangeInProgress = new AtomicBoolean(); + private AtomicLong myInternalIndexChangeEndTime = new AtomicLong(); + private MessageBusConnection myConnection; + + public GitIndexChangeListener(Project project) { + myProject = project; + myConnection = myProject.getMessageBus().connect(); + // listen to .git/index files for all repositories + loadIndexFilesForAllMappings(); + myConnection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, this); + VirtualFileManager.getInstance().addVirtualFileListener(this); + } + + public void dispose() { + myConnection.disconnect(); + VirtualFileManager.getInstance().removeVirtualFileListener(this); + } + + @Override + public void directoryMappingChanged() { + loadIndexFilesForAllMappings(); + } + + @Override + public void contentsChanged(VirtualFileEvent event) { + final VirtualFile file = event.getFile(); + if (file.getParent().getName().equals(".git") && file.getName().equals("index")) { + if (!myInternalIndexChangeInProgress.get() && !internalIndexChangeHappenedRecently()) { + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + } + } + } + + /** + * Notifies the listener that IDEA is going to change index file right away. + * For example, at the beginning of the commit process. + */ + public void internalIndexChangeStarted() { + myInternalIndexChangeInProgress.set(true); + } + + /** + * Notifies the listener that IDEA has finished changing index file. + */ + public void internalIndexChangeEnded() { + // no synchronization here, because it's not dangerous if index change time is a bit different. + myInternalIndexChangeInProgress.set(false); + myInternalIndexChangeEndTime.set(System.currentTimeMillis()); + } + + /** + * @return true if last index change via IDEA happened less than a second ago. + */ + private boolean internalIndexChangeHappenedRecently() { + return System.currentTimeMillis() - myInternalIndexChangeEndTime.get() < 1000; + } + + // load (and subscribe to changes) .git/index for all repositories + private void loadIndexFilesForAllMappings() { + for (VcsRoot root : ProjectLevelVcsManager.getInstance(myProject).getAllVcsRoots()) { + loadIndexFile(root.path); + } + } + + /** + * Loads .git/index file of the given git root, + * so that the correspondent VirtualFile is created and thus changes to this file will be fired to the listener. + */ + private static void loadIndexFile(VirtualFile vcsRoot) { + if (vcsRoot != null) { + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(vcsRoot.getPath(), ".git/index")); + } + } + +} diff --git a/plugins/git4idea/src/git4idea/GitVcs.java b/plugins/git4idea/src/git4idea/GitVcs.java index ea4ac1f32381..5a3767748ccd 100644 --- a/plugins/git4idea/src/git4idea/GitVcs.java +++ b/plugins/git4idea/src/git4idea/GitVcs.java @@ -119,6 +119,7 @@ public class GitVcs extends AbstractVcs { private GitReferenceTracker myReferenceTracker; private boolean isActivated; // If true, the vcs was activated private GitExecutableValidator myExecutableValidator; + private GitIndexChangeListener myIndexChangeListener; public static GitVcs getInstance(@NotNull Project project) { return (GitVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(NAME); @@ -423,6 +424,7 @@ public class GitVcs extends AbstractVcs { if (myGitIgnoreTracker == null) { myGitIgnoreTracker = new GitIgnoreTracker(myProject, this); } + myIndexChangeListener = new GitIndexChangeListener(myProject); myReferenceTracker.activate(); GitUsersComponent.getInstance(myProject).activate(); GitProjectLogManager.getInstance(myProject).activate(); @@ -452,6 +454,9 @@ public class GitVcs extends AbstractVcs { myConfigTracker.dispose(); myConfigTracker = null; } + if (myIndexChangeListener != null) { + myIndexChangeListener.dispose(); + } myReferenceTracker.deactivate(); GitUsersComponent.getInstance(myProject).deactivate(); GitProjectLogManager.getInstance(myProject).deactivate(); @@ -695,4 +700,9 @@ public class GitVcs extends AbstractVcs { public GitExecutableValidator getExecutableValidator() { return myExecutableValidator; } + + public GitIndexChangeListener getIndexChangeListener() { + return myIndexChangeListener; + } + } diff --git a/plugins/git4idea/src/git4idea/commands/GitCommand.java b/plugins/git4idea/src/git4idea/commands/GitCommand.java index cae54cb0f145..818b54d38781 100644 --- a/plugins/git4idea/src/git4idea/commands/GitCommand.java +++ b/plugins/git4idea/src/git4idea/commands/GitCommand.java @@ -55,23 +55,23 @@ public class GitCommand { public static final GitCommand UPDATE_INDEX = write("update-index"); public static final GitCommand VERSION = meta("version"); + // these commands modify .git/index + private static final GitCommand[] INDEX_MODIFIERS = {ADD, BRANCH, CHECKOUT, COMMIT, MERGE, RESET, RM, STASH}; + static { + for (GitCommand command : INDEX_MODIFIERS) { + command.myModifiesIndex = true; + } + } + /** * Name of environment variable that specifies editor for the git */ public static final String GIT_EDITOR_ENV = "GIT_EDITOR"; - /** - * The command myName - */ - @NotNull @NonNls private final String myName; - /** - * Locking policy for the command - */ - @NotNull private final LockingPolicy myLocking; - /** - * Thread policy for the command - */ - @NotNull private final ThreadPolicy myThreading; + @NotNull @NonNls private final String myName; // command name passed to git + @NotNull private final LockingPolicy myLocking; // Locking policy for the command + @NotNull private final ThreadPolicy myThreading; // Thread policy for the command + private boolean myModifiesIndex; // true if the command modifies .git/index /** * The constructor @@ -150,6 +150,13 @@ public class GitCommand { return myThreading; } + /** + * @return true if this command modifies .git/index file + */ + public boolean modifiesIndex() { + return myModifiesIndex; + } + /** * The myLocking policy for the command */ diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java index d3f11f0e021b..3d3ec2769ac8 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java @@ -25,6 +25,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.EventDispatcher; import com.intellij.util.Processor; +import git4idea.GitIndexChangeListener; import git4idea.GitUtil; import git4idea.GitVcs; import git4idea.config.GitVcsApplicationSettings; @@ -361,6 +362,13 @@ public abstract class GitHandler { */ public synchronized void start() { checkNotStarted(); + + GitIndexChangeListener indexChangeListener = null; + if (myCommand.modifiesIndex()) { + indexChangeListener = myVcs.getIndexChangeListener(); + indexChangeListener.internalIndexChangeStarted(); + } + try { // setup environment if (!myProject.isDefault() && !mySilent && (myVcs != null)) { @@ -386,6 +394,10 @@ public abstract class GitHandler { catch (Throwable t) { cleanupEnv(); myListeners.getMulticaster().startFailed(t); + } finally { + if (indexChangeListener != null) { + indexChangeListener.internalIndexChangeEnded(); + } } }