git4idea: IDEADEV-34938: added detection of changes in core.excludesfile. Also added...
authorConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Wed, 3 Jun 2009 15:16:14 +0000 (19:16 +0400)
committerConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Wed, 3 Jun 2009 15:16:14 +0000 (19:16 +0400)
plugins/git4idea/src/git4idea/GitUtil.java
plugins/git4idea/src/git4idea/GitVcs.java
plugins/git4idea/src/git4idea/vfs/GitConfigListener.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/vfs/GitConfigTracker.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/vfs/GitIgnoreTracker.java
plugins/git4idea/src/git4idea/vfs/GitRootTracker.java
plugins/git4idea/src/git4idea/vfs/GitRootsListener.java [new file with mode: 0644]

index 119c2fdd4829234c8dee5fa793eba155d3571f87..9f93e2f92bd127b5ba88b867017d4ca2d3bc22e2 100644 (file)
@@ -580,4 +580,44 @@ public class GitUtil {
   public static String formatLongRev(long rev) {
     return String.format("%015x%x", (rev >>> 4), rev & 0xF);
   }
-}
\ No newline at end of file
+
+  /**
+   * The get the possible base for the path. It tries to find the parent for the provided path, if it fails, it looks for the path without last member.
+   *
+   * @param file the file to get base for
+   * @param path the path to to check
+   * @return the file base
+   */
+  @Nullable
+  public static VirtualFile getPossibleBase(VirtualFile file, String... path) {
+    return getPossibleBase(file, path.length, path);
+  }
+
+  /**
+   * The get the possible base for the path. It tries to find the parent for the provided path, if it fails, it looks for the path without last member.
+   *
+   * @param file the file to get base for
+   * @param n    the length of the path to check
+   * @param path the path to to check
+   * @return the file base
+   */
+  @Nullable
+  private static VirtualFile getPossibleBase(VirtualFile file, int n, String... path) {
+    if (file == null || n <= 0 || n > path.length) {
+      return null;
+    }
+    int i = 1;
+    VirtualFile c = file;
+    for (; c != null && i < n; i++, c = c.getParent()) {
+      if (!path[n - i].equals(c.getName())) {
+        break;
+      }
+    }
+    if (i == n && c != null) {
+      // all components matched
+      return c.getParent();
+    }
+    // try shorter paths paths
+    return getPossibleBase(file, n - 1, path);
+  }
+}
index 6b59ba262cbb0f49fad3744bf78472ff317da4e3..55215b6e7b51bb046b2cdf6c708148e4fffb5923 100644 (file)
@@ -36,6 +36,7 @@ import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
 import com.intellij.openapi.vcs.update.UpdateEnvironment;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.EventDispatcher;
 import git4idea.annotate.GitAnnotationProvider;
 import git4idea.changes.GitChangeProvider;
 import git4idea.changes.GitCommittedChangeListProvider;
@@ -51,9 +52,7 @@ import git4idea.i18n.GitBundle;
 import git4idea.merge.GitMergeProvider;
 import git4idea.rollback.GitRollbackEnvironment;
 import git4idea.update.GitUpdateEnvironment;
