mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user