2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.jetbrains
.idea
.svn
;
18 import com
.intellij
.openapi
.diagnostic
.Logger
;
19 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
20 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
21 import com
.intellij
.openapi
.progress
.ProgressManager
;
22 import com
.intellij
.openapi
.util
.Comparing
;
23 import com
.intellij
.openapi
.util
.text
.StringUtil
;
24 import com
.intellij
.openapi
.vcs
.FilePath
;
25 import com
.intellij
.openapi
.vcs
.FilePathImpl
;
26 import com
.intellij
.openapi
.vcs
.VcsBundle
;
27 import com
.intellij
.openapi
.vcs
.VcsException
;
28 import com
.intellij
.openapi
.vcs
.actions
.VcsContextFactory
;
29 import com
.intellij
.openapi
.vcs
.changes
.*;
30 import com
.intellij
.openapi
.vfs
.VirtualFile
;
31 import com
.intellij
.util
.EventDispatcher
;
32 import org
.jetbrains
.annotations
.NotNull
;
33 import org
.jetbrains
.annotations
.Nullable
;
34 import org
.jetbrains
.idea
.svn
.actions
.CleanupWorker
;
35 import org
.tmatesoft
.svn
.core
.SVNDepth
;
36 import org
.tmatesoft
.svn
.core
.SVNException
;
37 import org
.tmatesoft
.svn
.core
.SVNNodeKind
;
38 import org
.tmatesoft
.svn
.core
.SVNURL
;
39 import org
.tmatesoft
.svn
.core
.internal
.util
.SVNPathUtil
;
40 import org
.tmatesoft
.svn
.core
.wc
.ISVNStatusFileProvider
;
41 import org
.tmatesoft
.svn
.core
.wc
.SVNStatus
;
42 import org
.tmatesoft
.svn
.core
.wc
.SVNStatusType
;
51 public class SvnChangeProvider
implements ChangeProvider
{
52 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.SvnChangeProvider");
53 public static final String ourDefaultListName
= VcsBundle
.message("changes.default.changlist.name");
55 private final SvnVcs myVcs
;
56 private final VcsContextFactory myFactory
;
57 private SvnFileUrlMappingImpl mySvnFileUrlMapping
;
59 public SvnChangeProvider(final SvnVcs vcs
) {
61 myFactory
= VcsContextFactory
.SERVICE
.getInstance();
62 mySvnFileUrlMapping
= (SvnFileUrlMappingImpl
) vcs
.getSvnFileUrlMapping();
65 public void getChanges(final VcsDirtyScope dirtyScope
, final ChangelistBuilder builder
, ProgressIndicator progress
,
66 final ChangeListManagerGate addGate
) throws VcsException
{
67 final SvnScopeZipper zipper
= new SvnScopeZipper(dirtyScope
);
70 final Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
= zipper
.getNonRecursiveDirs();
71 final ISVNStatusFileProvider fileProvider
= createFileProvider(nonRecursiveMap
);
74 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, progress
);
76 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, progress
);
77 final NestedCopiesBuilder nestedCopiesBuilder
= new NestedCopiesBuilder();
79 final EventDispatcher
<StatusReceiver
> statusReceiver
= EventDispatcher
.create(StatusReceiver
.class);
80 statusReceiver
.addListener(context
);
81 statusReceiver
.addListener(nestedCopiesBuilder
);
83 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(statusReceiver
.getMulticaster(), partner
);
85 for (FilePath path
: zipper
.getRecursiveDirs()) {
86 walker
.go(path
, SVNDepth
.INFINITY
);
89 partner
.setFileProvider(fileProvider
);
90 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
91 walker
.go(item
.getDir(), SVNDepth
.FILES
);
94 // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
95 /*for (FilePath path : zipper.getSingleFiles()) {
96 FileStatus status = getParentStatus(context, path);
97 processFile(path, context, status, false, context.getClient());
100 processCopiedAndDeleted(context
);
102 mySvnFileUrlMapping
.acceptNestedData(nestedCopiesBuilder
.getSet());
104 catch (SVNException e
) {
105 throw new VcsException(e
);
109 private ISVNStatusFileProvider
createFileProvider(Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
) {
110 // translate into terms of File.getAbsolutePath()
111 final Map
<String
, Map
> preparedMap
= new HashMap
<String
, Map
>();
112 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
113 final Map result
= new HashMap();
114 for (FilePath path
: item
.getChildrenList()) {
115 result
.put(path
.getName(), path
.getIOFile());
117 preparedMap
.put(item
.getDir().getIOFile().getAbsolutePath(), result
);
119 return new ISVNStatusFileProvider() {
120 public Map
getChildrenFiles(File parent
) {
121 return preparedMap
.get(parent
.getAbsolutePath());
126 private void processCopiedAndDeleted(final SvnChangeProviderContext context
) throws SVNException
{
127 for(SvnChangedFile copiedFile
: context
.getCopiedFiles()) {
128 if (context
.isCanceled()) {
129 throw new ProcessCanceledException();
131 processCopiedFile(copiedFile
, context
.getBuilder(), context
);
133 for(SvnChangedFile deletedFile
: context
.getDeletedFiles()) {
134 if (context
.isCanceled()) {
135 throw new ProcessCanceledException();
137 context
.processStatus(deletedFile
.getFilePath(), deletedFile
.getStatus());
141 public void getChanges(final FilePath path
, final boolean recursive
, final ChangelistBuilder builder
) throws SVNException
{
142 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, null);
143 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, ProgressManager
.getInstance().getProgressIndicator());
144 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(context
, partner
);
145 walker
.go(path
, recursive ? SVNDepth
.INFINITY
: SVNDepth
.IMMEDIATES
);
146 processCopiedAndDeleted(context
);
150 private String
changeListNameFromStatus(final SVNStatus status
) {
151 if (WorkingCopyFormat
.getInstance(status
.getWorkingCopyFormat()).supportsChangelists()) {
152 if (SVNNodeKind
.FILE
.equals(status
.getKind())) {
153 final String clName
= status
.getChangelistName();
154 return (clName
== null) ?
null : clName
;
157 // always null for earlier versions
161 private void processCopiedFile(SvnChangedFile copiedFile
, ChangelistBuilder builder
, SvnChangeProviderContext context
) throws SVNException
{
162 boolean foundRename
= false;
163 final SVNStatus copiedStatus
= copiedFile
.getStatus();
164 final String copyFromURL
= copiedFile
.getCopyFromURL();
165 final FilePath copiedToPath
= copiedFile
.getFilePath();
167 // if copy target is _deleted_, treat like deleted, not moved!
168 /*for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
169 final SvnChangedFile deletedFile = iterator.next();
170 final FilePath deletedPath = deletedFile.getFilePath();
172 if (Comparing.equal(deletedPath, copiedToPath)) {
177 final Set
<SvnChangedFile
> deletedToDelete
= new HashSet
<SvnChangedFile
>();
179 for (Iterator
<SvnChangedFile
> iterator
= context
.getDeletedFiles().iterator(); iterator
.hasNext();) {
180 SvnChangedFile deletedFile
= iterator
.next();
181 final SVNStatus deletedStatus
= deletedFile
.getStatus();
182 if ((deletedStatus
!= null) && (deletedStatus
.getURL() != null) && Comparing
.equal(copyFromURL
, deletedStatus
.getURL().toString())) {
183 final String clName
= changeListNameFromStatus(copiedFile
.getStatus());
184 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedFile
, true),
185 CurrentContentRevision
.create(copiedFile
.getFilePath()), copiedStatus
, deletedStatus
), clName
, SvnVcs
.getKey());
186 deletedToDelete
.add(deletedFile
);
187 for(Iterator
<SvnChangedFile
> iterChild
= context
.getDeletedFiles().iterator(); iterChild
.hasNext();) {
188 SvnChangedFile deletedChild
= iterChild
.next();
189 final SVNStatus childStatus
= deletedChild
.getStatus();
190 if (childStatus
== null) {
193 final SVNURL childUrl
= childStatus
.getURL();
194 if (childUrl
== null) {
197 final String childURL
= childUrl
.toString();
198 if (StringUtil
.startsWithConcatenationOf(childURL
, copyFromURL
, "/")) {
199 String relativePath
= childURL
.substring(copyFromURL
.length());
200 File newPath
= new File(copiedFile
.getFilePath().getIOFile(), relativePath
);
201 FilePath newFilePath
= myFactory
.createFilePathOn(newPath
);
202 if (!context
.isDeleted(newFilePath
)) {
203 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedChild
, true),
204 CurrentContentRevision
.create(newFilePath
),
205 context
.getTreeConflictStatus(newPath
), childStatus
), clName
, SvnVcs
.getKey());
206 deletedToDelete
.add(deletedChild
);
215 final List
<SvnChangedFile
> deletedFiles
= context
.getDeletedFiles();
216 for (SvnChangedFile file
: deletedToDelete
) {
217 deletedFiles
.remove(file
);
220 // handle the case when the deleted file wasn't included in the dirty scope - try searching for the local copy
221 // by building a relative url
222 if (!foundRename
&& copiedStatus
.getURL() != null) {
223 File wcPath
= guessWorkingCopyPath(copiedStatus
.getFile(), copiedStatus
.getURL(), copyFromURL
);
226 status
= context
.getClient().doStatus(wcPath
, false);
228 catch(SVNException ex
) {
231 if (status
!= null && status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
232 final FilePath filePath
= myFactory
.createFilePathOnDeleted(wcPath
, false);
233 final SvnContentRevision beforeRevision
= SvnContentRevision
.create(myVcs
, filePath
, status
.getCommittedRevision());
234 final ContentRevision afterRevision
= CurrentContentRevision
.create(copiedFile
.getFilePath());
235 builder
.processChangeInList(context
.createMovedChange(beforeRevision
, afterRevision
, copiedStatus
, status
), changeListNameFromStatus(status
),
243 LOG
.info("Rename not found for " + copiedFile
.getFilePath().getPresentableUrl());
244 context
.processStatus(copiedFile
.getFilePath(), copiedStatus
);
248 private SvnContentRevision
createBeforeRevision(final SvnChangedFile changedFile
, final boolean forDeleted
) {
249 return SvnContentRevision
.create(myVcs
,
250 forDeleted ? FilePathImpl
.createForDeletedFile(changedFile
.getStatus().getFile(), changedFile
.getFilePath().isDirectory()) :
251 changedFile
.getFilePath(), changedFile
.getStatus().getCommittedRevision());
254 private static File
guessWorkingCopyPath(final File file
, @NotNull final SVNURL url
, final String copyFromURL
) throws SVNException
{
255 String copiedPath
= url
.getPath();
256 String copyFromPath
= SVNURL
.parseURIEncoded(copyFromURL
).getPath();
257 String commonPathAncestor
= SVNPathUtil
.getCommonPathAncestor(copiedPath
, copyFromPath
);
258 int pathSegmentCount
= SVNPathUtil
.getSegmentsCount(copiedPath
);
259 int ancestorSegmentCount
= SVNPathUtil
.getSegmentsCount(commonPathAncestor
);
260 boolean startsWithSlash
= file
.getAbsolutePath().startsWith("/");
261 List
<String
> segments
= StringUtil
.split(file
.getPath(), File
.separator
);
262 List
<String
> copyFromPathSegments
= StringUtil
.split(copyFromPath
, "/");
263 List
<String
> resultSegments
= new ArrayList
<String
>();
264 final int keepSegments
= segments
.size() - pathSegmentCount
+ ancestorSegmentCount
;
265 for(int i
=0; i
< keepSegments
; i
++) {
266 resultSegments
.add(segments
.get(i
));
268 for(int i
=ancestorSegmentCount
; i
<copyFromPathSegments
.size(); i
++) {
269 resultSegments
.add(copyFromPathSegments
.get(i
));
272 String result
= StringUtil
.join(resultSegments
, "/");
273 if (startsWithSlash
) {
274 result
= "/" + result
;
276 return new File(result
);
279 public boolean isModifiedDocumentTrackingRequired() {
283 public void doCleanup(final List
<VirtualFile
> files
) {
284 new CleanupWorker(files
.toArray(new VirtualFile
[files
.size()]), myVcs
.getProject(), "action.Subversion.cleanup.progress.title").execute();