| /* |
| * 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.status; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.changes.*; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.PairProcessor; |
| import com.intellij.util.containers.Convertor; |
| import git4idea.GitContentRevision; |
| import git4idea.GitRevisionNumber; |
| import git4idea.GitUtil; |
| import git4idea.GitVcs; |
| import git4idea.changes.GitChangeUtils; |
| import git4idea.commands.Git; |
| import git4idea.config.GitVersion; |
| import git4idea.config.GitVersionSpecialty; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.*; |
| |
| /** |
| * Git repository change provider |
| */ |
| public class GitChangeProvider implements ChangeProvider { |
| |
| private static final Logger PROFILE_LOG = Logger.getInstance("#GitStatus"); |
| |
| @NotNull private final Project myProject; |
| @NotNull private final Git myGit; |
| @NotNull private final ChangeListManager myChangeListManager; |
| @NotNull private final FileDocumentManager myFileDocumentManager; |
| @NotNull private final ProjectLevelVcsManager myVcsManager; |
| |
| public GitChangeProvider(@NotNull Project project, @NotNull Git git, ChangeListManager changeListManager, |
| @NotNull FileDocumentManager fileDocumentManager, @NotNull ProjectLevelVcsManager vcsManager) { |
| myProject = project; |
| myGit = git; |
| myChangeListManager = changeListManager; |
| myFileDocumentManager = fileDocumentManager; |
| myVcsManager = vcsManager; |
| } |
| |
| @Override |
| public void getChanges(final VcsDirtyScope dirtyScope, |
| final ChangelistBuilder builder, |
| final ProgressIndicator progress, |
| final ChangeListManagerGate addGate) throws VcsException { |
| final GitVcs vcs = GitVcs.getInstance(myProject); |
| if (vcs == null) { |
| // already disposed or not yet initialized => ignoring |
| return; |
| } |
| |
| appendNestedVcsRootsToDirt(dirtyScope, vcs, myVcsManager); |
| |
| final Collection<VirtualFile> affected = dirtyScope.getAffectedContentRoots(); |
| Collection<VirtualFile> roots = GitUtil.gitRootsForPaths(affected); |
| |
| try { |
| final MyNonChangedHolder holder = new MyNonChangedHolder(myProject, dirtyScope.getDirtyFilesNoExpand(), addGate, |
| myFileDocumentManager, myVcsManager); |
| for (VirtualFile root : roots) { |
| debug("checking root: " + root.getPath()); |
| GitChangesCollector collector = isNewGitChangeProviderAvailable() |
| ? GitNewChangesCollector.collect(myProject, myGit, myChangeListManager, myVcsManager, |
| vcs, dirtyScope, root) |
| : GitOldChangesCollector.collect(myProject, myChangeListManager, myVcsManager, |
| vcs, dirtyScope, root); |
| final Collection<Change> changes = collector.getChanges(); |
| holder.changed(changes); |
| for (Change file : changes) { |
| debug("process change: " + ChangesUtil.getFilePath(file).getPath()); |
| builder.processChange(file, GitVcs.getKey()); |
| } |
| for (VirtualFile f : collector.getUnversionedFiles()) { |
| builder.processUnversionedFile(f); |
| holder.unversioned(f); |
| } |
| holder.feedBuilder(builder); |
| } |
| } |
| catch (VcsException e) { |
| PROFILE_LOG.info(e); |
| // most probably the error happened because git is not configured |
| vcs.getExecutableValidator().showNotificationOrThrow(e); |
| } |
| } |
| |
| public static void appendNestedVcsRootsToDirt(final VcsDirtyScope dirtyScope, GitVcs vcs, final ProjectLevelVcsManager vcsManager) { |
| final Set<FilePath> recursivelyDirtyDirectories = dirtyScope.getRecursivelyDirtyDirectories(); |
| if (recursivelyDirtyDirectories.isEmpty()) { |
| return; |
| } |
| |
| final LocalFileSystem lfs = LocalFileSystem.getInstance(); |
| final Set<VirtualFile> rootsUnderGit = new HashSet<VirtualFile>(Arrays.asList(vcsManager.getRootsUnderVcs(vcs))); |
| final Set<VirtualFile> inputColl = new HashSet<VirtualFile>(rootsUnderGit); |
| final Set<VirtualFile> existingInScope = new HashSet<VirtualFile>(); |
| for (FilePath dir : recursivelyDirtyDirectories) { |
| VirtualFile vf = dir.getVirtualFile(); |
| if (vf == null) { |
| vf = lfs.findFileByIoFile(dir.getIOFile()); |
| } |
| if (vf == null) { |
| vf = lfs.refreshAndFindFileByIoFile(dir.getIOFile()); |
| } |
| if (vf != null) { |
| existingInScope.add(vf); |
| } |
| } |
| inputColl.addAll(existingInScope); |
| FileUtil.removeAncestors(inputColl, new Convertor<VirtualFile, String>() { |
| @Override |
| public String convert(VirtualFile o) { |
| return o.getPath(); |
| } |
| }, new PairProcessor<VirtualFile, VirtualFile>() { |
| @Override |
| public boolean process(VirtualFile parent, VirtualFile child) { |
| if (! existingInScope.contains(child) && existingInScope.contains(parent)) { |
| debug("adding git root for check: " + child.getPath()); |
| ((VcsModifiableDirtyScope)dirtyScope).addDirtyDirRecursively(new FilePathImpl(child)); |
| } |
| return true; |
| } |
| } |
| ); |
| } |
| |
| private boolean isNewGitChangeProviderAvailable() { |
| GitVcs vcs = GitVcs.getInstance(myProject); |
| if (vcs == null) { |
| return false; |
| } |
| final GitVersion version = vcs.getVersion(); |
| return GitVersionSpecialty.KNOWS_STATUS_PORCELAIN.existsIn(version); |
| } |
| |
| /** |
| * Common debug logging method for all Git status related operations. |
| * Primarily used for measuring performance and tracking calls to heavy methods. |
| */ |
| public static void debug(String message) { |
| PROFILE_LOG.debug(message); |
| } |
| |
| private static class MyNonChangedHolder { |
| private final Project myProject; |
| private final Set<FilePath> myDirty; |
| private final ChangeListManagerGate myAddGate; |
| private FileDocumentManager myFileDocumentManager; |
| private ProjectLevelVcsManager myVcsManager; |
| |
| private MyNonChangedHolder(final Project project, |
| final Set<FilePath> dirty, |
| final ChangeListManagerGate addGate, |
| FileDocumentManager fileDocumentManager, ProjectLevelVcsManager vcsManager) { |
| myProject = project; |
| myDirty = dirty; |
| myAddGate = addGate; |
| myFileDocumentManager = fileDocumentManager; |
| myVcsManager = vcsManager; |
| } |
| |
| public void changed(final Collection<Change> changes) { |
| for (Change change : changes) { |
| final FilePath beforePath = ChangesUtil.getBeforePath(change); |
| if (beforePath != null) { |
| myDirty.remove(beforePath); |
| } |
| final FilePath afterPath = ChangesUtil.getBeforePath(change); |
| if (afterPath != null) { |
| myDirty.remove(afterPath); |
| } |
| } |
| } |
| |
| public void unversioned(final VirtualFile vf) { |
| // NB: There was an exception that happened several times: vf == null. |
| // Populating myUnversioned in the ChangeCollector makes nulls not possible in myUnversioned, |
| // so proposing that the exception was fixed. |
| // More detailed analysis will be needed in case the exception appears again. 2010-12-09. |
| myDirty.remove(new FilePathImpl(vf)); |
| } |
| |
| public void feedBuilder(final ChangelistBuilder builder) throws VcsException { |
| final VcsKey gitKey = GitVcs.getKey(); |
| |
| for (FilePath filePath : myDirty) { |
| final VirtualFile vf = filePath.getVirtualFile(); |
| if (vf != null) { |
| if ((myAddGate.getStatus(vf) == null) && myFileDocumentManager.isFileModified(vf)) { |
| final VirtualFile root = myVcsManager.getVcsRootFor(vf); |
| if (root != null) { |
| final GitRevisionNumber beforeRevisionNumber = GitChangeUtils.resolveReference(myProject, root, "HEAD"); |
| builder.processChange(new Change(GitContentRevision.createRevision(vf, beforeRevisionNumber, myProject), |
| GitContentRevision.createRevision(vf, null, myProject), FileStatus.MODIFIED), gitKey); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean isModifiedDocumentTrackingRequired() { |
| return true; |
| } |
| |
| public void doCleanup(final List<VirtualFile> files) { |
| } |
| } |