| /* |
| * 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 org.zmlx.hg4idea; |
| |
| import com.intellij.execution.ui.ConsoleViewContentType; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.changes.Change; |
| import com.intellij.openapi.vcs.changes.ChangeListManager; |
| import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; |
| import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.ui.AppUIUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.VcsBackgroundTask; |
| import com.intellij.vcsUtil.VcsUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.zmlx.hg4idea.command.*; |
| import org.zmlx.hg4idea.util.HgUtil; |
| |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Listens to VFS events (such as adding or deleting bunch of files) and performs necessary operations with the VCS. |
| * @author Kirill Likhodedov |
| */ |
| public class HgVFSListener extends VcsVFSListener { |
| |
| private final VcsDirtyScopeManager dirtyScopeManager; |
| private static final Logger LOG = Logger.getInstance(HgVFSListener.class); |
| |
| protected HgVFSListener(final Project project, final HgVcs vcs) { |
| super(project, vcs); |
| dirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject); |
| } |
| |
| @Override |
| protected String getAddTitle() { |
| return HgVcsMessages.message("hg4idea.add.title"); |
| } |
| |
| @Override |
| protected String getSingleFileAddTitle() { |
| return HgVcsMessages.message("hg4idea.add.single.title"); |
| } |
| |
| @Override |
| protected String getSingleFileAddPromptTemplate() { |
| return HgVcsMessages.message("hg4idea.add.body"); |
| } |
| |
| @Override |
| protected void executeAdd(final List<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copyFromMap) { |
| // if a file is copied from another repository, then 'hg add' should be used instead of 'hg copy'. |
| // Thus here we remove such files from the copyFromMap. |
| for (Iterator<Map.Entry<VirtualFile, VirtualFile>> it = copyFromMap.entrySet().iterator(); it.hasNext(); ) { |
| final Map.Entry<VirtualFile, VirtualFile> entry = it.next(); |
| final VirtualFile rootFrom = HgUtil.getHgRootOrNull(myProject, entry.getKey()); |
| final VirtualFile rootTo = HgUtil.getHgRootOrNull(myProject, entry.getValue()); |
| |
| if (rootTo == null || !rootTo.equals(rootFrom)) { |
| it.remove(); |
| } |
| } |
| |
| // exclude files which are added to a directory which is not version controlled |
| for (Iterator<VirtualFile> it = addedFiles.iterator(); it.hasNext(); ) { |
| if (HgUtil.getHgRootOrNull(myProject, it.next()) == null) { |
| it.remove(); |
| } |
| } |
| // exclude files which are ignored in .hgignore in background and execute adding after that |
| final Map<VirtualFile, Collection<VirtualFile>> sortedFiles = HgUtil.sortByHgRoots(myProject, addedFiles); |
| final HashSet<VirtualFile> untrackedFiles = new HashSet<VirtualFile>(); |
| new Task.Backgroundable(myProject, HgVcsMessages.message("hg4idea.progress.checking.ignored"), false) { |
| @Override |
| public void run(@NotNull ProgressIndicator pi) { |
| for (Map.Entry<VirtualFile, Collection<VirtualFile>> e : sortedFiles.entrySet()) { |
| VirtualFile repo = e.getKey(); |
| final Collection<VirtualFile> files = e.getValue(); |
| pi.setText(repo.getPresentableUrl()); |
| try { |
| untrackedFiles |
| .addAll(new HgStatusCommand.Builder(false).unknown(true).build(myProject) |
| .getHgUntrackedFiles(repo, new ArrayList<VirtualFile>(files))); |
| } |
| catch (final VcsException ex) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| public void run() { |
| ((HgVcs)myVcs).showMessageInConsole(ex.getMessage(), ConsoleViewContentType.ERROR_OUTPUT.getAttributes()); |
| } |
| }); |
| } |
| } |
| addedFiles.retainAll(untrackedFiles); |
| // select files to add if there is something to select |
| if (!addedFiles.isEmpty() || !copyFromMap.isEmpty()) { |
| |
| AppUIUtil.invokeLaterIfProjectAlive(myProject, new Runnable() { |
| @Override |
| public void run() { |
| originalExecuteAdd(addedFiles, copyFromMap); |
| } |
| }); |
| } |
| } |
| }.queue(); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| @Override |
| protected void performAdding(final Collection<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copyFromMap) { |
| (new Task.ConditionalModal(myProject, |
| HgVcsMessages.message("hg4idea.add.progress"), |
| false, |
| VcsConfiguration.getInstance(myProject).getAddRemoveOption() ) { |
| @Override public void run(@NotNull ProgressIndicator aProgressIndicator) { |
| final ArrayList<VirtualFile> adds = new ArrayList<VirtualFile>(); |
| final HashMap<VirtualFile, VirtualFile> copies = new HashMap<VirtualFile, VirtualFile>(); // from -> to |
| //delete unversioned and ignored files from copy source |
| LOG.assertTrue(myProject != null, "Project is null"); |
| Collection<VirtualFile> unversionedAndIgnoredFiles = new ArrayList<VirtualFile>(); |
| final Map<VirtualFile, Collection<VirtualFile>> sortedSourceFilesByRepos = HgUtil.sortByHgRoots(myProject, copyFromMap.values()); |
| HgStatusCommand statusCommand = new HgStatusCommand.Builder(false).unknown(true).ignored(true).build(myProject); |
| for (Map.Entry<VirtualFile, Collection<VirtualFile>> entry : sortedSourceFilesByRepos.entrySet()) { |
| Set<HgChange> changes = |
| statusCommand.execute(entry.getKey(), ContainerUtil.map(entry.getValue(), new Function<VirtualFile, FilePath>() { |
| @Override |
| public FilePath fun(VirtualFile virtualFile) { |
| return new FilePathImpl(virtualFile); |
| } |
| })); |
| for (HgChange change : changes) { |
| unversionedAndIgnoredFiles.add(change.afterFile().toFilePath().getVirtualFile()); |
| } |
| } |
| copyFromMap.values().removeAll(unversionedAndIgnoredFiles); |
| |
| // separate adds from copies |
| for (VirtualFile file : addedFiles) { |
| if (file.isDirectory()) { |
| continue; |
| } |
| |
| final VirtualFile copyFrom = copyFromMap.get(file); |
| if (copyFrom != null) { |
| copies.put(copyFrom, file); |
| } else { |
| adds.add(file); |
| } |
| } |
| |
| // add for all files at once |
| if (!adds.isEmpty()) { |
| new HgAddCommand(myProject).execute(adds); |
| } |
| |
| // copy needs to be run for each file separately |
| if (!copies.isEmpty()) { |
| for(Map.Entry<VirtualFile, VirtualFile> copy : copies.entrySet()) { |
| new HgCopyCommand(myProject).execute(copy.getKey(), copy.getValue()); |
| } |
| } |
| |
| for (VirtualFile file : addedFiles) { |
| dirtyScopeManager.fileDirty(file); |
| } |
| } |
| }).queue(); |
| } |
| |
| @Override |
| protected String getDeleteTitle() { |
| return HgVcsMessages.message("hg4idea.remove.multiple.title"); |
| } |
| |
| @Override |
| protected String getSingleFileDeleteTitle() { |
| return HgVcsMessages.message("hg4idea.remove.single.title"); |
| } |
| |
| @Override |
| protected String getSingleFileDeletePromptTemplate() { |
| return HgVcsMessages.message("hg4idea.remove.single.body"); |
| } |
| |
| @Override |
| protected VcsDeleteType needConfirmDeletion(final VirtualFile file) { |
| return ChangeListManagerImpl.getInstanceImpl(myProject).getUnversionedFiles().contains(file) |
| ? VcsDeleteType.IGNORE |
| : VcsDeleteType.CONFIRM; |
| } |
| |
| protected void executeDelete() { |
| final List<FilePath> filesToDelete = new ArrayList<FilePath>(myDeletedWithoutConfirmFiles); |
| final List<FilePath> filesToConfirmDeletion = new ArrayList<FilePath>(myDeletedFiles); |
| myDeletedWithoutConfirmFiles.clear(); |
| myDeletedFiles.clear(); |
| |
| // skip files which are not under Mercurial |
| skipNotUnderHg(filesToDelete); |
| skipNotUnderHg(filesToConfirmDeletion); |
| |
| // newly added files (which were added to the repo but never committed) should be removed from the VCS, |
| // but without user confirmation. |
| for (Iterator<FilePath> it = filesToConfirmDeletion.iterator(); it.hasNext(); ) { |
| FilePath filePath = it.next(); |
| Change fileChange = ChangeListManager.getInstance(myProject).getChange(filePath); |
| if (fileChange != null && fileChange.getFileStatus().equals(FileStatus.ADDED)) { |
| filesToDelete.add(filePath); |
| it.remove(); |
| } |
| } |
| |
| new Task.ConditionalModal(myProject, |
| HgVcsMessages.message("hg4idea.remove.progress"), |
| false, |
| VcsConfiguration.getInstance(myProject).getAddRemoveOption()) { |
| @Override public void run( @NotNull ProgressIndicator indicator ) { |
| // confirm removal from the VCS if needed |
| if (myRemoveOption.getValue() != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { |
| if (myRemoveOption.getValue() == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY || filesToConfirmDeletion.isEmpty()) { |
| filesToDelete.addAll(filesToConfirmDeletion); |
| } |
| else { |
| final AtomicReference<Collection<FilePath>> filePaths = new AtomicReference<Collection<FilePath>>(); |
| ApplicationManager.getApplication().invokeAndWait(new Runnable() { |
| @Override public void run() { |
| filePaths.set(selectFilePathsToDelete(filesToConfirmDeletion)); |
| } |
| }, indicator.getModalityState()); |
| if (filePaths.get() != null) { |
| filesToDelete.addAll(filePaths.get()); |
| } |
| } |
| } |
| |
| if (!filesToDelete.isEmpty()) { |
| performDeletion(filesToDelete); |
| } |
| } |
| }.queue(); |
| } |
| |
| /** |
| * Changes the given collection of files by filtering out unversioned files and |
| * files which are not under Mercurial repository. |
| * |
| * @param filesToFilter files to be filtered. |
| */ |
| private void skipNotUnderHg(Collection<FilePath> filesToFilter) { |
| for (Iterator<FilePath> iter = filesToFilter.iterator(); iter.hasNext(); ) { |
| final FilePath filePath = iter.next(); |
| if (HgUtil.getHgRootOrNull(myProject, filePath) == null) { |
| iter.remove(); |
| } |
| } |
| } |
| |
| @Override |
| protected void performDeletion( final List<FilePath> filesToDelete) { |
| final ArrayList<HgFile> deletes = new ArrayList<HgFile>(); |
| for (FilePath file : filesToDelete) { |
| if (file.isDirectory()) { |
| continue; |
| } |
| |
| VirtualFile root = VcsUtil.getVcsRootFor(myProject, file); |
| if (root != null) { |
| deletes.add(new HgFile(root, file)); |
| } |
| } |
| |
| if (!deletes.isEmpty()) { |
| new HgRemoveCommand(myProject).execute(deletes); |
| } |
| |
| for (HgFile file : deletes) { |
| dirtyScopeManager.fileDirty(file.toFilePath()); |
| } |
| } |
| |
| @Override |
| protected void performMoveRename(List<MovedFileInfo> movedFiles) { |
| (new VcsBackgroundTask<MovedFileInfo>(myProject, |
| HgVcsMessages.message("hg4idea.move.progress"), |
| VcsConfiguration.getInstance(myProject).getAddRemoveOption(), |
| movedFiles) { |
| protected void process(final MovedFileInfo file) throws VcsException { |
| final FilePath source = VcsUtil.getFilePath(file.myOldPath); |
| final FilePath target = VcsUtil.getFilePath(file.myNewPath); |
| VirtualFile sourceRoot = VcsUtil.getVcsRootFor(myProject, source); |
| VirtualFile targetRoot = VcsUtil.getVcsRootFor(myProject, target); |
| if (sourceRoot != null && targetRoot != null) { |
| (new HgMoveCommand(myProject)).execute(new HgFile(sourceRoot, source), new HgFile(targetRoot, target)); |
| } |
| dirtyScopeManager.fileDirty(source); |
| dirtyScopeManager.fileDirty(target); |
| } |
| |
| }).queue(); |
| } |
| |
| @Override |
| protected boolean isDirectoryVersioningSupported() { |
| return false; |
| } |
| } |