-import git4idea.vfs.GitIgnoreTracker;
-import git4idea.vfs.GitRootTracker;
-import git4idea.vfs.GitVFSListener;
+import git4idea.vfs.*;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -147,10 +146,22 @@ public class GitVcs extends AbstractVcs {
    * The tracker that checks validity of git roots
    */
   private GitRootTracker myRootTracker;
+  /**
+   * The dispatcher object for root events
+   */
+  private EventDispatcher<GitRootsListener> myRootListeners = EventDispatcher.create(GitRootsListener.class);
+  /**
+   * The dispatcher object for git configuration events
+   */
+  private EventDispatcher<GitConfigListener> myConfigListeners = EventDispatcher.create(GitConfigListener.class);
   /**
    * Tracker for ignored files
    */
   private GitIgnoreTracker myGitIgnoreTracker;
+  /**
+   * Configuration file tracker
+   */
+  private GitConfigTracker myConfigTracker;
 
   public static GitVcs getInstance(@NotNull Project project) {
     return (GitVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(NAME);
@@ -181,6 +192,43 @@ public class GitVcs extends AbstractVcs {
     myCommittedChangeListProvider = new GitCommittedChangeListProvider(myProject);
   }
 
+  /**
+   * Add listener for git roots
+   *
+   * @param listener the listener to add
+   */
+  public void addGitConfigListener(GitConfigListener listener) {
+    myConfigListeners.addListener(listener);
+  }
+
+  /**
+   * Remove listener for git roots
+   *
+   * @param listener the listener to remove
+   */
+  public void removeGitConfigListener(GitConfigListener listener) {
+    myConfigListeners.removeListener(listener);
+  }
+
+
+  /**
+   * Add listener for git roots
+   *
+   * @param listener the listener to add
+   */
+  public void addGitRootsListener(GitRootsListener listener) {
+    myRootListeners.addListener(listener);
+  }
+
+  /**
+   * Remove listener for git roots
+   *
+   * @param listener the listener to remove
+   */
+  public void removeGitRootsListener(GitRootsListener listener) {
+    myRootListeners.removeListener(listener);
+  }
+
   /**
    * {@inheritDoc}
    */
@@ -340,7 +388,7 @@ public class GitVcs extends AbstractVcs {
   public void start() throws VcsException {
     super.start();
     if (!myProject.isDefault() && myRootTracker == null) {
-      myRootTracker = new GitRootTracker(this, myProject);
+      myRootTracker = new GitRootTracker(this, myProject, myRootListeners.getMulticaster());
     }
   }
 
@@ -365,6 +413,9 @@ public class GitVcs extends AbstractVcs {
     if (myVFSListener == null) {
       myVFSListener = new GitVFSListener(myProject, this);
     }
+    if (myConfigTracker == null) {
+      myConfigTracker = new GitConfigTracker(myProject, this, myConfigListeners.getMulticaster());
+    }
     if (myGitIgnoreTracker == null) {
       myGitIgnoreTracker = new GitIgnoreTracker(myProject, this);
     }
@@ -383,6 +434,10 @@ public class GitVcs extends AbstractVcs {
       myGitIgnoreTracker.dispose();
       myGitIgnoreTracker = null;
     }
+    if (myConfigTracker != null) {
+      myConfigTracker.dispose();
+      myConfigTracker = null;
+    }
     super.deactivate();
   }
 
diff --git a/plugins/git4idea/src/git4idea/vfs/GitConfigListener.java b/plugins/git4idea/src/git4idea/vfs/GitConfigListener.java
new file mode 100644 (file)
index 0000000..de61d13
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2009 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.EventListener;
+
+/**
+ * The listener for configuration change events
+ */
+public interface GitConfigListener extends EventListener {
+  /**
+   * This method is invoked when configuration changes for configured git root.
+   * If ".git/config" changes, only appropriate root is notified. If "~/.gitconfig" changes,
+   * all configured roots are notified.
+   * <p/>
+   * When root configuration changes, the listeners are called for all new roots.
+   *
+   * @param gitRoot    the affected git root
+   * @param configFile the changed configuration file, the parameter might be null if config file is missing
+   */
+  void configChanged(@NotNull VirtualFile gitRoot, @Nullable VirtualFile configFile);
+}
diff --git a/plugins/git4idea/src/git4idea/vfs/GitConfigTracker.java b/plugins/git4idea/src/git4idea/vfs/GitConfigTracker.java
new file mode 100644 (file)
index 0000000..db62df7
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2000-2009 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.ProjectLevelVcsManager;
+import com.intellij.openapi.vfs.*;
+import com.intellij.util.containers.HashSet;
+import git4idea.GitUtil;
+import git4idea.GitVcs;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * The tracker for configuration files, it tracks the following events:
+ * <ul>
+ * <li>Changes in configuration files: .git/config and ~/.gitconfig</li>
+ * </ul>
+ * The tracker assumes that git roots are configured correctly.
+ */
+public class GitConfigTracker implements GitRootsListener {
+  /**
+   * The context project
+   */
+  private Project myProject;
+  /**
+   * The vcs object
+   */
+  private GitVcs myVcs;
+  /**
+   * The vcs manager that tracks content roots
+   */
+  private final ProjectLevelVcsManager myVcsManager;
+  /**
+   * The listener collection (managed by GitVcs object since lifetime of this object is less than lifetime of GitVcs)
+   */
+  private GitConfigListener myMulticaster;
+  /**
+   * The appeared roots that has been already reported as changed
+   */
+  private final HashSet<VirtualFile> myReportedRoots = new HashSet<VirtualFile>();
+  /**
+   * Local file system service
+   */
+  private LocalFileSystem myLocalFileSystem;
+  /**
+   * The file listener
+   */
+  private MyFileListener myFileListener;
+
+  /**
+   * The constructor
+   *
+   * @param project     the context project
+   * @param vcs         the vcs object
+   * @param multicaster the listener collection to use
+   */
+  public GitConfigTracker(Project project, GitVcs vcs, GitConfigListener multicaster) {
+    myProject = project;
+    myVcs = vcs;
+    myMulticaster = multicaster;
+    myLocalFileSystem = LocalFileSystem.getInstance();
+    myVcsManager = ProjectLevelVcsManager.getInstance(project);
+    myVcs.addGitRootsListener(this);
+    myFileListener = new MyFileListener();
+    VirtualFileManager.getInstance().addVirtualFileListener(myFileListener);
+    gitRootsChanged();
+  }
+
+  /**
+   * This method is invoked when set of configured roots changed.
+   */
+  public void gitRootsChanged() {
+    VirtualFile[] contentRoots = myVcsManager.getRootsUnderVcs(myVcs);
+    if (contentRoots == null || contentRoots.length == 0) {
+      return;
+    }
+    Set<VirtualFile> currentRootSet = GitUtil.gitRootsForPaths(Arrays.asList(contentRoots));
+    HashSet<VirtualFile> newRoots = new HashSet<VirtualFile>(currentRootSet);
+    synchronized (myReportedRoots) {
+      for (Iterator<VirtualFile> i = myReportedRoots.iterator(); i.hasNext();) {
+        VirtualFile root = i.next();
+        if (!root.isValid()) {
+          i.remove();
+        }
+      }
+      newRoots.removeAll(myReportedRoots);
+      myReportedRoots.clear();
+      myReportedRoots.addAll(currentRootSet);
+    }
+    for (VirtualFile root : newRoots) {
+      VirtualFile config = root.findFileByRelativePath(".git/config");
+      myMulticaster.configChanged(root, config);
+    }
+    // visit user home directory in order to notice .gitconfig changes later
+    VirtualFile userHome = getUserHome();
+    if (userHome != null) {
+      userHome.getChildren();
+    }
+  }
+
+  /**
+   * @return user home directory
+   */
+  @Nullable
+  private VirtualFile getUserHome() {
+    return myLocalFileSystem.findFileByPath(System.getProperty("user.home"));
+  }
+
+
+  /**
+   * Dispose the tracker removing all registered listeners
+   */
+  public void dispose() {
+    myVcs.removeGitRootsListener(this);
+    VirtualFileManager.getInstance().removeVirtualFileListener(myFileListener);
+  }
+
+
+  /**
+   * The listener for the file system that checks if the configuration files are changed.
+   * Note that events are checked in quite a shallow form. More radical events will cause
+   * remapping of git roots and gitRootsChanged() event will be delivered.
+   */
+  private class MyFileListener extends VirtualFileAdapter {
+
+    /**
+     * Check if the event affects configuration files in registered roots
+     *
+     * @param file the file to check
+     */
+    private void checkConfigAffected(VirtualFile file) {
+      if (file.getName().equals(".gitconfig")) {
+        VirtualFile userHome = getUserHome();
+        VirtualFile parent = file.getParent();
+        if (userHome != null && parent != null && parent.equals(userHome)) {
+          HashSet<VirtualFile> allRoots;
+          synchronized (myReportedRoots) {
+            allRoots = new HashSet<VirtualFile>(myReportedRoots);
+          }
+          for (VirtualFile root : allRoots) {
+            myMulticaster.configChanged(root, file);
+          }
+        }
+        return;
+      }
+      VirtualFile base = GitUtil.getPossibleBase(file, ".git", "config");
+      if (base != null) {
+        boolean reported;
+        synchronized (myReportedRoots) {
+          reported = myReportedRoots.contains(base);
+        }
+        if (reported) {
+          myMulticaster.configChanged(base, file);
+        }
+      }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void fileCreated(VirtualFileEvent event) {
+      checkConfigAffected(event.getFile());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void beforeFileDeletion(VirtualFileEvent event) {
+      checkConfigAffected(event.getFile());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void contentsChanged(VirtualFileEvent event) {
+      checkConfigAffected(event.getFile());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void fileCopied(VirtualFileCopyEvent event) {
+      super.fileCopied(event);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void fileMoved(VirtualFileMoveEvent event) {
+      String fileName = event.getFileName();
+      VirtualFile newParent = event.getNewParent();
+      VirtualFile oldParent = event.getOldParent();
+      if (fileName.equals("config")) {
+        checkParent(newParent);
+        checkParent(oldParent);
+      }
+      if (fileName.equals(".gitconfig")) {
+        VirtualFile userHome = getUserHome();
+        if (userHome != null && (newParent.equals(userHome) || oldParent.equals(userHome))) {
+          HashSet<VirtualFile> allRoots;
+          synchronized (myReportedRoots) {
+            allRoots = new HashSet<VirtualFile>(myReportedRoots);
+          }
+          VirtualFile config = userHome.findChild(".gitconfig");
+          for (VirtualFile root : allRoots) {
+            myMulticaster.configChanged(root, config);
+          }
+        }
+      }
+    }
+
+    /**
+     * Check parent and report event if it is one of reported roots
+     *
+     * @param parent the parent to check
+     */
+    private void checkParent(VirtualFile parent) {
+      if (parent.getName().equals(".git")) {
+        VirtualFile base = parent.getParent();
+        if (base != null) {
+          boolean reported;
+          synchronized (myReportedRoots) {
+            reported = myReportedRoots.contains(base);
+          }
+          if (reported) {
+            myMulticaster.configChanged(base, parent.findChild("config"));
+          }
+        }
+      }
+    }
+  }
+}
index 532d5667e832c18cf678ba2b58350a5a6a440f98..8ba3f471db2ee558f39578e7d17eaad154e36717 100644 (file)
 package git4idea.vfs;
 
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
-import com.intellij.openapi.vcs.VcsListener;
+import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
 import com.intellij.openapi.vfs.*;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+import git4idea.GitUtil;
 import git4idea.GitVcs;
+import git4idea.config.GitConfigUtil;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.File;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * The tracker for ".gitignore" and ".git/info/exclude" files. If it detects changes
  * in ignored files in the project it dirties the project state. The following changes
@@ -42,6 +53,10 @@ public class GitIgnoreTracker {
    * The vcs manager that tracks content roots
    */
   private final ProjectLevelVcsManager myVcsManager;
+  /**
+   * The context project
+   */
+  private Project myProject;
   /**
    * The vcs instance
    */
@@ -65,11 +80,27 @@ public class GitIgnoreTracker {
   /**
    * The listener for vcs events
    */
-  private final VcsListener myVcsListener;
+  private final GitRootsListener myVcsListener;
   /**
    * The listener for file events
    */
   private final MyFileListener myFileListener;
+  /**
+   * The git configuration listener
+   */
+  private GitConfigListener myConfigListener;
+  /**
+   * The map from roots to paths of exclude files from config
+   */
+  private final Map<VirtualFile, String> myExcludeFiles = new HashMap<VirtualFile, String>();
+  /**
+   * The map from roots to paths of exclude files from config
+   */
+  private final Set<String> myExcludeFilesPaths = new HashSet<String>();
+  /**
+   * Cygwin absolute path prefix
+   */
+  private static final String CYGDRIVE_PREFIX = "/cygdrive/";
 
   /**
    * The constructor for service
@@ -78,15 +109,38 @@ public class GitIgnoreTracker {
    * @param vcs     the git vcs instance
    */
   public GitIgnoreTracker(Project project, GitVcs vcs) {
+    myProject = project;
     myVcs = vcs;
     myVcsManager = ProjectLevelVcsManager.getInstance(project);
     myDirtyScopeManager = VcsDirtyScopeManager.getInstance(project);
-    myVcsListener = new VcsListener() {
-      public void directoryMappingChanged() {
+    myVcsListener = new GitRootsListener() {
+      public void gitRootsChanged() {
         scan();
       }
     };
-    myVcsManager.addVcsListener(myVcsListener);
+    myConfigListener = new GitConfigListener() {
+      public void configChanged(@NotNull VirtualFile gitRoot, @Nullable VirtualFile configFile) {
+        String oldPath;
+        synchronized (myExcludeFiles) {
+          if (!myExcludeFiles.containsKey(gitRoot)) {
+            return;
+          }
+          oldPath = myExcludeFiles.get(gitRoot);
+        }
+        String newPath = getExcludeFile(gitRoot);
+        if (oldPath == null ? newPath == null : oldPath.equals(newPath)) {
+          return;
+        }
+        synchronized (myExcludeFiles) {
+          myExcludeFiles.put(gitRoot, newPath);
+          myExcludeFilesPaths.clear();
+          myExcludeFilesPaths.addAll(myExcludeFiles.values());
+        }
+        myDirtyScopeManager.dirDirtyRecursively(gitRoot);
+      }
+    };
+    myVcs.addGitRootsListener(myVcsListener);
+    myVcs.addGitConfigListener(myConfigListener);
     myFileListener = new MyFileListener();
     VirtualFileManager.getInstance().addVirtualFileListener(myFileListener);
     scan();
@@ -100,10 +154,74 @@ public class GitIgnoreTracker {
     if (contentRoots == null || contentRoots.length == 0) {
       return;
     }
+    HashMap<VirtualFile, String> newRoots = new HashMap<VirtualFile, String>();
     for (VirtualFile root : contentRoots) {
-      scanParents(root);
+      VirtualFile gitRoot = scanParents(root);
+      if (!newRoots.containsKey(gitRoot)) {
+        newRoots.put(gitRoot, getExcludeFile(gitRoot));
+      }
       // note that the component relies on root tracker to scan all children including .gitignore files.
     }
+    synchronized (myExcludeFiles) {
+      myExcludeFiles.clear();
+      myExcludeFiles.putAll(newRoots);
+      myExcludeFilesPaths.clear();
+      myExcludeFilesPaths.addAll(myExcludeFiles.values());
+    }
+  }
+
+  /**
+   * Get normalized path for git root and visit config path so it will be noticed by file events
+   *
+   * @param gitRoot the git root to examine
+   * @return the normalized path
+   */
+  @Nullable
+  private String getExcludeFile(VirtualFile gitRoot) {
+    try {
+      String file = GitConfigUtil.getValue(myProject, gitRoot, "core.excludesfile");
+      file = fixFileName(file);
+      if (file != null && file.trim().length() != 0) {
+        // locate path so it will be tracked
+        VirtualFile fileForPath = LocalFileSystem.getInstance().findFileByPath(file);
+        if (fileForPath != null) {
+          return fileForPath.getPath();
+        }
+      }
+    }
+    catch (VcsException e) {
+      // return null
+    }
+    return null;
+  }
+
+  /**
+   * Fix name for the file
+   *
+   * @param file the file name to fix
+   * @return the file name is fixed according to MSYS and cygwin rules
+   */
+  private String fixFileName(String file) {
+    if (SystemInfo.isWindows && file != null && file.startsWith("/")) {
+      int cp = CYGDRIVE_PREFIX.length();
+      if (file.startsWith(CYGDRIVE_PREFIX) && file.length() > cp + 3 && Character.isLetter(file.charAt(cp)) && file.charAt(cp + 1) == '/') {
+        // cygwin absolute path syntax is used
+        return String.valueOf(file.charAt(cp)) + ":" + file.substring(cp + 1);
+      }
+      if (file.length() > 3 && Character.isLetter(file.charAt(1)) && file.charAt(2) == '/') {
+        // msys drive syntax is used
+        return String.valueOf(file.charAt(1)) + ":" + file.substring(2);
+      }
+      // otherwise the path is relative to "bin/git.exe"
+      File gitDir = new File(myVcs.getSettings().GIT_EXECUTABLE).getParentFile();
+      if (gitDir != null) {
+        gitDir = gitDir.getParentFile();
+      }
+      if (gitDir != null) {
+        return new File(gitDir, file.substring(1)).getPath();
+      }
+    }
+    return file;
   }
 
   /**
@@ -111,24 +229,28 @@ public class GitIgnoreTracker {
    *
    * @param root the directory to scan
    */
-  private static void scanParents(VirtualFile root) {
+  @Nullable
+  private static VirtualFile scanParents(VirtualFile root) {
     VirtualFile meta = root.findChild(GIT_FOLDER);
     if (meta != null) {
       root.findFileByRelativePath(LOCAL_EXCLUDE);
+      return root;
     }
     else {
       VirtualFile parent = root.getParent();
       if (parent != null) {
-        scanParents(parent);
+        return scanParents(parent);
       }
     }
+    return null;
   }
 
   /**
    * Dispose the component removing all related listeners
    */
   public void dispose() {
-    myVcsManager.removeVcsListener(myVcsListener);
+    myVcs.removeGitRootsListener(myVcsListener);
+    myVcs.removeGitConfigListener(myConfigListener);
     VirtualFileManager.getInstance().removeVirtualFileListener(myFileListener);
   }
 
@@ -161,6 +283,15 @@ public class GitIgnoreTracker {
         myDirtyScopeManager.dirDirtyRecursively(event.getNewParent());
         myDirtyScopeManager.dirDirtyRecursively(event.getOldParent());
       }
+      checkExcludeFile(event.getOldParent().findChild(event.getFileName()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void fileMoved(VirtualFileMoveEvent event) {
+      checkExcludeFile(event.getNewParent().findChild(event.getFileName()));
     }
 
     /**
@@ -192,50 +323,37 @@ public class GitIgnoreTracker {
         }
         return;
       }
-      VirtualFile base = getBase(file, LOCAL_EXCLUDE_ARRAY);
+      VirtualFile base = GitUtil.getPossibleBase(file, LOCAL_EXCLUDE_ARRAY);
       if (base != null) {
         myDirtyScopeManager.dirDirtyRecursively(base);
+        return;
       }
+      checkExcludeFile(file);
     }
 
     /**
-     * The get the possible base for the path. It tries to find the parent for the provided path, if it fails, it looks for the path without last member.
-     *
-     * @param file the file to get base for
-     * @param path the path to to check
-     * @return the file base
-     */
-    @Nullable
-    private VirtualFile getBase(VirtualFile file, String... path) {
-      return getBase(file, path.length, path);
-    }
-
-    /**
-     * The get the possible base for the path. It tries to find the parent for the provided path, if it fails, it looks for the path without last member.
+     * Check if the file is exclude file (specified in git config) and process it
      *
-     * @param file the file to get base for
-     * @param n    the length of the path to check
-     * @param path the path to to check
-     * @return the file base
+     * @param file the file to process
      */
-    @Nullable
-    private VirtualFile getBase(VirtualFile file, int n, String... path) {
-      if (file == null || n <= 0 || n > path.length) {
-        return null;
-      }
-      int i = 1;
-      VirtualFile c = file;
-      for (; c != null && i < n; i++, c = c.getParent()) {
-        if (!path[n - i].equals(c.getName())) {
-          break;
+    private void checkExcludeFile(VirtualFile file) {
+      String path = file.getPath();
+      LinkedList<VirtualFile> toDirty = null;
+      synchronized (myExcludeFiles) {
+        if (myExcludeFilesPaths.contains(path)) {
+          toDirty = new LinkedList<VirtualFile>();
+          for (Map.Entry<VirtualFile, String> entry : myExcludeFiles.entrySet()) {
+            if (path.equals(entry.getValue())) {
+              toDirty.add(entry.getKey());
+            }
+          }
         }
       }
-      if (i == n && c != null) {
-        // all components matched
-        return c.getParent();
+      if (toDirty != null) {
+        for (VirtualFile f : toDirty) {
+          myDirtyScopeManager.dirDirtyRecursively(f);
+        }
       }
-      // try shorter paths paths
-      return getBase(file, n - 1, path);
     }
   }
 }
index 47ef5aab688235a9fac09557e1e522cda3943f17..8243b4b05f93cff519c000dc04cd7f63e5ca369e 100644 (file)
@@ -93,13 +93,19 @@ public class GitRootTracker implements VcsListener {
    * Local file system service
    */
   private LocalFileSystem myLocalFileSystem;
+  /**
+   * The multicaster for root events
+   */
+  private GitRootsListener myMulticaster;
 
   /**
    * The constructor
    *
-   * @param project the project instance
+   * @param project     the project instance
+   * @param multicaster
    */
-  public GitRootTracker(GitVcs vcs, @NotNull Project project) {
+  public GitRootTracker(GitVcs vcs, @NotNull Project project, @NotNull GitRootsListener multicaster) {
+    myMulticaster = multicaster;
     if (project.isDefault()) {
       throw new IllegalArgumentException("The project must not be default");
     }
@@ -253,6 +259,7 @@ public class GitRootTracker implements VcsListener {
         }
       }
     });
+    myMulticaster.gitRootsChanged();
   }
 
   /**
diff --git a/plugins/git4idea/src/git4idea/vfs/GitRootsListener.java b/plugins/git4idea/src/git4idea/vfs/GitRootsListener.java
new file mode 100644 (file)
index 0000000..b4558f2
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2009 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 java.util.EventListener;
+
+/**
+ * The listener interface that allows tracking actual changes in
+ * the vcs root configuration.
+ */
+public interface GitRootsListener extends EventListener {
+  /**
+   * The method is invoked when set of actual roots changes.
+   * This could happen when the list of configured roots changes or when
+   * ".git" directories added/removed for the configured roots.
+   */
+  void gitRootsChanged();
+}