VCS: allow Git to report changed on server files comparing the whole tree. !! require...
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnChangeProvider.java
blobbbf09a98d9d1c6e053c63720b6a67441fc37d49a
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;
29 import java.io.File;
30 import java.util.*;
32 /**
33 * @author max
34 * @author yole
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) {
45 myVcs = 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);
53 zipper.run();
55 final Map<String, SvnScopeZipper.MyDirNonRecursive> nonRecursiveMap = zipper.getNonRecursiveDirs();
56 final ISVNStatusFileProvider fileProvider = createFileProvider(nonRecursiveMap);
58 try {
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());
83 }*/
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);
134 @Nullable
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
143 return null;
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)) {
158 return;
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) {
176 continue;
178 final SVNURL childUrl = childStatus.getURL();
179 if (childUrl == null) {
180 continue;
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);
195 foundRename = true;
196 break;
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);
209 SVNStatus status;
210 try {
211 status = context.getClient().doStatus(wcPath, false);
213 catch(SVNException ex) {
214 status = null;
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),
221 SvnVcs.getKey());
222 foundRename = true;
226 if (!foundRename) {
227 // for debug
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() {
265 return true;
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();