blob: 869c5b27580ce64edc65a5f2f71a54af7c1943ff [file] [log] [blame]
/*
* 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.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsVFSListener;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.ui.AppUIUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcsUtil.VcsFileUtil;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.Git;
import git4idea.i18n.GitBundle;
import git4idea.util.GitFileUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public class GitVFSListener extends VcsVFSListener {
/**
* More than zero if events are suppressed
*/
private final AtomicInteger myEventsSuppressLevel = new AtomicInteger(0);
private final Git myGit;
public GitVFSListener(final Project project, final GitVcs vcs, Git git) {
super(project, vcs);
myGit = git;
}
/**
* Set events suppressed, the events should be unsuppressed later
*
* @param value true if events should be suppressed, false otherwise
*/
public void setEventsSuppressed(boolean value) {
if (value) {
myEventsSuppressLevel.incrementAndGet();
}
else {
int v = myEventsSuppressLevel.decrementAndGet();
assert v >= 0;
}
}
@Override
protected boolean isEventIgnored(VirtualFileEvent event, boolean putInDirty) {
return super.isEventIgnored(event, putInDirty) || myEventsSuppressLevel.get() != 0;
}
protected String getAddTitle() {
return GitBundle.getString("vfs.listener.add.title");
}
protected String getSingleFileAddTitle() {
return GitBundle.getString("vfs.listener.add.single.title");
}
protected String getSingleFileAddPromptTemplate() {
return GitBundle.getString("vfs.listener.add.single.prompt");
}
@Override
protected void executeAdd(final List<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copiedFiles) {
// Filter added files before further processing
final Map<VirtualFile, List<VirtualFile>> sortedFiles;
try {
sortedFiles = GitUtil.sortFilesByGitRoot(addedFiles, true);
}
catch (VcsException e) {
throw new RuntimeException("The exception is not expected here", e);
}
final HashSet<VirtualFile> retainedFiles = new HashSet<VirtualFile>();
final ProgressManager progressManager = ProgressManager.getInstance();
progressManager.run(new Task.Backgroundable(myProject, GitBundle.getString("vfs.listener.checking.ignored"), false) {
@Override
public void run(@NotNull ProgressIndicator pi) {
for (Map.Entry<VirtualFile, List<VirtualFile>> e : sortedFiles.entrySet()) {
VirtualFile root = e.getKey();
final List<VirtualFile> files = e.getValue();
pi.setText(root.getPresentableUrl());
try {
retainedFiles.addAll(myGit.untrackedFiles(myProject, root, files));
}
catch (final VcsException ex) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
gitVcs().showMessages(ex.getMessage());
}
});
}
}
addedFiles.retainAll(retainedFiles);
AppUIUtil.invokeLaterIfProjectAlive(myProject, new Runnable() {
@Override
public void run() {
originalExecuteAdd(addedFiles, copiedFiles);
}
});
}
});
}
/**
* The version of execute add before overriding
*
* @param addedFiles the added files
* @param copiedFiles the copied files
*/
private void originalExecuteAdd(List<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copiedFiles) {
super.executeAdd(addedFiles, copiedFiles);
}
protected void performAdding(final Collection<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copyFromMap) {
// copied files (copyFromMap) are ignored, because they are included into added files.
performAdding(ObjectsConvertor.vf2fp(new ArrayList<VirtualFile>(addedFiles)));
}
private GitVcs gitVcs() {
return ((GitVcs)myVcs);
}
private void performAdding(Collection<FilePath> filesToAdd) {
performBackgroundOperation(filesToAdd, GitBundle.getString("add.adding"), new LongOperationPerRootExecutor() {
@Override
public void execute(@NotNull VirtualFile root, @NotNull List<FilePath> files) throws VcsException {
GitFileUtils.addPaths(myProject, root, files);
VcsFileUtil.markFilesDirty(myProject, files);
}
@Override
public Collection<File> getFilesToRefresh() {
return Collections.emptyList();
}
});
}
protected String getDeleteTitle() {
return GitBundle.getString("vfs.listener.delete.title");
}
protected String getSingleFileDeleteTitle() {
return GitBundle.getString("vfs.listener.delete.single.title");
}
protected String getSingleFileDeletePromptTemplate() {
return GitBundle.getString("vfs.listener.delete.single.prompt");
}
protected void performDeletion(final List<FilePath> filesToDelete) {
performBackgroundOperation(filesToDelete, GitBundle.getString("remove.removing"), new LongOperationPerRootExecutor() {
HashSet<File> filesToRefresh = new HashSet<File>();
public void execute(@NotNull VirtualFile root, @NotNull List<FilePath> files) throws VcsException {
final File rootFile = new File(root.getPath());
GitFileUtils.delete(myProject, root, files, "--ignore-unmatch");
if (myProject != null && !myProject.isDisposed()) {
VcsFileUtil.markFilesDirty(myProject, files);
}
for (FilePath p : files) {
for (File f = p.getIOFile(); f != null && !f.equals(rootFile); f = f.getParentFile()) {
filesToRefresh.add(f);
}
}
}
public Collection<File> getFilesToRefresh() {
return filesToRefresh;
}
});
}
protected void performMoveRename(final List<MovedFileInfo> movedFiles) {
// because git does not tracks moves, the file are just added and deleted.
ArrayList<FilePath> added = new ArrayList<FilePath>();
ArrayList<FilePath> removed = new ArrayList<FilePath>();
for (MovedFileInfo info : movedFiles) {
if (!GitFileUtils.shouldIgnoreCaseChange(info.myNewPath, info.myOldPath)) {
added.add(VcsUtil.getFilePath(info.myNewPath));
removed.add(VcsUtil.getFilePath(info.myOldPath));
}
}
performAdding(added);
performDeletion(removed);
}
protected boolean isDirectoryVersioningSupported() {
return false;
}
@Override
protected Collection<FilePath> selectFilePathsToDelete(final List<FilePath> deletedFiles) {
// For git asking about vcs delete does not make much sense. The result is practically identical.
return deletedFiles;
}
private void performBackgroundOperation(@NotNull Collection<FilePath> files, @NotNull String operationTitle,
@NotNull final LongOperationPerRootExecutor executor) {
final Map<VirtualFile, List<FilePath>> sortedFiles;
try {
sortedFiles = GitUtil.sortFilePathsByGitRoot(files, true);
}
catch (VcsException e) {
gitVcs().showMessages(e.getMessage());
return;
}
GitVcs.runInBackground(new Task.Backgroundable(myProject, operationTitle) {
public void run(@NotNull ProgressIndicator indicator) {
for (Map.Entry<VirtualFile, List<FilePath>> e : sortedFiles.entrySet()) {
try {
executor.execute(e.getKey(), e.getValue());
}
catch (final VcsException ex) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
gitVcs().showMessages(ex.getMessage());
}
});
}
}
LocalFileSystem.getInstance().refreshIoFiles(executor.getFilesToRefresh());
}
});
}
private interface LongOperationPerRootExecutor {
void execute(@NotNull VirtualFile root, @NotNull List<FilePath> files) throws VcsException;
Collection<File> getFilesToRefresh();
}
}