1 // Copyright 2008-2010 Victor Iacoban
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software distributed under
10 // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 // either express or implied. See the License for the specific language governing permissions and
12 // limitations under the License.
13 package org.zmlx.hg4idea.provider;
15 import com.intellij.openapi.components.ServiceManager;
16 import com.intellij.openapi.fileEditor.FileDocumentManager;
17 import com.intellij.openapi.progress.ProgressIndicator;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.vcs.*;
20 import com.intellij.openapi.vcs.changes.*;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.vcsUtil.VcsUtil;
23 import org.zmlx.hg4idea.*;
24 import org.zmlx.hg4idea.command.HgResolveCommand;
25 import org.zmlx.hg4idea.command.HgResolveStatusEnum;
26 import org.zmlx.hg4idea.command.HgStatusCommand;
27 import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand;
28 import org.zmlx.hg4idea.util.HgUtil;
32 import java.util.List;
34 public class HgChangeProvider implements ChangeProvider {
36 private final Project myProject;
37 private final VcsKey myVcsKey;
39 public static final FileStatus COPIED =
40 ServiceManager.getService(FileStatusFactory.class).createFileStatus("COPIED", "Copied", FileStatus.COLOR_ADDED);
41 public static final FileStatus RENAMED = ServiceManager.getService(FileStatusFactory.class).createFileStatus("RENAMED", "Renamed",
42 Color.cyan.darker().darker());
44 private static final EnumMap<HgFileStatusEnum, HgChangeProcessor> PROCESSORS =
45 new EnumMap<HgFileStatusEnum, HgChangeProcessor>(HgFileStatusEnum.class);
48 PROCESSORS.put(HgFileStatusEnum.ADDED, HgChangeProcessor.ADDED);
49 PROCESSORS.put(HgFileStatusEnum.DELETED, HgChangeProcessor.DELETED);
50 PROCESSORS.put(HgFileStatusEnum.IGNORED, HgChangeProcessor.IGNORED);
51 PROCESSORS.put(HgFileStatusEnum.MISSING, HgChangeProcessor.MISSING);
52 PROCESSORS.put(HgFileStatusEnum.COPY, HgChangeProcessor.COPIED);
53 PROCESSORS.put(HgFileStatusEnum.MODIFIED, HgChangeProcessor.MODIFIED);
54 PROCESSORS.put(HgFileStatusEnum.UNMODIFIED, HgChangeProcessor.UNMODIFIED);
55 PROCESSORS.put(HgFileStatusEnum.UNVERSIONED, HgChangeProcessor.UNVERSIONED);
58 public HgChangeProvider(Project project, VcsKey vcsKey) {
63 public boolean isModifiedDocumentTrackingRequired() {
67 public void doCleanup(List<VirtualFile> files) {
70 public void getChanges(VcsDirtyScope dirtyScope, ChangelistBuilder builder,
71 ProgressIndicator progress, ChangeListManagerGate addGate) throws VcsException {
72 final Collection<HgChange> changes = new HashSet<HgChange>();
73 changes.addAll(process(builder, dirtyScope.getRecursivelyDirtyDirectories()));
74 changes.addAll(process(builder, dirtyScope.getDirtyFiles()));
75 processUnsavedChanges(builder, dirtyScope.getDirtyFilesNoExpand(), changes);
78 private Collection<HgChange> process(ChangelistBuilder builder, Collection<FilePath> files) {
79 final Set<HgChange> hgChanges = new HashSet<HgChange>();
80 for (Map.Entry<VirtualFile, Collection<FilePath>> entry : HgUtil.groupFilePathsByHgRoots(myProject, files).entrySet()) {
81 VirtualFile repo = entry.getKey();
83 final HgRevisionNumber workingRevision = new HgWorkingCopyRevisionsCommand(myProject).identify(repo);
84 final HgRevisionNumber parentRevision = new HgWorkingCopyRevisionsCommand(myProject).firstParent(repo);
85 final Map<HgFile, HgResolveStatusEnum> list = new HgResolveCommand(myProject).getListSynchronously(repo);
87 hgChanges.addAll(new HgStatusCommand(myProject).execute(repo, entry.getValue()));
88 sendChanges(builder, hgChanges, list, workingRevision, parentRevision);
93 private void sendChanges(ChangelistBuilder builder, Set<HgChange> changes,
94 Map<HgFile, HgResolveStatusEnum> resolveStatus, HgRevisionNumber workingRevision,
95 HgRevisionNumber parentRevision) {
96 for (HgChange change : changes) {
97 HgFile afterFile = change.afterFile();
98 HgFile beforeFile = change.beforeFile();
99 HgFileStatusEnum status = change.getStatus();
101 if (resolveStatus.containsKey(afterFile)
102 && resolveStatus.get(afterFile) == HgResolveStatusEnum.UNRESOLVED) {
103 builder.processChange(
105 new HgContentRevision(myProject, beforeFile, parentRevision),
106 HgCurrentContentRevision.create(afterFile, workingRevision),
107 FileStatus.MERGED_WITH_CONFLICTS
112 if (isDeleteOfCopiedFile(change, changes)) {
113 // Don't register the 'delete' change for renamed or moved files; IDEA already handles these
118 HgChangeProcessor processor = PROCESSORS.get(status);
119 if (processor != null) {
120 processor.process(myProject, myVcsKey, builder,
121 workingRevision, parentRevision, beforeFile, afterFile);
126 private static boolean isDeleteOfCopiedFile(HgChange change, Set<HgChange> changes) {
127 if (change.getStatus().equals(HgFileStatusEnum.DELETED)) {
128 for (HgChange otherChange : changes) {
129 if (otherChange.getStatus().equals(HgFileStatusEnum.COPY) &&
130 otherChange.beforeFile().equals(change.afterFile())) {
140 * Finds modified but unsaved files in the given list of dirty files and notifies the builder about MODIFIED changes.
141 * Changes contained in <code>alreadyProcessed</code> are skipped - they have already been processed as modified, or else.
143 public void processUnsavedChanges(ChangelistBuilder builder, Set<FilePath> dirtyFiles, Collection<HgChange> alreadyProcessed) {
144 // exclude already processed
145 for (HgChange c : alreadyProcessed) {
146 dirtyFiles.remove(c.beforeFile().toFilePath());
147 dirtyFiles.remove(c.afterFile().toFilePath());
150 final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
151 final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
152 for (FilePath filePath : dirtyFiles) {
153 final VirtualFile vf = filePath.getVirtualFile();
154 if (vf != null && fileDocumentManager.isFileModified(vf)) {
155 final VirtualFile root = vcsManager.getVcsRootFor(vf);
157 final HgRevisionNumber beforeRevisionNumber = new HgWorkingCopyRevisionsCommand(myProject).tip(root);
158 final ContentRevision beforeRevision = (beforeRevisionNumber == null ? null :
159 new HgContentRevision(myProject, new HgFile(myProject, vf), beforeRevisionNumber));
160 builder.processChange(new Change(beforeRevision, CurrentContentRevision.create(filePath), FileStatus.MODIFIED), myVcsKey);
167 private enum HgChangeProcessor {
170 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
171 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
172 HgFile beforeFile, HgFile afterFile) {
175 HgCurrentContentRevision.create(afterFile, currentNumber),
185 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
186 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
187 HgFile beforeFile, HgFile afterFile) {
189 new HgContentRevision(project, beforeFile, parentRevision),
200 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
201 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
202 HgFile beforeFile, HgFile afterFile) {
203 builder.processIgnoredFile(VcsUtil.getVirtualFile(afterFile.getFile()));
209 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
210 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
211 HgFile beforeFile, HgFile afterFile) {
212 builder.processLocallyDeletedFile(new LocallyDeletedChange(beforeFile.toFilePath()));
218 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
219 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
220 HgFile beforeFile, HgFile afterFile) {
221 if (beforeFile.getFile().exists()) {
222 // The original file exists so this is a duplication of the file.
223 // Don't create the before ContentRevision or IDEA will think
224 // this was a rename.
227 HgCurrentContentRevision.create(afterFile, currentNumber),
228 HgChangeProvider.COPIED,
233 // The original file does not exists so this is a rename.
235 new HgContentRevision(project, beforeFile, parentRevision),
236 HgCurrentContentRevision.create(afterFile, currentNumber),
237 HgChangeProvider.RENAMED,
247 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
248 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
249 HgFile beforeFile, HgFile afterFile) {
251 new HgContentRevision(project, beforeFile, parentRevision),
252 HgCurrentContentRevision.create(afterFile, currentNumber),
262 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
263 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
264 HgFile beforeFile, HgFile afterFile) {
271 void process(Project project, VcsKey vcsKey, ChangelistBuilder builder,
272 HgRevisionNumber currentNumber, HgRevisionNumber parentRevision,
273 HgFile beforeFile, HgFile afterFile) {
274 builder.processUnversionedFile(VcsUtil.getVirtualFile(afterFile.getFile()));
278 abstract void process(
281 ChangelistBuilder builder,
282 HgRevisionNumber currentNumber,
283 HgRevisionNumber parentRevision,
288 final void processChange(ContentRevision contentRevisionBefore,
289 ContentRevision contentRevisionAfter, FileStatus fileStatus,
290 ChangelistBuilder builder, VcsKey vcsKey) {
291 if (contentRevisionBefore == null && contentRevisionAfter == null) {
294 builder.processChange(
295 new Change(contentRevisionBefore, contentRevisionAfter, fileStatus),