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
;
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
;
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
) {
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();
77 processEntriesIn(dir
, dirtyScope
, builder
, true);
80 processFile(path
, builder
);
84 for (FilePath path
: dirtyScope
.getDirtyFiles()) {
85 if (path
.isDirectory()) {
86 final VirtualFile dir
= path
.getVirtualFile();
88 processEntriesIn(dir
, dirtyScope
, builder
, false);
91 processFile(path
, builder
);
95 processFile(path
, builder
);
98 if (LOG
.isDebugEnabled()) {
99 LOG
.debug("Done processing changes");
103 public boolean isModifiedDocumentTrackingRequired() {
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
);
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
);
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
);
160 processEntriesIn(file
, scope
, builder
, true);
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()) {
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
)) {
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
);
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
)) {
229 builder
.processSwitchedFile(dir
, CvsBundle
.message("switched.tag.format", tag
), true);
231 else if (dirTag
.startsWith(CvsUtil
.STICKY_DATE_PREFIX
)) {
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
)) {
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();
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);
270 builder
.processSwitchedFile(file
, CvsUtil
.HEAD
, false);
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());
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());
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
);
337 return new CvsContentRevision(file
, ioFile
, versionInfo
, settings
, project
);
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) {
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
) {
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
);
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
);
447 if (isInContent(unknown
)) {
448 boolean isIgnored
= result
.getCvsInfo().getIgnoreFilter().shouldBeIgnored(unknown
.getName());
450 result
.addIgnoredFile(unknown
);
453 result
.addUnknownFile(unknown
);
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
;
473 public String
getContent() throws VcsException
{
474 if (myContent
== null) {
476 byte[] fileBytes
= getUpToDateBinaryContent();
477 myContent
= fileBytes
== null ?
null : new String(fileBytes
, myPath
.getCharset().name());
479 catch (CannotFindCvsRootException e
) {
482 catch (UnsupportedEncodingException e
) {
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
));
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
);
524 public FilePath
getFile() {
529 public VcsRevisionNumber
getRevisionNumber() {
530 return myRevisionNumber
;
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
);
547 public byte[] getBinaryContent() throws VcsException
{
548 if (myBinaryContent
== null) {
550 myBinaryContent
= getUpToDateBinaryContent();
552 catch (CannotFindCvsRootException e
) {
553 throw new VcsException(e
);
556 return myBinaryContent
;
560 public String
toString() {
561 return "CvsUpToDateBinaryRevision:" + myPath
;