/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java

https://bitbucket.org/nbargnesi/idea · Java · 332 lines · 260 code · 34 blank · 38 comment · 47 complexity · 3ce75533b965115964ec3b9f4abc2976 MD5 · raw file

  1. /*
  2. * Copyright 2000-2009 JetBrains s.r.o.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. package org.jetbrains.idea.svn;
  17. import com.intellij.openapi.application.ApplicationManager;
  18. import com.intellij.openapi.diagnostic.Logger;
  19. import com.intellij.openapi.editor.Document;
  20. import com.intellij.openapi.fileEditor.FileDocumentManager;
  21. import com.intellij.openapi.progress.ProcessCanceledException;
  22. import com.intellij.openapi.progress.ProgressIndicator;
  23. import com.intellij.openapi.progress.ProgressManager;
  24. import com.intellij.openapi.util.Comparing;
  25. import com.intellij.openapi.util.Computable;
  26. import com.intellij.openapi.util.text.StringUtil;
  27. import com.intellij.openapi.vcs.*;
  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.jetbrains.idea.svn.portable.SvnExceptionWrapper;
  37. import org.tmatesoft.svn.core.SVNDepth;
  38. import org.tmatesoft.svn.core.SVNException;
  39. import org.tmatesoft.svn.core.SVNNodeKind;
  40. import org.tmatesoft.svn.core.SVNURL;
  41. import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
  42. import org.tmatesoft.svn.core.wc.ISVNStatusFileProvider;
  43. import org.tmatesoft.svn.core.wc.SVNStatus;
  44. import org.tmatesoft.svn.core.wc.SVNStatusType;
  45. import java.io.File;
  46. import java.util.*;
  47. /**
  48. * @author max
  49. * @author yole
  50. */
  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");
  54. public static final String PROPERTY_LAYER = "Property";
  55. private final SvnVcs myVcs;
  56. private final VcsContextFactory myFactory;
  57. private final SvnFileUrlMappingImpl mySvnFileUrlMapping;
  58. public SvnChangeProvider(final SvnVcs vcs) {
  59. myVcs = vcs;
  60. myFactory = VcsContextFactory.SERVICE.getInstance();
  61. mySvnFileUrlMapping = (SvnFileUrlMappingImpl) vcs.getSvnFileUrlMapping();
  62. }
  63. public void getChanges(final VcsDirtyScope dirtyScope, final ChangelistBuilder builder, ProgressIndicator progress,
  64. final ChangeListManagerGate addGate) throws VcsException {
  65. final SvnScopeZipper zipper = new SvnScopeZipper(dirtyScope);
  66. zipper.run();
  67. final Map<String, SvnScopeZipper.MyDirNonRecursive> nonRecursiveMap = zipper.getNonRecursiveDirs();
  68. final ISVNStatusFileProvider fileProvider = createFileProvider(nonRecursiveMap);
  69. try {
  70. final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, progress);
  71. final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, progress);
  72. final NestedCopiesBuilder nestedCopiesBuilder = new NestedCopiesBuilder();
  73. final EventDispatcher<StatusReceiver> statusReceiver = EventDispatcher.create(StatusReceiver.class);
  74. statusReceiver.addListener(context);
  75. statusReceiver.addListener(nestedCopiesBuilder);
  76. final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs.getProject(), statusReceiver.getMulticaster(), partner);
  77. for (FilePath path : zipper.getRecursiveDirs()) {
  78. walker.go(path, SVNDepth.INFINITY);
  79. }
  80. partner.setFileProvider(fileProvider);
  81. for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
  82. walker.go(item.getDir(), SVNDepth.FILES);
  83. }
  84. // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
  85. /*for (FilePath path : zipper.getSingleFiles()) {
  86. FileStatus status = getParentStatus(context, path);
  87. processFile(path, context, status, false, context.getClient());
  88. }*/
  89. processCopiedAndDeleted(context, dirtyScope);
  90. processUnsaved(dirtyScope, addGate, context);
  91. mySvnFileUrlMapping.acceptNestedData(nestedCopiesBuilder.getSet());
  92. } catch (SvnExceptionWrapper e) {
  93. LOG.info(e);
  94. throw new VcsException(e.getCause());
  95. } catch (SVNException e) {
  96. if (e.getCause() != null) {
  97. throw new VcsException(e.getMessage() + e.getCause().getMessage(), e);
  98. }
  99. throw new VcsException(e);
  100. }
  101. }
  102. private void processUnsaved(VcsDirtyScope dirtyScope, ChangeListManagerGate addGate, SvnChangeProviderContext context)
  103. throws SVNException {
  104. final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
  105. final Document[] unsavedDocuments = fileDocumentManager.getUnsavedDocuments();
  106. for (Document unsavedDocument : unsavedDocuments) {
  107. final VirtualFile file = fileDocumentManager.getFile(unsavedDocument);
  108. if (file != null && dirtyScope.belongsTo(new FilePathImpl(file)) && fileDocumentManager.isFileModified(file)) {
  109. final FileStatus status = addGate.getStatus(file);
  110. if (status == null || FileStatus.NOT_CHANGED.equals(status)) {
  111. context.addModifiedNotSavedChange(file);
  112. }
  113. }
  114. }
  115. }
  116. private ISVNStatusFileProvider createFileProvider(Map<String, SvnScopeZipper.MyDirNonRecursive> nonRecursiveMap) {
  117. // translate into terms of File.getAbsolutePath()
  118. final Map<String, Map> preparedMap = new HashMap<String, Map>();
  119. for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
  120. final Map result = new HashMap();
  121. for (FilePath path : item.getChildrenList()) {
  122. result.put(path.getName(), path.getIOFile());
  123. }
  124. preparedMap.put(item.getDir().getIOFile().getAbsolutePath(), result);
  125. }
  126. return new ISVNStatusFileProvider() {
  127. public Map getChildrenFiles(File parent) {
  128. return preparedMap.get(parent.getAbsolutePath());
  129. }
  130. };
  131. }
  132. private void processCopiedAndDeleted(final SvnChangeProviderContext context, final VcsDirtyScope dirtyScope) throws SVNException {
  133. for(SvnChangedFile copiedFile: context.getCopiedFiles()) {
  134. if (context.isCanceled()) {
  135. throw new ProcessCanceledException();
  136. }
  137. processCopiedFile(copiedFile, context.getBuilder(), context, dirtyScope);
  138. }
  139. for(SvnChangedFile deletedFile: context.getDeletedFiles()) {
  140. if (context.isCanceled()) {
  141. throw new ProcessCanceledException();
  142. }
  143. context.processStatus(deletedFile.getFilePath(), deletedFile.getStatus());
  144. }
  145. }
  146. public void getChanges(final FilePath path, final boolean recursive, final ChangelistBuilder builder) throws SVNException {
  147. final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, null);
  148. final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, ProgressManager.getInstance().getProgressIndicator());
  149. final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(myVcs.getProject(), context, partner);
  150. walker.go(path, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES);
  151. processCopiedAndDeleted(context, null);
  152. }
  153. @Nullable
  154. private String changeListNameFromStatus(final SVNStatus status) {
  155. if (WorkingCopyFormat.getInstance(status.getWorkingCopyFormat()).supportsChangelists()) {
  156. if (SVNNodeKind.FILE.equals(status.getKind())) {
  157. final String clName = status.getChangelistName();
  158. return (clName == null) ? null : clName;
  159. }
  160. }
  161. // always null for earlier versions
  162. return null;
  163. }
  164. private void processCopiedFile(SvnChangedFile copiedFile,
  165. ChangelistBuilder builder,
  166. SvnChangeProviderContext context, final VcsDirtyScope dirtyScope) throws SVNException {
  167. boolean foundRename = false;
  168. final SVNStatus copiedStatus = copiedFile.getStatus();
  169. final String copyFromURL = copiedFile.getCopyFromURL();
  170. final FilePath copiedToPath = copiedFile.getFilePath();
  171. // if copy target is _deleted_, treat like deleted, not moved!
  172. /*for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
  173. final SvnChangedFile deletedFile = iterator.next();
  174. final FilePath deletedPath = deletedFile.getFilePath();
  175. if (Comparing.equal(deletedPath, copiedToPath)) {
  176. return;
  177. }
  178. }*/
  179. 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. final Change newChange = context.createMovedChange(createBeforeRevision(deletedFile, true),
  186. CurrentContentRevision.create(copiedFile.getFilePath()), copiedStatus,
  187. deletedStatus);
  188. applyMovedChange(copiedFile.getFilePath(), builder, dirtyScope, deletedToDelete, deletedFile, clName, newChange);
  189. for(Iterator<SvnChangedFile> iterChild = context.getDeletedFiles().iterator(); iterChild.hasNext();) {
  190. SvnChangedFile deletedChild = iterChild.next();
  191. final SVNStatus childStatus = deletedChild.getStatus();
  192. if (childStatus == null) {
  193. continue;
  194. }
  195. final SVNURL childUrl = childStatus.getURL();
  196. if (childUrl == null) {
  197. continue;
  198. }
  199. final String childURL = childUrl.toDecodedString();
  200. if (StringUtil.startsWithConcatenationOf(childURL, copyFromURL, "/")) {
  201. String relativePath = childURL.substring(copyFromURL.length());
  202. File newPath = new File(copiedFile.getFilePath().getIOFile(), relativePath);
  203. FilePath newFilePath = myFactory.createFilePathOn(newPath);
  204. if (!context.isDeleted(newFilePath)) {
  205. final Change movedChange = context.createMovedChange(createBeforeRevision(deletedChild, true),
  206. CurrentContentRevision.create(newFilePath),
  207. context.getTreeConflictStatus(newPath), childStatus);
  208. applyMovedChange(newFilePath, builder, dirtyScope, deletedToDelete, deletedChild, clName, movedChange);
  209. }
  210. }
  211. }
  212. foundRename = true;
  213. break;
  214. }
  215. }
  216. final List<SvnChangedFile> deletedFiles = context.getDeletedFiles();
  217. for (SvnChangedFile file : deletedToDelete) {
  218. deletedFiles.remove(file);
  219. }
  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);
  224. SVNStatus status;
  225. try {
  226. status = context.getClient().doStatus(wcPath, false);
  227. }
  228. catch(SVNException ex) {
  229. status = null;
  230. }
  231. if (status != null && SvnVcs.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
  232. final FilePath filePath = myFactory.createFilePathOnDeleted(wcPath, false);
  233. final SvnContentRevision beforeRevision = SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision());
  234. final ContentRevision afterRevision = CurrentContentRevision.create(copiedFile.getFilePath());
  235. builder.processChangeInList(context.createMovedChange(beforeRevision, afterRevision, copiedStatus, status), changeListNameFromStatus(status),
  236. SvnVcs.getKey());
  237. foundRename = true;
  238. }
  239. }
  240. if (!foundRename) {
  241. // for debug
  242. LOG.info("Rename not found for " + copiedFile.getFilePath().getPresentableUrl());
  243. context.processStatus(copiedFile.getFilePath(), copiedStatus);
  244. }
  245. }
  246. private void applyMovedChange(final FilePath oldPath,
  247. ChangelistBuilder builder,
  248. final VcsDirtyScope dirtyScope,
  249. Set<SvnChangedFile> deletedToDelete, SvnChangedFile deletedFile, String clName, final Change newChange) {
  250. final boolean isUnder = dirtyScope == null ? true : ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
  251. @Override
  252. public Boolean compute() {
  253. return ChangeListManagerImpl.isUnder(newChange, dirtyScope);
  254. }
  255. });
  256. if (isUnder) {
  257. builder.removeRegisteredChangeFor(oldPath);
  258. builder.processChangeInList(newChange, clName, SvnVcs.getKey());
  259. deletedToDelete.add(deletedFile);
  260. }
  261. }
  262. private SvnContentRevision createBeforeRevision(final SvnChangedFile changedFile, final boolean forDeleted) {
  263. return SvnContentRevision.createBaseRevision(myVcs,
  264. forDeleted ? FilePathImpl.createForDeletedFile(changedFile.getStatus().getFile(),
  265. changedFile.getFilePath().isDirectory()) :
  266. changedFile.getFilePath(), changedFile.getStatus().getRevision());
  267. }
  268. private static File guessWorkingCopyPath(final File file, @NotNull final SVNURL url, final String copyFromURL) throws SVNException {
  269. String copiedPath = url.getPath();
  270. String copyFromPath = SVNURL.parseURIEncoded(copyFromURL).getPath();
  271. String commonPathAncestor = SVNPathUtil.getCommonPathAncestor(copiedPath, copyFromPath);
  272. int pathSegmentCount = SVNPathUtil.getSegmentsCount(copiedPath);
  273. int ancestorSegmentCount = SVNPathUtil.getSegmentsCount(commonPathAncestor);
  274. boolean startsWithSlash = file.getAbsolutePath().startsWith("/");
  275. List<String> segments = StringUtil.split(file.getPath(), File.separator);
  276. List<String> copyFromPathSegments = StringUtil.split(copyFromPath, "/");
  277. List<String> resultSegments = new ArrayList<String>();
  278. final int keepSegments = segments.size() - pathSegmentCount + ancestorSegmentCount;
  279. for(int i=0; i< keepSegments; i++) {
  280. resultSegments.add(segments.get(i));
  281. }
  282. for(int i=ancestorSegmentCount; i<copyFromPathSegments.size(); i++) {
  283. resultSegments.add(copyFromPathSegments.get(i));
  284. }
  285. String result = StringUtil.join(resultSegments, "/");
  286. if (startsWithSlash) {
  287. result = "/" + result;
  288. }
  289. return new File(result);
  290. }
  291. public boolean isModifiedDocumentTrackingRequired() {
  292. return true;
  293. }
  294. public void doCleanup(final List<VirtualFile> files) {
  295. new CleanupWorker(VfsUtil.toVirtualFileArray(files), myVcs.getProject(), "action.Subversion.cleanup.progress.title").execute();
  296. }
  297. }