VCS: VcsKey to identify Vcs without asking for project-level component; changelist...
[fedora-idea.git] / plugins / cvs2 / source / com / intellij / cvsSupport2 / cvsstatuses / CvsChangeProvider.java
blob5386fdfdccac3ab6be1d7a4efa1da988d466718d
1 package com.intellij.cvsSupport2.cvsstatuses;
3 import com.intellij.CvsBundle;
4 import com.intellij.cvsSupport2.CvsUtil;
5 import com.intellij.cvsSupport2.CvsVcs2;
6 import com.intellij.cvsSupport2.application.CvsEntriesManager;
7 import com.intellij.cvsSupport2.application.CvsInfo;
8 import com.intellij.cvsSupport2.changeBrowser.CvsBinaryContentRevision;
9 import com.intellij.cvsSupport2.changeBrowser.CvsContentRevision;
10 import com.intellij.cvsSupport2.checkinProject.DirectoryContent;
11 import com.intellij.cvsSupport2.checkinProject.VirtualFileEntry;
12 import com.intellij.cvsSupport2.connections.CvsConnectionSettings;
13 import com.intellij.cvsSupport2.cvsoperations.cvsContent.GetFileContentOperation;
14 import com.intellij.cvsSupport2.cvsoperations.dateOrRevision.RevisionOrDate;
15 import com.intellij.cvsSupport2.cvsoperations.dateOrRevision.RevisionOrDateImpl;
16 import com.intellij.cvsSupport2.cvsoperations.dateOrRevision.SimpleRevision;
17 import com.intellij.cvsSupport2.errorHandling.CannotFindCvsRootException;
18 import com.intellij.cvsSupport2.history.CvsRevisionNumber;
19 import com.intellij.cvsSupport2.util.CvsVfsUtil;
20 import com.intellij.history.FileRevisionTimestampComparator;
21 import com.intellij.history.LocalHistory;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.fileEditor.FileDocumentManager;
24 import com.intellij.openapi.fileTypes.FileTypeManager;
25 import com.intellij.openapi.progress.ProgressIndicator;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.roots.ProjectFileIndex;
28 import com.intellij.openapi.roots.ProjectRootManager;
29 import com.intellij.openapi.util.Comparing;
30 import com.intellij.openapi.vcs.FilePath;
31 import com.intellij.openapi.vcs.FileStatus;
32 import com.intellij.openapi.vcs.VcsException;
33 import com.intellij.openapi.vcs.actions.VcsContextFactory;
34 import com.intellij.openapi.vcs.changes.*;
35 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.util.containers.HashMap;
38 import com.intellij.vcsUtil.VcsUtil;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.netbeans.lib.cvsclient.admin.Entry;
44 import java.io.File;
45 import java.io.UnsupportedEncodingException;
46 import java.text.ParseException;
47 import java.util.Collection;
48 import java.util.Date;
49 import java.util.List;
51 /**
52 * @author max
54 public class CvsChangeProvider implements ChangeProvider {
55 private static final Logger LOG = Logger.getInstance("#com.intellij.cvsSupport2.cvsstatuses.CvsChangeProvider");
57 private final CvsVcs2 myVcs;
58 private final CvsEntriesManager myEntriesManager;
59 private final ProjectFileIndex myFileIndex;
60 private final ChangeListManager myChangeListManager;
62 public CvsChangeProvider(final CvsVcs2 vcs, CvsEntriesManager entriesManager) {
63 myVcs = vcs;
64 myEntriesManager = entriesManager;
65 myFileIndex = ProjectRootManager.getInstance(vcs.getProject()).getFileIndex();
66 myChangeListManager = ChangeListManager.getInstance(vcs.getProject());
69 public void getChanges(final VcsDirtyScope dirtyScope, final ChangelistBuilder builder, final ProgressIndicator progress,
70 final ChangeListManagerGate addGate) {
71 if (LOG.isDebugEnabled()) {
72 LOG.debug("Processing changes for scope " + dirtyScope);
74 for (FilePath path : dirtyScope.getRecursivelyDirtyDirectories()) {
75 final VirtualFile dir = path.getVirtualFile();
76 if (dir != null) {
77 processEntriesIn(dir, dirtyScope, builder, true);
79 else {
80 processFile(path, builder);
84 for (FilePath path : dirtyScope.getDirtyFiles()) {
85 if (path.isDirectory()) {
86 final VirtualFile dir = path.getVirtualFile();
87 if (dir != null) {
88 processEntriesIn(dir, dirtyScope, builder, false);
90 else {
91 processFile(path, builder);
94 else {
95 processFile(path, builder);
98 if (LOG.isDebugEnabled()) {
99 LOG.debug("Done processing changes");
103 public boolean isModifiedDocumentTrackingRequired() {
104 return true;
107 public void doCleanup(final List<VirtualFile> files) {
110 private void processEntriesIn(@NotNull VirtualFile dir, VcsDirtyScope scope, ChangelistBuilder builder, boolean recursively) {
111 final FilePath path = VcsContextFactory.SERVICE.getInstance().createFilePathOn(dir);
112 if (!scope.belongsTo(path)) {
113 if (LOG.isDebugEnabled()) {
114 LOG.debug("Skipping out of scope path " + path);
116 return;
118 final DirectoryContent dirContent = getDirectoryContent(dir);
120 for (VirtualFile file : dirContent.getUnknownFiles()) {
121 builder.processUnversionedFile(file);
123 for (VirtualFile file : dirContent.getIgnoredFiles()) {
124 builder.processIgnoredFile(file);
127 for (Entry entry : dirContent.getDeletedDirectories()) {
128 builder.processLocallyDeletedFile(VcsUtil.getFilePath(CvsVfsUtil.getFileFor(dir, entry.getFileName()), true));
131 for (Entry entry : dirContent.getDeletedFiles()) {
132 builder.processLocallyDeletedFile(VcsUtil.getFilePath(CvsVfsUtil.getFileFor(dir, entry.getFileName()), false));
136 final Collection<VirtualFile> unknownDirs = dirContent.getUnknownDirectories();
137 for (VirtualFile file : unknownDirs) {
138 builder.processUnversionedFile(file);
142 checkSwitchedDir(dir, builder, scope);
144 if (CvsUtil.fileIsUnderCvs(dir) && dir.getChildren().length == 1 /* admin dir */ &&
145 dirContent.getDeletedFiles().isEmpty() && hasRemovedFiles(dirContent.getFiles())) {
146 // directory is going to be deleted
147 builder.processChange(new Change(CurrentContentRevision.create(path), CurrentContentRevision.create(path), FileStatus.DELETED), CvsVcs2.getKey());
149 for (VirtualFileEntry fileEntry : dirContent.getFiles()) {
150 processFile(dir, fileEntry.getVirtualFile(), fileEntry.getEntry(), builder);
153 if (recursively) {
154 final VirtualFile[] children = CvsVfsUtil.getChildrenOf(dir);
155 if (children != null) {
156 for (VirtualFile file : children) {
157 if (file.isDirectory()) {
158 final boolean isIgnored = myFileIndex.isIgnored(file);
159 if (!isIgnored) {
160 processEntriesIn(file, scope, builder, true);
162 else {
163 if (LOG.isDebugEnabled()) {
164 LOG.debug("Skipping ignored path " + file.getPath());
173 private static boolean hasRemovedFiles(final Collection<VirtualFileEntry> files) {
174 for(VirtualFileEntry e: files) {
175 if (e.getEntry().isRemoved()) {
176 return true;
179 return false;
183 private void processFile(final FilePath filePath, final ChangelistBuilder builder) {
184 final VirtualFile dir = filePath.getVirtualFileParent();
185 if (dir == null) return;
187 final Entry entry = myEntriesManager.getEntryFor(dir, filePath.getName());
188 final FileStatus status = CvsStatusProvider.getStatus(filePath.getVirtualFile(), entry);
189 VcsRevisionNumber number = entry != null ? new CvsRevisionNumber(entry.getRevision()) : VcsRevisionNumber.NULL;
190 processStatus(filePath, dir.findChild(filePath.getName()), status, number, entry != null && entry.isBinary(), builder);
191 checkSwitchedFile(filePath, builder, dir, entry);
194 private void processFile(final VirtualFile dir, @Nullable VirtualFile file, Entry entry, final ChangelistBuilder builder) {
195 final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(dir, entry.getFileName());
196 final FileStatus status = CvsStatusProvider.getStatus(file, entry);
197 final VcsRevisionNumber number = new CvsRevisionNumber(entry.getRevision());
198 processStatus(filePath, file, status, number, entry.isBinary(), builder);
199 checkSwitchedFile(filePath, builder, dir, entry);
202 private void checkSwitchedDir(final VirtualFile dir, final ChangelistBuilder builder, final VcsDirtyScope scope) {
203 VirtualFile parentDir = dir.getParent();
204 if (parentDir == null || !myFileIndex.isInContent(parentDir)) {
205 return;
207 final CvsInfo info = myEntriesManager.getCvsInfoFor(dir);
208 if (info.getRepository() == null) {
209 // don't report unversioned directories as switched (IDEADEV-17178)
210 builder.processUnversionedFile(dir);
211 return;
213 final String dirTag = info.getStickyTag();
214 final CvsInfo parentInfo = myEntriesManager.getCvsInfoFor(parentDir);
215 final String parentDirTag = parentInfo.getStickyTag();
216 if (!Comparing.equal(dirTag, parentDirTag)) {
217 if (dirTag == null) {
218 builder.processSwitchedFile(dir, CvsUtil.HEAD, true);
220 else if (CvsUtil.isNonDateTag(dirTag)) {
221 final String tag = dirTag.substring(1);
222 // a switch between a branch tag and a non-branch tag is not a switch
223 if (parentDirTag != null && CvsUtil.isNonDateTag(parentDirTag)) {
224 String parentTag = parentDirTag.substring(1);
225 if (tag.equals(parentTag)) {
226 return;
229 builder.processSwitchedFile(dir, CvsBundle.message("switched.tag.format", tag), true);
231 else if (dirTag.startsWith(CvsUtil.STICKY_DATE_PREFIX)) {
232 try {
233 Date date = Entry.STICKY_DATE_FORMAT.parse(dirTag.substring(1));
234 builder.processSwitchedFile(dir, CvsBundle.message("switched.date.format", date), true);
236 catch (ParseException e) {
237 builder.processSwitchedFile(dir, CvsBundle.message("switched.date.format", dirTag.substring(1)), true);
241 else if (!scope.belongsTo(VcsContextFactory.SERVICE.getInstance().createFilePathOn(parentDir))) {
242 // check if we're doing a partial refresh below a switched dir (IDEADEV-16611)
243 final String parentBranch = myChangeListManager.getSwitchedBranch(parentDir);
244 if (parentBranch != null) {
245 builder.processSwitchedFile(dir, parentBranch, true);
250 private void checkSwitchedFile(final FilePath filePath, final ChangelistBuilder builder, final VirtualFile dir, final Entry entry) {
251 // if content root itself is switched, ignore
252 if (!myFileIndex.isInContent(dir)) {
253 return;
255 final String dirTag = myEntriesManager.getCvsInfoFor(dir).getStickyTag();
256 String dirStickyInfo = getStickyInfo(dirTag);
257 if (entry != null && !Comparing.equal(entry.getStickyInformation(), dirStickyInfo)) {
258 VirtualFile file = filePath.getVirtualFile();
259 if (file != null) {
260 if (entry.getStickyTag() != null) {
261 builder.processSwitchedFile(file, CvsBundle.message("switched.tag.format", entry.getStickyTag()), false);
263 else if (entry.getStickyDate() != null) {
264 builder.processSwitchedFile(file, CvsBundle.message("switched.date.format", entry.getStickyDate()), false);
266 else if (entry.getStickyRevision() != null) {
267 builder.processSwitchedFile(file, CvsBundle.message("switched.revision.format", entry.getStickyRevision()), false);
269 else {
270 builder.processSwitchedFile(file, CvsUtil.HEAD, false);
276 @Nullable
277 private static String getStickyInfo(final String dirTag) {
278 return (dirTag != null && dirTag.length() > 1) ? dirTag.substring(1) : null;
281 private void processStatus(final FilePath filePath,
282 final VirtualFile file,
283 final FileStatus status,
284 final VcsRevisionNumber number,
285 final boolean isBinary,
286 final ChangelistBuilder builder) {
287 if (LOG.isDebugEnabled()) {
288 LOG.debug("processStatus: filePath=" + filePath + " status=" + status);
290 if (status == FileStatus.NOT_CHANGED) {
291 if (file != null && FileDocumentManager.getInstance().isFileModifiedAndDocumentUnsaved(file)) {
292 builder.processChange(
293 new Change(createCvsRevision(filePath, number, isBinary), CurrentContentRevision.create(filePath), FileStatus.MODIFIED), CvsVcs2.getKey());
295 return;
297 if (status == FileStatus.MODIFIED || status == FileStatus.MERGE || status == FileStatus.MERGED_WITH_CONFLICTS) {
298 builder.processChange(new Change(createCvsRevision(filePath, number, isBinary),
299 CurrentContentRevision.create(filePath), status), CvsVcs2.getKey());
301 else if (status == FileStatus.ADDED) {
302 builder.processChange(new Change(null, CurrentContentRevision.create(filePath), status), CvsVcs2.getKey());
304 else if (status == FileStatus.DELETED) {
305 // not sure about deleted content
306 builder.processChange(new Change(createCvsRevision(filePath, number, isBinary), null, status), CvsVcs2.getKey());
308 else if (status == FileStatus.DELETED_FROM_FS) {
309 builder.processLocallyDeletedFile(filePath);
311 else if (status == FileStatus.UNKNOWN) {
312 builder.processUnversionedFile(filePath.getVirtualFile());
314 else if (status == FileStatus.IGNORED) {
315 builder.processIgnoredFile(filePath.getVirtualFile());
319 private ContentRevision createRemote(final CvsRevisionNumber revisionNumber, final VirtualFile selectedFile) {
320 final CvsConnectionSettings settings = CvsEntriesManager.getInstance().getCvsConnectionSettingsFor(selectedFile.getParent());
321 final File file = new File(CvsUtil.getModuleName(selectedFile));
323 final RevisionOrDate versionInfo;
324 if (revisionNumber.getDateOrRevision() != null) {
325 versionInfo = RevisionOrDateImpl.createOn(revisionNumber.getDateOrRevision());
327 else {
328 versionInfo = new SimpleRevision(revisionNumber.asString());
331 final Project project = myVcs.getProject();
332 final File ioFile = new File(selectedFile.getPath());
333 if (selectedFile.getFileType().isBinary()) {
334 return new CvsBinaryContentRevision(file, ioFile, versionInfo, settings, project);
336 else {
337 return new CvsContentRevision(file, ioFile, versionInfo, settings, project);
341 @Nullable
342 public byte[] getLastUpToDateContentFor(@NotNull final VirtualFile f) {
343 Entry entry = myEntriesManager.getEntryFor(f.getParent(), f.getName());
344 if (entry != null && entry.isResultOfMerge()) {
345 // try created by VCS during merge
346 byte[] content = CvsUtil.getStoredContentForFile(f, entry.getRevision());
347 if (content != null) {
348 return content;
350 // try cached by IDEA in CVS dir
351 return CvsUtil.getCachedStoredContent(f, entry.getRevision());
353 final long upToDateTimestamp = getUpToDateTimeForFile(f);
354 FileRevisionTimestampComparator c = new FileRevisionTimestampComparator() {
355 public boolean isSuitable(long revisionTimestamp) {
356 return CvsStatusProvider.timeStampsAreEqual(upToDateTimestamp, revisionTimestamp);
359 byte[] localHistoryContent = LocalHistory.getByteContent(myVcs.getProject(), f, c);
360 if (localHistoryContent == null) {
361 if (entry != null && CvsUtil.haveCachedContent(f, entry.getRevision())) {
362 return CvsUtil.getCachedStoredContent(f, entry.getRevision());
365 return localHistoryContent;
368 public long getUpToDateTimeForFile(@NotNull VirtualFile vFile) {
369 Entry entry = myEntriesManager.getEntryFor(vFile.getParent(), vFile.getName());
370 if (entry == null) return -1;
371 // retrieve of any file version in time is not correct since update/merge was applie3d to already modified file
372 /*if (entry.isResultOfMerge()) {
373 long resultForMerge = CvsUtil.getUpToDateDateForFile(vFile);
374 if (resultForMerge > 0) {
375 return resultForMerge;
379 Date lastModified = entry.getLastModified();
380 if (lastModified == null) return -1;
381 return lastModified.getTime();
384 private CvsUpToDateRevision createCvsRevision(FilePath path, VcsRevisionNumber revisionNumber, boolean isBinary) {
385 if (isBinary) {
386 return new CvsUpToDateBinaryRevision(path, revisionNumber);
388 return new CvsUpToDateRevision(path, revisionNumber);
391 private static boolean isInContent(VirtualFile file) {
392 return file == null || !FileTypeManager.getInstance().isFileIgnored(file.getName());
395 private static DirectoryContent getDirectoryContent(VirtualFile directory) {
396 if (LOG.isDebugEnabled()) {
397 LOG.debug("Retrieving directory content for " + directory);
399 CvsInfo cvsInfo = CvsEntriesManager.getInstance().getCvsInfoFor(directory);
400 DirectoryContent result = new DirectoryContent(cvsInfo);
402 VirtualFile[] children = CvsVfsUtil.getChildrenOf(directory);
403 if (children == null) children = VirtualFile.EMPTY_ARRAY;
405 Collection<Entry> entries = cvsInfo.getEntries();
407 HashMap<String, VirtualFile> nameToFileMap = new HashMap<String, VirtualFile>();
408 for (VirtualFile child : children) {
409 nameToFileMap.put(child.getName(), child);
412 for (final Entry entry : entries) {
413 String fileName = entry.getFileName();
414 if (entry.isDirectory()) {
415 if (nameToFileMap.containsKey(fileName)) {
416 VirtualFile virtualFile = nameToFileMap.get(fileName);
417 if (isInContent(virtualFile)) {
418 result.addDirectory(new VirtualFileEntry(virtualFile, entry));
421 else if (!entry.isRemoved() && !FileTypeManager.getInstance().isFileIgnored(fileName)) {
422 result.addDeletedDirectory(entry);
425 else {
426 if (nameToFileMap.containsKey(fileName) || entry.isRemoved()) {
427 VirtualFile virtualFile = nameToFileMap.get(fileName);
428 if (isInContent(virtualFile)) {
429 result.addFile(new VirtualFileEntry(virtualFile, entry));
432 else if (!entry.isAddedFile()) {
433 result.addDeletedFile(entry);
436 nameToFileMap.remove(fileName);
439 for (final String name : nameToFileMap.keySet()) {
440 VirtualFile unknown = nameToFileMap.get(name);
441 if (unknown.isDirectory()) {
442 if (isInContent(unknown)) {
443 result.addUnknownDirectory(unknown);
446 else {
447 if (isInContent(unknown)) {
448 boolean isIgnored = result.getCvsInfo().getIgnoreFilter().shouldBeIgnored(unknown.getName());
449 if (isIgnored) {
450 result.addIgnoredFile(unknown);
452 else {
453 result.addUnknownFile(unknown);
459 return result;
462 private class CvsUpToDateRevision implements ContentRevision {
463 protected final FilePath myPath;
464 private final VcsRevisionNumber myRevisionNumber;
465 private String myContent;
467 protected CvsUpToDateRevision(final FilePath path, final VcsRevisionNumber revisionNumber) {
468 myRevisionNumber = revisionNumber;
469 myPath = path;
472 @Nullable
473 public String getContent() throws VcsException {
474 if (myContent == null) {
475 try {
476 byte[] fileBytes = getUpToDateBinaryContent();
477 myContent = fileBytes == null ? null : new String(fileBytes, myPath.getCharset().name());
479 catch (CannotFindCvsRootException e) {
480 myContent = null;
482 catch (UnsupportedEncodingException e) {
483 myContent = null;
486 return myContent;
489 @Nullable
490 protected byte[] getUpToDateBinaryContent() throws CannotFindCvsRootException {
491 VirtualFile virtualFile = myPath.getVirtualFile();
492 byte[] result = null;
493 if (virtualFile != null) {
494 result = getLastUpToDateContentFor(virtualFile);
496 if (result == null) {
497 String createVersionFile = null;
498 final GetFileContentOperation operation;
499 if (virtualFile != null) {
500 // todo maybe refactor where data lives
501 Entry entry = myEntriesManager.getEntryFor(virtualFile.getParent(), virtualFile.getName());
502 if (entry != null && entry.isResultOfMerge()) {
503 createVersionFile = entry.getRevision();
506 operation = GetFileContentOperation.createForFile(virtualFile, SimpleRevision.createForTheSameVersionOf(virtualFile));
508 else {
509 operation = GetFileContentOperation.createForFile(myPath);
511 if (operation.getRoot().isOffline()) return null;
512 CvsVcs2.executeQuietOperation(CvsBundle.message("operation.name.get.file.content"), operation, myVcs.getProject());
513 result = operation.tryGetFileBytes();
515 if (result != null && createVersionFile != null) {
516 // cache in CVS area to reduce remote requests number (old revisions are deleted)
517 CvsUtil.storeContentForRevision(virtualFile, createVersionFile, result);
520 return result;
523 @NotNull
524 public FilePath getFile() {
525 return myPath;
528 @NotNull
529 public VcsRevisionNumber getRevisionNumber() {
530 return myRevisionNumber;
533 @NonNls
534 public String toString() {
535 return "CvsUpToDateRevision:" + myPath;
539 private class CvsUpToDateBinaryRevision extends CvsUpToDateRevision implements BinaryContentRevision {
540 private byte[] myBinaryContent;
542 public CvsUpToDateBinaryRevision(final FilePath path, final VcsRevisionNumber revisionNumber) {
543 super(path, revisionNumber);
546 @Nullable
547 public byte[] getBinaryContent() throws VcsException {
548 if (myBinaryContent == null) {
549 try {
550 myBinaryContent = getUpToDateBinaryContent();
552 catch (CannotFindCvsRootException e) {
553 throw new VcsException(e);
556 return myBinaryContent;
559 @NonNls
560 public String toString() {
561 return "CvsUpToDateBinaryRevision:" + myPath;