1 package org
.jetbrains
.idea
.svn
;
3 import com
.intellij
.openapi
.diagnostic
.Logger
;
4 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
5 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
6 import com
.intellij
.openapi
.progress
.ProgressManager
;
7 import com
.intellij
.openapi
.util
.Comparing
;
8 import com
.intellij
.openapi
.util
.text
.StringUtil
;
9 import com
.intellij
.openapi
.vcs
.FilePath
;
10 import com
.intellij
.openapi
.vcs
.FilePathImpl
;
11 import com
.intellij
.openapi
.vcs
.VcsBundle
;
12 import com
.intellij
.openapi
.vcs
.VcsException
;
13 import com
.intellij
.openapi
.vcs
.actions
.VcsContextFactory
;
14 import com
.intellij
.openapi
.vcs
.changes
.*;
15 import com
.intellij
.openapi
.vfs
.VirtualFile
;
16 import com
.intellij
.util
.EventDispatcher
;
17 import org
.jetbrains
.annotations
.NotNull
;
18 import org
.jetbrains
.annotations
.Nullable
;
19 import org
.jetbrains
.idea
.svn
.actions
.CleanupWorker
;
20 import org
.tmatesoft
.svn
.core
.SVNDepth
;
21 import org
.tmatesoft
.svn
.core
.SVNException
;
22 import org
.tmatesoft
.svn
.core
.SVNNodeKind
;
23 import org
.tmatesoft
.svn
.core
.SVNURL
;
24 import org
.tmatesoft
.svn
.core
.internal
.util
.SVNPathUtil
;
25 import org
.tmatesoft
.svn
.core
.wc
.ISVNStatusFileProvider
;
26 import org
.tmatesoft
.svn
.core
.wc
.SVNStatus
;
27 import org
.tmatesoft
.svn
.core
.wc
.SVNStatusType
;
36 public class SvnChangeProvider
implements ChangeProvider
{
37 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.SvnChangeProvider");
38 public static final String ourDefaultListName
= VcsBundle
.message("changes.default.changlist.name");
40 private final SvnVcs myVcs
;
41 private final VcsContextFactory myFactory
;
42 private SvnFileUrlMappingImpl mySvnFileUrlMapping
;
44 public SvnChangeProvider(final SvnVcs vcs
) {
46 myFactory
= VcsContextFactory
.SERVICE
.getInstance();
47 mySvnFileUrlMapping
= (SvnFileUrlMappingImpl
) vcs
.getSvnFileUrlMapping();
50 public void getChanges(final VcsDirtyScope dirtyScope
, final ChangelistBuilder builder
, ProgressIndicator progress
,
51 final ChangeListManagerGate addGate
) throws VcsException
{
52 final SvnScopeZipper zipper
= new SvnScopeZipper(dirtyScope
);
55 final Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
= zipper
.getNonRecursiveDirs();
56 final ISVNStatusFileProvider fileProvider
= createFileProvider(nonRecursiveMap
);
59 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, progress
);
61 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, progress
);
62 final NestedCopiesBuilder nestedCopiesBuilder
= new NestedCopiesBuilder();
64 final EventDispatcher
<StatusReceiver
> statusReceiver
= EventDispatcher
.create(StatusReceiver
.class);
65 statusReceiver
.addListener(context
);
66 statusReceiver
.addListener(nestedCopiesBuilder
);
68 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(statusReceiver
.getMulticaster(), partner
);
70 for (FilePath path
: zipper
.getRecursiveDirs()) {
71 walker
.go(path
, SVNDepth
.INFINITY
);
74 partner
.setFileProvider(fileProvider
);
75 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
76 walker
.go(item
.getDir(), SVNDepth
.FILES
);
79 // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
80 /*for (FilePath path : zipper.getSingleFiles()) {
81 FileStatus status = getParentStatus(context, path);
82 processFile(path, context, status, false, context.getClient());
85 processCopiedAndDeleted(context
);
87 mySvnFileUrlMapping
.acceptNestedData(nestedCopiesBuilder
.getSet());
89 catch (SVNException e
) {
90 throw new VcsException(e
);
94 private ISVNStatusFileProvider
createFileProvider(Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
) {
95 // translate into terms of File.getAbsolutePath()
96 final Map
<String
, Map
> preparedMap
= new HashMap
<String
, Map
>();
97 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
98 final Map result
= new HashMap();
99 for (FilePath path
: item
.getChildrenList()) {
100 result
.put(path
.getName(), path
.getIOFile());
102 preparedMap
.put(item
.getDir().getIOFile().getAbsolutePath(), result
);
104 return new ISVNStatusFileProvider() {
105 public Map
getChildrenFiles(File parent
) {
106 return preparedMap
.get(parent
.getAbsolutePath());
111 private void processCopiedAndDeleted(final SvnChangeProviderContext context
) throws SVNException
{
112 for(SvnChangedFile copiedFile
: context
.getCopiedFiles()) {
113 if (context
.isCanceled()) {
114 throw new ProcessCanceledException();
116 processCopiedFile(copiedFile
, context
.getBuilder(), context
);
118 for(SvnChangedFile deletedFile
: context
.getDeletedFiles()) {
119 if (context
.isCanceled()) {
120 throw new ProcessCanceledException();
122 context
.processStatus(deletedFile
.getFilePath(), deletedFile
.getStatus());
126 public void getChanges(final FilePath path
, final boolean recursive
, final ChangelistBuilder builder
) throws SVNException
{
127 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, null);
128 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, ProgressManager
.getInstance().getProgressIndicator());
129 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(context
, partner
);
130 walker
.go(path
, recursive ? SVNDepth
.INFINITY
: SVNDepth
.IMMEDIATES
);
131 processCopiedAndDeleted(context
);
135 private String
changeListNameFromStatus(final SVNStatus status
) {
136 if (WorkingCopyFormat
.getInstance(status
.getWorkingCopyFormat()).supportsChangelists()) {
137 if (SVNNodeKind
.FILE
.equals(status
.getKind())) {
138 final String clName
= status
.getChangelistName();
139 return (clName
== null) ?
null : clName
;
142 // always null for earlier versions
146 private void processCopiedFile(SvnChangedFile copiedFile
, ChangelistBuilder builder
, SvnChangeProviderContext context
) throws SVNException
{
147 boolean foundRename
= false;
148 final SVNStatus copiedStatus
= copiedFile
.getStatus();
149 final String copyFromURL
= copiedFile
.getCopyFromURL();
150 final FilePath copiedToPath
= copiedFile
.getFilePath();
152 // if copy target is _deleted_, treat like deleted, not moved!
153 /*for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
154 final SvnChangedFile deletedFile = iterator.next();
155 final FilePath deletedPath = deletedFile.getFilePath();
157 if (Comparing.equal(deletedPath, copiedToPath)) {
162 final Set
<SvnChangedFile
> deletedToDelete
= new HashSet
<SvnChangedFile
>();
164 for (Iterator
<SvnChangedFile
> iterator
= context
.getDeletedFiles().iterator(); iterator
.hasNext();) {
165 SvnChangedFile deletedFile
= iterator
.next();
166 final SVNStatus deletedStatus
= deletedFile
.getStatus();
167 if ((deletedStatus
!= null) && (deletedStatus
.getURL() != null) && Comparing
.equal(copyFromURL
, deletedStatus
.getURL().toString())) {
168 final String clName
= changeListNameFromStatus(copiedFile
.getStatus());
169 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedFile
, true),
170 CurrentContentRevision
.create(copiedFile
.getFilePath()), copiedStatus
, deletedStatus
), clName
, SvnVcs
.getKey());
171 deletedToDelete
.add(deletedFile
);
172 for(Iterator
<SvnChangedFile
> iterChild
= context
.getDeletedFiles().iterator(); iterChild
.hasNext();) {
173 SvnChangedFile deletedChild
= iterChild
.next();
174 final SVNStatus childStatus
= deletedChild
.getStatus();
175 if (childStatus
== null) {
178 final SVNURL childUrl
= childStatus
.getURL();
179 if (childUrl
== null) {
182 final String childURL
= childUrl
.toString();
183 if (StringUtil
.startsWithConcatenationOf(childURL
, copyFromURL
, "/")) {
184 String relativePath
= childURL
.substring(copyFromURL
.length());
185 File newPath
= new File(copiedFile
.getFilePath().getIOFile(), relativePath
);
186 FilePath newFilePath
= myFactory
.createFilePathOn(newPath
);
187 if (!context
.isDeleted(newFilePath
)) {
188 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedChild
, true),
189 CurrentContentRevision
.create(newFilePath
),
190 context
.getTreeConflictStatus(newPath
), childStatus
), clName
, SvnVcs
.getKey());
191 deletedToDelete
.add(deletedChild
);
200 final List
<SvnChangedFile
> deletedFiles
= context
.getDeletedFiles();
201 for (SvnChangedFile file
: deletedToDelete
) {
202 deletedFiles
.remove(file
);
205 // handle the case when the deleted file wasn't included in the dirty scope - try searching for the local copy
206 // by building a relative url
207 if (!foundRename
&& copiedStatus
.getURL() != null) {
208 File wcPath
= guessWorkingCopyPath(copiedStatus
.getFile(), copiedStatus
.getURL(), copyFromURL
);
211 status
= context
.getClient().doStatus(wcPath
, false);
213 catch(SVNException ex
) {
216 if (status
!= null && status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
217 final FilePath filePath
= myFactory
.createFilePathOnDeleted(wcPath
, false);
218 final SvnContentRevision beforeRevision
= SvnContentRevision
.create(myVcs
, filePath
, status
.getCommittedRevision());
219 final ContentRevision afterRevision
= CurrentContentRevision
.create(copiedFile
.getFilePath());
220 builder
.processChangeInList(context
.createMovedChange(beforeRevision
, afterRevision
, copiedStatus
, status
), changeListNameFromStatus(status
),
228 LOG
.info("Rename not found for " + copiedFile
.getFilePath().getPresentableUrl());
229 context
.processStatus(copiedFile
.getFilePath(), copiedStatus
);
233 private SvnContentRevision
createBeforeRevision(final SvnChangedFile changedFile
, final boolean forDeleted
) {
234 return SvnContentRevision
.create(myVcs
,
235 forDeleted ? FilePathImpl
.createForDeletedFile(changedFile
.getStatus().getFile(), changedFile
.getFilePath().isDirectory()) :
236 changedFile
.getFilePath(), changedFile
.getStatus().getCommittedRevision());
239 private static File
guessWorkingCopyPath(final File file
, @NotNull final SVNURL url
, final String copyFromURL
) throws SVNException
{
240 String copiedPath
= url
.getPath();
241 String copyFromPath
= SVNURL
.parseURIEncoded(copyFromURL
).getPath();
242 String commonPathAncestor
= SVNPathUtil
.getCommonPathAncestor(copiedPath
, copyFromPath
);
243 int pathSegmentCount
= SVNPathUtil
.getSegmentsCount(copiedPath
);
244 int ancestorSegmentCount
= SVNPathUtil
.getSegmentsCount(commonPathAncestor
);
245 boolean startsWithSlash
= file
.getAbsolutePath().startsWith("/");
246 List
<String
> segments
= StringUtil
.split(file
.getPath(), File
.separator
);
247 List
<String
> copyFromPathSegments
= StringUtil
.split(copyFromPath
, "/");
248 List
<String
> resultSegments
= new ArrayList
<String
>();
249 final int keepSegments
= segments
.size() - pathSegmentCount
+ ancestorSegmentCount
;
250 for(int i
=0; i
< keepSegments
; i
++) {
251 resultSegments
.add(segments
.get(i
));
253 for(int i
=ancestorSegmentCount
; i
<copyFromPathSegments
.size(); i
++) {
254 resultSegments
.add(copyFromPathSegments
.get(i
));
257 String result
= StringUtil
.join(resultSegments
, "/");
258 if (startsWithSlash
) {
259 result
= "/" + result
;
261 return new File(result
);
264 public boolean isModifiedDocumentTrackingRequired() {
268 public void doCleanup(final List
<VirtualFile
> files
) {
269 new CleanupWorker(files
.toArray(new VirtualFile
[files
.size()]), myVcs
.getProject(), "action.Subversion.cleanup.progress.title").execute();