diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 1dc047a07dfc..bc3f4f3ba5e2 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1266,6 +1266,9 @@ git.use.setsid.for.native.ssh.description=Wrap 'git' process with 'setsid' for r This fixes issue with SSH_ASKPASS if IDE is executed from terminal or under DM/WM that pass system TTY from Xorg to GUI applications. git.use.push.force.with.lease=true git.use.push.force.with.lease.description=Use '--force-with-lease' to push from IDE instead of '--force' +git.process.ignored=false +git.process.ignored.description=Process and highlight Git ignored files. + hg4idea.process.ignored=true hg4idea.process.ignored.description=Process and highlight mercurial ignored files. \ diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java index 182f3dac374a..32c00189eb24 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java @@ -507,10 +507,13 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec Set refreshFiles) { while (iterator.hasNext()) { VirtualFile file = iterator.next().getVirtualFile(); - if (file != null && isIgnoredFile(file)) { - iterator.remove(); - fileHolder.addFile(file); - refreshFiles.add(file); + if (file != null && isVcsIgnoredFile(file)) { + AbstractVcs vcs = VcsUtil.getVcsFor(myProject, file); + if (vcs != null) { + iterator.remove(); + fileHolder.addFile(vcs, file); + refreshFiles.add(file); + } } } } @@ -566,7 +569,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec takeChanges = myUpdateException == null; } if (takeChanges) { - // update IDEA-level ignored files + // update vcs ignored files updateIgnoredFiles(dataHolder.getComposite()); } @@ -647,7 +650,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec // do actual requests about file statuses Getter disposedGetter = () -> myProject.isDisposed() || myUpdater.isStopped(); final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(updater, - dataHolder.getComposite(), disposedGetter, this); + dataHolder.getComposite(), disposedGetter); for (final VcsDirtyScope scope : scopes) { indicator.checkCanceled(); @@ -1505,7 +1508,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec scheduleUnversionedUpdate(); } - private void updateIgnoredFiles(final FileHolderComposite composite) { + private void updateIgnoredFiles(FileHolderComposite composite) { final VirtualFileHolder vfHolder = composite.getVFHolder(FileHolder.HolderType.UNVERSIONED); final List unversionedFiles = vfHolder.getFiles(); exchangeWithIgnored(composite, vfHolder, unversionedFiles); @@ -1516,10 +1519,14 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec } private void exchangeWithIgnored(FileHolderComposite composite, VirtualFileHolder vfHolder, List unversionedFiles) { + IgnoredFilesCompositeHolder ignoredFileHolder = composite.getIgnoredFileHolder(); for (VirtualFile file : unversionedFiles) { - if (isIgnoredFile(file)) { - vfHolder.removeFile(file); - composite.getIgnoredFileHolder().addFile(file); + if (ignoredFileHolder.containsFile(file)) { + AbstractVcs vcs = VcsUtil.getVcsFor(myProject, file); + if (vcs != null) { + vfHolder.removeFile(file); + ignoredFileHolder.addFile(vcs, file); + } } } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/IgnoredFilesCompositeHolder.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/IgnoredFilesCompositeHolder.java index d50091438fc7..903eb0af7c51 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/IgnoredFilesCompositeHolder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/IgnoredFilesCompositeHolder.java @@ -13,7 +13,6 @@ import java.util.*; public class IgnoredFilesCompositeHolder implements FileHolder { private final Map myVcsIgnoredHolderMap; - private IgnoredFilesHolder myIdeIgnoredFilesHolder; private final Project myProject; private final ProjectLevelVcsManager myVcsManager; @@ -21,7 +20,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { super(); myProject = project; myVcsIgnoredHolderMap = new HashMap<>(); - myIdeIgnoredFilesHolder = new RecursiveFileHolder(myProject, HolderType.IGNORED); myVcsManager = ProjectLevelVcsManager.getInstance(myProject); } @@ -29,7 +27,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { public void cleanAll() { myVcsIgnoredHolderMap.values().forEach(IgnoredFilesHolder::cleanAll); myVcsIgnoredHolderMap.clear(); - myIdeIgnoredFilesHolder.cleanAll(); } @Override @@ -38,7 +35,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { if (myVcsIgnoredHolderMap.containsKey(vcs)) { myVcsIgnoredHolderMap.get(vcs).cleanAndAdjustScope(scope); } - myIdeIgnoredFilesHolder.cleanAndAdjustScope(scope); } @Override @@ -47,7 +43,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { for (Map.Entry entry : myVcsIgnoredHolderMap.entrySet()) { result.myVcsIgnoredHolderMap.put(entry.getKey(), (IgnoredFilesHolder)entry.getValue().copy()); } - result.myIdeIgnoredFilesHolder = (IgnoredFilesHolder)myIdeIgnoredFilesHolder.copy(); return result; } @@ -56,10 +51,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { return HolderType.IGNORED; } - public void addFile(@NotNull VirtualFile file) { - myIdeIgnoredFilesHolder.addFile(file); - } - public void addFile(@NotNull AbstractVcs vcs, @NotNull VirtualFile file) { myVcsIgnoredHolderMap.get(vcs).addFile(file); } @@ -70,7 +61,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { } public boolean containsFile(@NotNull VirtualFile file) { - if (myIdeIgnoredFilesHolder.containsFile(file)) return true; final AbstractVcs vcs = myVcsManager.getVcsFor(file); if (vcs == null) return false; final IgnoredFilesHolder ignoredFilesHolder = myVcsIgnoredHolderMap.get(vcs); @@ -80,7 +70,6 @@ public class IgnoredFilesCompositeHolder implements FileHolder { @NotNull public Collection values() { final HashSet result = ContainerUtil.newHashSet(); - result.addAll(myIdeIgnoredFilesHolder.values()); result.addAll(StreamEx.of(myVcsIgnoredHolderMap.values()).flatCollection(IgnoredFilesHolder::values).toSet()); return result; } @@ -113,11 +102,11 @@ public class IgnoredFilesCompositeHolder implements FileHolder { return false; } IgnoredFilesCompositeHolder other = (IgnoredFilesCompositeHolder)obj; - return myVcsIgnoredHolderMap.equals(other.myVcsIgnoredHolderMap) && myIdeIgnoredFilesHolder.equals(other.myIdeIgnoredFilesHolder); + return myVcsIgnoredHolderMap.equals(other.myVcsIgnoredHolderMap); } @Override public int hashCode() { - return Objects.hash(myVcsIgnoredHolderMap, myIdeIgnoredFilesHolder); + return Objects.hash(myVcsIgnoredHolderMap); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdatingChangeListBuilder.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdatingChangeListBuilder.java index 47f80a15e8e5..e43e16adf018 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdatingChangeListBuilder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdatingChangeListBuilder.java @@ -23,7 +23,6 @@ class UpdatingChangeListBuilder implements ChangelistBuilder { private final ChangeListUpdater myChangeListUpdater; private final FileHolderComposite myComposite; private final Getter myDisposedGetter; - private final ChangeListManager myChangeListManager; private final ProjectLevelVcsManager myVcsManager; private VcsDirtyScope myScope; @@ -31,14 +30,12 @@ class UpdatingChangeListBuilder implements ChangelistBuilder { private Factory myAdditionalInfo; - UpdatingChangeListBuilder(final ChangeListUpdater changeListUpdater, - final FileHolderComposite composite, - final Getter disposedGetter, - final ChangeListManager changeListManager) { + UpdatingChangeListBuilder(ChangeListUpdater changeListUpdater, + FileHolderComposite composite, + Getter disposedGetter) { myChangeListUpdater = changeListUpdater; myComposite = composite; myDisposedGetter = disposedGetter; - myChangeListManager = changeListManager; myVcsManager = ProjectLevelVcsManager.getInstance(changeListUpdater.getProject()); } @@ -60,7 +57,7 @@ class UpdatingChangeListBuilder implements ChangelistBuilder { public void processChangeInList(Change change, @Nullable ChangeList changeList, VcsKey vcsKey) { checkIfDisposed(); - LOG.debug("[processChangeInList-1] entering, cl name: " + ((changeList == null) ? null: changeList.getName()) + + LOG.debug("[processChangeInList-1] entering, cl name: " + ((changeList == null) ? null : changeList.getName()) + " change: " + ChangesUtil.getFilePath(change).getPath()); final String fileName = ChangesUtil.getFilePath(change).getName(); if (FileTypeManager.getInstance().isFileIgnored(fileName)) { @@ -104,15 +101,7 @@ class UpdatingChangeListBuilder implements ChangelistBuilder { @Override public void processUnversionedFile(VirtualFile file) { if (acceptFile(file, false)) { - if (myChangeListManager.isIgnoredFile(file)) { - myComposite.getIgnoredFileHolder().addFile(file); - } - else if (myComposite.getIgnoredFileHolder().containsFile(file)) { - // does not need to add: parent dir is already added - } - else { - myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).addFile(file); - } + myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).addFile(file); // if a file was previously marked as switched through recursion, remove it from switched list myComposite.getSwitchedFileHolder().removeFile(file); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsIgnoredFilesHolder.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsIgnoredFilesHolder.java index d1bdeb3517e8..2452cd8896b1 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsIgnoredFilesHolder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsIgnoredFilesHolder.java @@ -24,6 +24,8 @@ public interface VcsIgnoredFilesHolder extends IgnoredFilesHolder { default boolean isInUpdatingMode() {return false;} + default void startRescan(){} + interface Provider { @NotNull AbstractVcs getVcs(); diff --git a/plugins/git4idea/resources/META-INF/plugin.xml b/plugins/git4idea/resources/META-INF/plugin.xml index fe5f542005b6..d4d59292b609 100644 --- a/plugins/git4idea/resources/META-INF/plugin.xml +++ b/plugins/git4idea/resources/META-INF/plugin.xml @@ -190,6 +190,8 @@ + + diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index 948612d647e3..30376187cd0f 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -70,6 +70,9 @@ public interface Git { @NotNull GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners); + @NotNull + Set ignoredFiles(@NotNull Project project, @NotNull VirtualFile root) throws VcsException; + @NotNull Set untrackedFiles(@NotNull Project project, @NotNull VirtualFile root, @Nullable Collection files) throws VcsException; diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index a02660757722..56279a4fc821 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -48,8 +48,6 @@ import static java.util.Collections.*; /** * Easy-to-use wrapper of common native Git commands. * Most of them return result as {@link GitCommandResult}. - * - * @author Kirill Likhodedov */ public class GitImpl extends GitImplBase { @@ -74,6 +72,40 @@ public class GitImpl extends GitImplBase { return runCommand(h); } + @NotNull + @Override + public Set ignoredFiles(@NotNull Project project, @NotNull VirtualFile root) throws VcsException { + GitLineHandler h = new GitLineHandler(project, root, GitCommand.STATUS); + h.setSilent(true); + h.addParameters("--ignored", "--porcelain", "-z"); + h.endOptions(); + + final String output = runCommand(h).getOutputOrThrow(); + return parseLSFilesOutput(root, output, "!! "); + } + + @NotNull + private static Set parseLSFilesOutput(@NotNull VirtualFile root, String output, @NotNull String fileStatusPrefix) { + if (StringUtil.isEmptyOrSpaces(output)) return emptySet(); + + final Set ignoredFiles = new HashSet<>(); + for (String relPath : output.split("\u0000")) { + if (!fileStatusPrefix.isEmpty() && !relPath.startsWith(fileStatusPrefix)) continue; + + VirtualFile f = root.findFileByRelativePath(relPath.substring(fileStatusPrefix.length())); + if (f == null) { + // files was created on disk, but VirtualFile hasn't yet been created, + // when the GitChangeProvider has already been requested about changes. + LOG.info(String.format("VirtualFile for path [%s] is null", relPath)); + } + else { + ignoredFiles.add(f); + } + } + + return ignoredFiles; + } + /** *

Queries Git for the unversioned files in the given paths.

*

Ignored files are left ignored, i. e. no information is returned about them (thus this method may also be used as a @@ -108,7 +140,6 @@ public class GitImpl extends GitImplBase { @NotNull VirtualFile root, @Nullable List relativePaths) throws VcsException { - final Set untrackedFiles = new HashSet<>(); GitLineHandler h = new GitLineHandler(project, root, GitCommand.LS_FILES); h.setSilent(true); h.addParameters("--exclude-standard", "--others", "-z"); @@ -118,23 +149,7 @@ public class GitImpl extends GitImplBase { } final String output = runCommand(h).getOutputOrThrow(); - if (StringUtil.isEmptyOrSpaces(output)) { - return untrackedFiles; - } - - for (String relPath : output.split("\u0000")) { - VirtualFile f = root.findFileByRelativePath(relPath); - if (f == null) { - // files was created on disk, but VirtualFile hasn't yet been created, - // when the GitChangeProvider has already been requested about changes. - LOG.info(String.format("VirtualFile for path [%s] is null", relPath)); - } - else { - untrackedFiles.add(f); - } - } - - return untrackedFiles; + return parseLSFilesOutput(root, output, ""); } @Override diff --git a/plugins/git4idea/src/git4idea/ignore/GitIgnoredFilesHolder.kt b/plugins/git4idea/src/git4idea/ignore/GitIgnoredFilesHolder.kt new file mode 100644 index 000000000000..94fc69ed9f11 --- /dev/null +++ b/plugins/git4idea/src/git4idea/ignore/GitIgnoredFilesHolder.kt @@ -0,0 +1,80 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.ignore + +import com.intellij.dvcs.repo.AsyncFilesManagerListener +import com.intellij.openapi.project.Project +import com.intellij.openapi.vcs.AbstractVcs +import com.intellij.openapi.vcs.changes.ChangesViewRefresher +import com.intellij.openapi.vcs.changes.FileHolder +import com.intellij.openapi.vcs.changes.VcsIgnoredFilesHolder +import com.intellij.openapi.vcs.changes.VcsModifiableDirtyScope +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.EventDispatcher +import com.intellij.vcsUtil.VcsUtil +import git4idea.GitUtil +import git4idea.GitVcs +import git4idea.repo.GitRepository +import git4idea.repo.GitRepositoryManager + +class GitIgnoredFilesHolder(val project: Project, val manager: GitRepositoryManager) : VcsIgnoredFilesHolder { + + private val listeners = EventDispatcher.create(AsyncFilesManagerListener::class.java) + + private val vcsIgnoredHolderMap = + manager.repositories.associateTo(hashMapOf()) { it to it.ignoredFilesHolder } + + override fun isInUpdatingMode() = vcsIgnoredHolderMap.values.any(GitRepositoryIgnoredHolder::isInUpdateMode) + + override fun notifyVcsStarted(scope: AbstractVcs<*>?) {} + + override fun addFile(file: VirtualFile) { + findIgnoreHolderByFile(file)?.addFile(file) + } + + override fun containsFile(file: VirtualFile) = findIgnoreHolderByFile(file)?.containsFile(file) ?: false + + override fun values() = vcsIgnoredHolderMap.flatMap { it.value.getIgnoredFiles() } + + override fun startRescan() { + fireUpdateStarted() + vcsIgnoredHolderMap.values.forEach { it.startRescan() } + fireUpdateFinished() + } + + override fun cleanAll() { + vcsIgnoredHolderMap.clear() + } + + override fun copy() = GitIgnoredFilesHolder(project, manager) + + override fun getType() = FileHolder.HolderType.IGNORED + + override fun cleanAndAdjustScope(scope: VcsModifiableDirtyScope) {} + + private fun findIgnoreHolderByFile(file: VirtualFile): GitRepositoryIgnoredHolder? { + val repositoryRoot = VcsUtil.getVcsRootFor(project, file) ?: return null + val repositoryForRoot = manager.getRepositoryForRoot(repositoryRoot) ?: return null + return vcsIgnoredHolderMap[repositoryForRoot] + } + + private fun fireUpdateStarted() { + listeners.multicaster.updateStarted() + } + + private fun fireUpdateFinished() { + listeners.multicaster.updateFinished() + } + + class Provider(val project: Project, val manager: GitRepositoryManager) : VcsIgnoredFilesHolder.Provider, ChangesViewRefresher { + + private val gitVcs = GitVcs.getInstance(project) + + override fun getVcs() = gitVcs + + override fun createHolder() = GitIgnoredFilesHolder(project, manager) + + override fun refresh(project: Project) { + GitUtil.getRepositoryManager(project).repositories.forEach { r -> r.ignoredFilesHolder.startRescan() } + } + } +} \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/ignore/GitRepositoryIgnoredHolder.kt b/plugins/git4idea/src/git4idea/ignore/GitRepositoryIgnoredHolder.kt new file mode 100644 index 000000000000..893f80132821 --- /dev/null +++ b/plugins/git4idea/src/git4idea/ignore/GitRepositoryIgnoredHolder.kt @@ -0,0 +1,110 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.ignore + +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.Alarm +import com.intellij.util.EventDispatcher +import com.intellij.util.ui.update.MergingUpdateQueue +import com.intellij.util.ui.update.Update +import git4idea.commands.Git +import git4idea.repo.GitRepository +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +class GitRepositoryIgnoredHolder(private val project: Project, private val repository: GitRepository, private val git: Git) : Disposable { + private val updateQueue = MergingUpdateQueue("GitIgnoreUpdate", 500, true, null, this, null, Alarm.ThreadToUse.POOLED_THREAD) + private val inUpdateMode = AtomicBoolean(false) + private val ignoredSet = hashSetOf() + private val SET_LOCK = ReentrantReadWriteLock() + private val listeners = EventDispatcher.create(GitRepositoryIgnoredHolderUpdateListener::class.java) + + @JvmOverloads + fun startRescan(gitIgnorePath: String? = null) { + if (scanTurnedOff()) return + + updateQueue.queue(object : Update("gitRescanIgnored") { + override fun canEat(update: Update) = true + + override fun run() { + if (inUpdateMode.compareAndSet(false, true)) { + fireUpdateStarted(gitIgnorePath) + rescanAllIgnored() + inUpdateMode.set(false) + fireUpdateFinished(gitIgnorePath) + } + } + }) + } + + private fun scanTurnedOff() = !Registry.`is`("git.process.ignored") + + private fun fireUpdateStarted(gitIgnorePath: String? = null) { + when(gitIgnorePath){ + null -> listeners.multicaster.updateStarted() + else -> listeners.multicaster.updateStarted(gitIgnorePath) + } + } + + private fun fireUpdateFinished(gitIgnorePath: String? = null) { + when(gitIgnorePath){ + null -> listeners.multicaster.updateFinished() + else -> listeners.multicaster.updateFinished(gitIgnorePath) + } + } + + fun addUpdateStateListener(listener: GitRepositoryIgnoredHolderUpdateListener) { + listeners.addListener(listener, this) + } + + fun addFiles(files: List) = SET_LOCK.write { ignoredSet.addAll(files) } + + fun addFile(file: VirtualFile) = SET_LOCK.write { ignoredSet.add(file) } + + fun isInUpdateMode() = inUpdateMode.get() + + fun getIgnoredFiles(): Set = SET_LOCK.read { ignoredSet.toHashSet() } + + fun containsFile(file: VirtualFile) = SET_LOCK.read { ignoredSet.any { ignoredFile -> VfsUtil.isAncestor(ignoredFile, file, false) } } + + fun getSize() = SET_LOCK.read { ignoredSet.size } + + override fun dispose() { + updateQueue.cancelAllUpdates() + SET_LOCK.write { + ignoredSet.clear() + } + } + + private fun rescanAllIgnored() { + val ignored: Collection = HashSet(git.ignoredFiles(project, repository.root)) + SET_LOCK.write { + ignoredSet.clear() + ignoredSet.addAll(ignored) + } + } +} + +interface GitRepositoryIgnoredHolderUpdateListener : EventListener { + fun updateStarted() + fun updateStarted(gitIgnorePath: String) + + fun updateFinished(gitIgnorePath: String) + fun updateFinished() +} + +abstract class GitRepositoryIgnoredHolderUpdateAdapter : GitRepositoryIgnoredHolderUpdateListener{ + override fun updateStarted() {} + + override fun updateStarted(gitIgnorePath: String) {} + + override fun updateFinished(gitIgnorePath: String) {} + + override fun updateFinished() {} +} \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/repo/GitRepository.java b/plugins/git4idea/src/git4idea/repo/GitRepository.java index f4533b631930..d46653310214 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepository.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepository.java @@ -21,6 +21,7 @@ import com.intellij.util.messages.Topic; import git4idea.GitLocalBranch; import git4idea.GitVcs; import git4idea.branch.GitBranchesCollection; +import git4idea.ignore.GitRepositoryIgnoredHolder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -123,4 +124,7 @@ public interface GitRepository extends Repository { */ @NotNull Collection getSubmodules(); + + @NotNull + GitRepositoryIgnoredHolder getIgnoredFilesHolder(); } diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java index 688c59a24f30..9874bcb8444a 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java @@ -6,6 +6,10 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.progress.util.BackgroundTaskUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.changes.ChangesViewI; +import com.intellij.openapi.vcs.changes.ChangesViewManager; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcs.log.util.StopWatch; @@ -13,6 +17,9 @@ import git4idea.GitLocalBranch; import git4idea.GitUtil; import git4idea.GitVcs; import git4idea.branch.GitBranchesCollection; +import git4idea.commands.Git; +import git4idea.ignore.GitRepositoryIgnoredHolder; +import git4idea.ignore.GitRepositoryIgnoredHolderUpdateAdapter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,6 +40,7 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { @NotNull private final GitRepositoryFiles myRepositoryFiles; @Nullable private final GitUntrackedFilesHolder myUntrackedFilesHolder; + @Nullable private final GitRepositoryIgnoredHolder myIgnoredRepositoryFilesHolder; @NotNull private volatile GitRepoInfo myInfo; @@ -47,12 +55,17 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { myRepositoryFiles = GitRepositoryFiles.getInstance(gitDir); myReader = new GitRepositoryReader(myRepositoryFiles); myInfo = readRepoInfo(); + if (!light) { myUntrackedFilesHolder = new GitUntrackedFilesHolder(this, myRepositoryFiles); Disposer.register(this, myUntrackedFilesHolder); + myIgnoredRepositoryFilesHolder = new GitRepositoryIgnoredHolder(project, this, Git.getInstance()); + Disposer.register(this, myIgnoredRepositoryFilesHolder); + myIgnoredRepositoryFilesHolder.addUpdateStateListener(new MyRepositoryIgnoredHolderUpdateAdapter(getProject())); } else { myUntrackedFilesHolder = null; + myIgnoredRepositoryFilesHolder = null; } } @@ -80,6 +93,9 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { private void setupUpdater() { GitRepositoryUpdater updater = new GitRepositoryUpdater(this, myRepositoryFiles); Disposer.register(this, updater); + if (myIgnoredRepositoryFilesHolder != null) { + myIgnoredRepositoryFilesHolder.startRescan(); + } } @Deprecated @@ -205,7 +221,8 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { Collection remotes = config.parseRemotes(); GitBranchState state = myReader.readState(remotes); boolean isShallow = myReader.hasShallowCommits(); - Collection trackInfos = config.parseTrackInfos(state.getLocalBranches().keySet(), state.getRemoteBranches().keySet()); + Collection trackInfos = + config.parseTrackInfos(state.getLocalBranches().keySet(), state.getRemoteBranches().keySet()); GitHooksInfo hooksInfo = myReader.readHooksInfo(); Collection submodules = new GitModulesFileReader().read(getSubmoduleFile()); sw.report(); @@ -219,7 +236,9 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { return new File(VfsUtilCore.virtualToIoFile(getRoot()), ".gitmodules"); } - private static void notifyIfRepoChanged(@NotNull final GitRepository repository, @NotNull GitRepoInfo previousInfo, @NotNull GitRepoInfo info) { + private static void notifyIfRepoChanged(@NotNull final GitRepository repository, + @NotNull GitRepoInfo previousInfo, + @NotNull GitRepoInfo info) { if (!repository.getProject().isDisposed() && !info.equals(previousInfo)) { notifyListenersAsync(repository); } @@ -237,4 +256,49 @@ public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { public String toLogString() { return "GitRepository " + getRoot() + " : " + myInfo; } + + @NotNull + @Override + public GitRepositoryIgnoredHolder getIgnoredFilesHolder() { + if (myIgnoredRepositoryFilesHolder == null) throw new UnsupportedOperationException("Unsupported for light Git repository"); + return myIgnoredRepositoryFilesHolder; + } + + private static class MyRepositoryIgnoredHolderUpdateAdapter extends GitRepositoryIgnoredHolderUpdateAdapter { + @NotNull private final ChangesViewI myChangesViewI; + @NotNull private final VcsDirtyScopeManager myDirtyScopeManager; + + MyRepositoryIgnoredHolderUpdateAdapter(@NotNull Project project) { + myChangesViewI = ChangesViewManager.getInstance(project); + myDirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + } + + @Override + public void updateStarted(@NotNull String gitIgnorePath) { + myChangesViewI.scheduleRefresh(); + } + + @Override + public void updateStarted() { + myChangesViewI.scheduleRefresh(); + } + + @Override + public void updateFinished(@NotNull String gitIgnorePath) { + markGitignoreContainingFolderAsDirty(gitIgnorePath); + myChangesViewI.scheduleRefresh(); + } + + @Override + public void updateFinished() { + myChangesViewI.scheduleRefresh(); + } + + private void markGitignoreContainingFolderAsDirty(@NotNull String gitIgnorePath) { + VirtualFile gitIgnore = LocalFileSystem.getInstance().findFileByPath(gitIgnorePath); + if (gitIgnore != null && gitIgnore.getParent() != null) { + myDirtyScopeManager.dirDirtyRecursively(gitIgnore.getParent()); + } + } + } } diff --git a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java index 76d06b25d4e3..395b868dc511 100644 --- a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java +++ b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java @@ -291,7 +291,17 @@ public class GitUntrackedFilesHolder implements Disposable, AsyncVfsEventsListen private boolean gitignoreChanged(@NotNull String path) { // TODO watch file stored in core.excludesfile - return path.endsWith(".gitignore") || myRepositoryFiles.isExclude(path); + boolean gitIgnoreChanged = path.endsWith(GitRepositoryFiles.GITIGNORE); + boolean excludeChanged = myRepositoryFiles.isExclude(path); + + if(gitIgnoreChanged || excludeChanged){ + rescanIgnoredFiles(path); + } + return gitIgnoreChanged || excludeChanged; + } + + private void rescanIgnoredFiles(@NotNull String gitIgnorePath) { //TODO move to ignore manager + myRepository.getIgnoredFilesHolder().startRescan(gitIgnorePath); } @Nullable diff --git a/plugins/git4idea/src/git4idea/vfs/GitVFSListener.java b/plugins/git4idea/src/git4idea/vfs/GitVFSListener.java index 1ea33401ebfc..1b102c4ade83 100644 --- a/plugins/git4idea/src/git4idea/vfs/GitVFSListener.java +++ b/plugins/git4idea/src/git4idea/vfs/GitVFSListener.java @@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.ObjectsConvertor; import com.intellij.openapi.vcs.VcsException; @@ -16,6 +17,7 @@ import com.intellij.openapi.vcs.update.RefreshVFsSynchronously; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.ui.AppUIUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcsUtil.VcsFileUtil; import com.intellij.vcsUtil.VcsUtil; @@ -25,6 +27,7 @@ import git4idea.commands.Git; import git4idea.commands.GitCommand; import git4idea.commands.GitLineHandler; import git4idea.i18n.GitBundle; +import git4idea.ignore.GitRepositoryIgnoredHolder; import git4idea.util.GitFileUtils; import git4idea.util.GitVcsConsoleWriter; import org.jetbrains.annotations.NotNull; @@ -109,7 +112,12 @@ public class GitVFSListener extends VcsVFSListener { List files = e.getValue(); pi.setText(root.getPresentableUrl()); try { - retainedFiles.addAll(myGit.untrackedFiles(myProject, root, files)); + Set untrackedForRepo = myGit.untrackedFiles(myProject, root, files); + if (Registry.is("git.process.ignored")) { + List ignoredForRepo = ContainerUtil.filter(files, file -> !untrackedForRepo.contains(file)); + getIgnoreRepoHolder(root).addFiles(ignoredForRepo); + } + retainedFiles.addAll(untrackedForRepo); } catch (VcsException ex) { myVcsConsoleWriter.showMessage(ex.getMessage()); @@ -122,6 +130,11 @@ public class GitVFSListener extends VcsVFSListener { }); } + @NotNull + private GitRepositoryIgnoredHolder getIgnoreRepoHolder(@NotNull VirtualFile repoRoot) { + return ObjectUtils.assertNotNull(GitUtil.getRepositoryManager(myProject).getRepositoryForRootQuick(repoRoot)).getIgnoredFilesHolder(); + } + /** * The version of execute add before overriding * diff --git a/plugins/git4idea/tests/git4idea/test/MockGitRepository.java b/plugins/git4idea/tests/git4idea/test/MockGitRepository.java index 9cd918b2b8bc..65cbfb826d47 100644 --- a/plugins/git4idea/tests/git4idea/test/MockGitRepository.java +++ b/plugins/git4idea/tests/git4idea/test/MockGitRepository.java @@ -20,6 +20,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitLocalBranch; import git4idea.GitVcs; import git4idea.branch.GitBranchesCollection; +import git4idea.ignore.GitRepositoryIgnoredHolder; import git4idea.repo.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -164,6 +165,12 @@ public class MockGitRepository implements GitRepository { throw new UnsupportedOperationException(); } + @NotNull + @Override + public GitRepositoryIgnoredHolder getIgnoredFilesHolder() { + throw new UnsupportedOperationException(); + } + @Override public void dispose() { }