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
.VfsUtil
;
31 import com
.intellij
.openapi
.vfs
.VirtualFile
;
32 import com
.intellij
.util
.EventDispatcher
;
33 import org
.jetbrains
.annotations
.NotNull
;
34 import org
.jetbrains
.annotations
.Nullable
;
35 import org
.jetbrains
.idea
.svn
.actions
.CleanupWorker
;
36 import org
.tmatesoft
.svn
.core
.SVNDepth
;
37 import org
.tmatesoft
.svn
.core
.SVNException
;
38 import org
.tmatesoft
.svn
.core
.SVNNodeKind
;
39 import org
.tmatesoft
.svn
.core
.SVNURL
;
40 import org
.tmatesoft
.svn
.core
.internal
.util
.SVNPathUtil
;
41 import org
.tmatesoft
.svn
.core
.wc
.ISVNStatusFileProvider
;
42 import org
.tmatesoft
.svn
.core
.wc
.SVNStatus
;
43 import org
.tmatesoft
.svn
.core
.wc
.SVNStatusType
;
52 public class SvnChangeProvider
implements ChangeProvider
{
53 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.SvnChangeProvider");
54 public static final String ourDefaultListName
= VcsBundle
.message("changes.default.changlist.name");
56 private final SvnVcs myVcs
;
57 private final VcsContextFactory myFactory
;
58 private SvnFileUrlMappingImpl mySvnFileUrlMapping
;
60 public SvnChangeProvider(final SvnVcs vcs
) {
62 myFactory
= VcsContextFactory
.SERVICE
.getInstance();
63 mySvnFileUrlMapping
= (SvnFileUrlMappingImpl
) vcs
.getSvnFileUrlMapping();
66 public void getChanges(final VcsDirtyScope dirtyScope
, final ChangelistBuilder builder
, ProgressIndicator progress
,
67 final ChangeListManagerGate addGate
) throws VcsException
{
68 final SvnScopeZipper zipper
= new SvnScopeZipper(dirtyScope
);
71 final Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
= zipper
.getNonRecursiveDirs();
72 final ISVNStatusFileProvider fileProvider
= createFileProvider(nonRecursiveMap
);
75 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, progress
);
77 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, progress
);
78 final NestedCopiesBuilder nestedCopiesBuilder
= new NestedCopiesBuilder();
80 final EventDispatcher
<StatusReceiver
> statusReceiver
= EventDispatcher
.create(StatusReceiver
.class);
81 statusReceiver
.addListener(context
);
82 statusReceiver
.addListener(nestedCopiesBuilder
);
84 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(statusReceiver
.getMulticaster(), partner
);
86 for (FilePath path
: zipper
.getRecursiveDirs()) {
87 walker
.go(path
, SVNDepth
.INFINITY
);
90 partner
.setFileProvider(fileProvider
);
91 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
92 walker
.go(item
.getDir(), SVNDepth
.FILES
);
95 // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
96 /*for (FilePath path : zipper.getSingleFiles()) {
97 FileStatus status = getParentStatus(context, path);
98 processFile(path, context, status, false, context.getClient());
101 processCopiedAndDeleted(context
);
103 mySvnFileUrlMapping
.acceptNestedData(nestedCopiesBuilder
.getSet());
105 catch (SVNException e
) {
106 throw new VcsException(e
);
110 private ISVNStatusFileProvider
createFileProvider(Map
<String
, SvnScopeZipper
.MyDirNonRecursive
> nonRecursiveMap
) {
111 // translate into terms of File.getAbsolutePath()
112 final Map
<String
, Map
> preparedMap
= new HashMap
<String
, Map
>();
113 for (SvnScopeZipper
.MyDirNonRecursive item
: nonRecursiveMap
.values()) {
114 final Map result
= new HashMap();
115 for (FilePath path
: item
.getChildrenList()) {
116 result
.put(path
.getName(), path
.getIOFile());
118 preparedMap
.put(item
.getDir().getIOFile().getAbsolutePath(), result
);
120 return new ISVNStatusFileProvider() {
121 public Map
getChildrenFiles(File parent
) {
122 return preparedMap
.get(parent
.getAbsolutePath());
127 private void processCopiedAndDeleted(final SvnChangeProviderContext context
) throws SVNException
{
128 for(SvnChangedFile copiedFile
: context
.getCopiedFiles()) {
129 if (context
.isCanceled()) {
130 throw new ProcessCanceledException();
132 processCopiedFile(copiedFile
, context
.getBuilder(), context
);
134 for(SvnChangedFile deletedFile
: context
.getDeletedFiles()) {
135 if (context
.isCanceled()) {
136 throw new ProcessCanceledException();
138 context
.processStatus(deletedFile
.getFilePath(), deletedFile
.getStatus());
142 public void getChanges(final FilePath path
, final boolean recursive
, final ChangelistBuilder builder
) throws SVNException
{
143 final SvnChangeProviderContext context
= new SvnChangeProviderContext(myVcs
, builder
, null);
144 final StatusWalkerPartnerImpl partner
= new StatusWalkerPartnerImpl(myVcs
, ProgressManager
.getInstance().getProgressIndicator());
145 final SvnRecursiveStatusWalker walker
= new SvnRecursiveStatusWalker(context
, partner
);
146 walker
.go(path
, recursive ? SVNDepth
.INFINITY
: SVNDepth
.IMMEDIATES
);
147 processCopiedAndDeleted(context
);
151 private String
changeListNameFromStatus(final SVNStatus status
) {
152 if (WorkingCopyFormat
.getInstance(status
.getWorkingCopyFormat()).supportsChangelists()) {
153 if (SVNNodeKind
.FILE
.equals(status
.getKind())) {
154 final String clName
= status
.getChangelistName();
155 return (clName
== null) ?
null : clName
;
158 // always null for earlier versions
162 private void processCopiedFile(SvnChangedFile copiedFile
, ChangelistBuilder builder
, SvnChangeProviderContext context
) throws SVNException
{
163 boolean foundRename
= false;
164 final SVNStatus copiedStatus
= copiedFile
.getStatus();
165 final String copyFromURL
= copiedFile
.getCopyFromURL();
166 final FilePath copiedToPath
= copiedFile
.getFilePath();
168 // if copy target is _deleted_, treat like deleted, not moved!
169 /*for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
170 final SvnChangedFile deletedFile = iterator.next();
171 final FilePath deletedPath = deletedFile.getFilePath();
173 if (Comparing.equal(deletedPath, copiedToPath)) {
178 final Set
<SvnChangedFile
> deletedToDelete
= new HashSet
<SvnChangedFile
>();
180 for (Iterator
<SvnChangedFile
> iterator
= context
.getDeletedFiles().iterator(); iterator
.hasNext();) {
181 SvnChangedFile deletedFile
= iterator
.next();
182 final SVNStatus deletedStatus
= deletedFile
.getStatus();
183 if ((deletedStatus
!= null) && (deletedStatus
.getURL() != null) && Comparing
.equal(copyFromURL
, deletedStatus
.getURL().toString())) {
184 final String clName
= changeListNameFromStatus(copiedFile
.getStatus());
185 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedFile
, true),
186 CurrentContentRevision
.create(copiedFile
.getFilePath()), copiedStatus
, deletedStatus
), clName
, SvnVcs
.getKey());
187 deletedToDelete
.add(deletedFile
);
188 for(Iterator
<SvnChangedFile
> iterChild
= context
.getDeletedFiles().iterator(); iterChild
.hasNext();) {
189 SvnChangedFile deletedChild
= iterChild
.next();
190 final SVNStatus childStatus
= deletedChild
.getStatus();
191 if (childStatus
== null) {
194 final SVNURL childUrl
= childStatus
.getURL();
195 if (childUrl
== null) {
198 final String childURL
= childUrl
.toString();
199 if (StringUtil
.startsWithConcatenationOf(childURL
, copyFromURL
, "/")) {
200 String relativePath
= childURL
.substring(copyFromURL
.length());
201 File newPath
= new File(copiedFile
.getFilePath().getIOFile(), relativePath
);
202 FilePath newFilePath
= myFactory
.createFilePathOn(newPath
);
203 if (!context
.isDeleted(newFilePath
)) {
204 builder
.processChangeInList(context
.createMovedChange(createBeforeRevision(deletedChild
, true),
205 CurrentContentRevision
.create(newFilePath
),
206 context
.getTreeConflictStatus(newPath
), childStatus
), clName
, SvnVcs
.getKey());
207 deletedToDelete
.add(deletedChild
);
216 final List
<SvnChangedFile
> deletedFiles
= context
.getDeletedFiles();
217 for (SvnChangedFile file
: deletedToDelete
) {
218 deletedFiles
.remove(file
);
221 // handle the case when the deleted file wasn't included in the dirty scope - try searching for the local copy
222 // by building a relative url
223 if (!foundRename
&& copiedStatus
.getURL() != null) {
224 File wcPath
= guessWorkingCopyPath(copiedStatus
.getFile(), copiedStatus
.getURL(), copyFromURL
);
227 status
= context
.getClient().doStatus(wcPath
, false);
229 catch(SVNException ex
) {
232 if (status
!= null && status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
233 final FilePath filePath
= myFactory
.createFilePathOnDeleted(wcPath
, false);
234 final SvnContentRevision beforeRevision
= SvnContentRevision
.create(myVcs
, filePath
, status
.getCommittedRevision());
235 final ContentRevision afterRevision
= CurrentContentRevision
.create(copiedFile
.getFilePath());
236 builder
.processChangeInList(context
.createMovedChange(beforeRevision
, afterRevision
, copiedStatus
, status
), changeListNameFromStatus(status
),
244 LOG
.info("Rename not found for " + copiedFile
.getFilePath().getPresentableUrl());
245 context
.processStatus(copiedFile
.getFilePath(), copiedStatus
);
249 private SvnContentRevision
createBeforeRevision(final SvnChangedFile changedFile
, final boolean forDeleted
) {
250 return SvnContentRevision
.create(myVcs
,
251 forDeleted ? FilePathImpl
.createForDeletedFile(changedFile
.getStatus().getFile(), changedFile
.getFilePath().isDirectory()) :
252 changedFile
.getFilePath(), changedFile
.getStatus().getCommittedRevision());
255 private static File
guessWorkingCopyPath(final File file
, @NotNull final SVNURL url
, final String copyFromURL
) throws SVNException
{
256 String copiedPath
= url
.getPath();
257 String copyFromPath
= SVNURL
.parseURIEncoded(copyFromURL
).getPath();
258 String commonPathAncestor
= SVNPathUtil
.getCommonPathAncestor(copiedPath
, copyFromPath
);
259 int pathSegmentCount
= SVNPathUtil
.getSegmentsCount(copiedPath
);
260 int ancestorSegmentCount
= SVNPathUtil
.getSegmentsCount(commonPathAncestor
);
261 boolean startsWithSlash
= file
.getAbsolutePath().startsWith("/");
262 List
<String
> segments
= StringUtil
.split(file
.getPath(), File
.separator
);
263 List
<String
> copyFromPathSegments
= StringUtil
.split(copyFromPath
, "/");
264 List
<String
> resultSegments
= new ArrayList
<String
>();
265 final int keepSegments
= segments
.size() - pathSegmentCount
+ ancestorSegmentCount
;
266 for(int i
=0; i
< keepSegments
; i
++) {
267 resultSegments
.add(segments
.get(i
));
269 for(int i
=ancestorSegmentCount
; i
<copyFromPathSegments
.size(); i
++) {
270 resultSegments
.add(copyFromPathSegments
.get(i
));
273 String result
= StringUtil
.join(resultSegments
, "/");
274 if (startsWithSlash
) {
275 result
= "/" + result
;
277 return new File(result
);
280 public boolean isModifiedDocumentTrackingRequired() {
284 public void doCleanup(final List
<VirtualFile
> files
) {
285 new CleanupWorker(VfsUtil
.toVirtualFileArray(files
), myVcs
.getProject(), "action.Subversion.cleanup.progress.title").execute();