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.
This commit is contained in:
Kirill Likhodedov
2010-11-18 13:15:50 +03:00
parent 937cf767fa
commit 69d44d63bd
4 changed files with 153 additions and 12 deletions
@@ -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"));
}
}
}
+10
View File
@@ -119,6 +119,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
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<CommittedChangeList> {
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<CommittedChangeList> {
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<CommittedChangeList> {
public GitExecutableValidator getExecutableValidator() {
return myExecutableValidator;
}
public GitIndexChangeListener getIndexChangeListener() {
return myIndexChangeListener;
}
}
@@ -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
*/
@@ -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();
}
}
}