001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.wc;
013:
014: import java.io.File;
015: import java.io.OutputStream;
016: import java.util.Date;
017: import java.util.Iterator;
018: import java.util.Map;
019: import java.util.TreeMap;
020:
021: import org.tmatesoft.svn.core.SVNCommitInfo;
022: import org.tmatesoft.svn.core.SVNErrorCode;
023: import org.tmatesoft.svn.core.SVNException;
024: import org.tmatesoft.svn.core.SVNLock;
025: import org.tmatesoft.svn.core.SVNNodeKind;
026: import org.tmatesoft.svn.core.SVNProperty;
027: import org.tmatesoft.svn.core.SVNURL;
028: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
029: import org.tmatesoft.svn.core.internal.util.SVNTimeUtil;
030: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
031: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
032: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
033: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
034: import org.tmatesoft.svn.core.io.ISVNEditor;
035: import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
036: import org.tmatesoft.svn.core.wc.ISVNOptions;
037: import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
038: import org.tmatesoft.svn.core.wc.SVNRevision;
039: import org.tmatesoft.svn.core.wc.SVNStatus;
040: import org.tmatesoft.svn.core.wc.SVNStatusType;
041:
042: /**
043: * @version 1.1.1
044: * @author TMate Software Ltd.
045: */
046: public class SVNRemoteStatusEditor extends SVNStatusEditor implements
047: ISVNEditor, ISVNStatusHandler {
048:
049: private boolean myIsRootOpen;
050: private SVNStatus myAnchorStatus;
051:
052: private DirectoryInfo myDirectoryInfo;
053: private FileInfo myFileInfo;
054:
055: public SVNRemoteStatusEditor(ISVNOptions options,
056: SVNWCAccess wcAccess, SVNAdminAreaInfo info,
057: boolean noIgnore, boolean reportAll, boolean descend,
058: ISVNStatusHandler handler) throws SVNException {
059: super (options, wcAccess, info, noIgnore, reportAll, descend,
060: handler);
061: myAnchorStatus = createStatus(info.getAnchor().getRoot());
062: }
063:
064: public void openRoot(long revision) throws SVNException {
065: myIsRootOpen = true;
066: myDirectoryInfo = new DirectoryInfo(null, null);
067: }
068:
069: public void deleteEntry(String path, long revision)
070: throws SVNException {
071: File file = getAnchor().getFile(path);
072: SVNFileType type = SVNFileType.getType(file);
073: File dirPath;
074: String name;
075: if (type == SVNFileType.DIRECTORY) {
076: dirPath = file;
077: name = "";
078: } else {
079: dirPath = file.getParentFile();
080: name = file.getName();
081: }
082: SVNAdminArea dir = null;
083: try {
084: dir = getWCAccess().retrieve(dirPath);
085: } catch (SVNException e) {
086: if (type == SVNFileType.NONE
087: && e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
088: return;
089: }
090: throw e;
091: }
092: SVNEntry entry = dir.getEntry(name, false);
093: if (entry != null) {
094: tweakStatusHash(myDirectoryInfo, null, file,
095: SVNStatusType.STATUS_DELETED,
096: SVNStatusType.STATUS_NONE, null);
097: }
098: if (myDirectoryInfo.myParent != null && !hasTarget()) {
099: tweakStatusHash(myDirectoryInfo.myParent, myDirectoryInfo,
100: myDirectoryInfo.myPath,
101: SVNStatusType.STATUS_MODIFIED,
102: SVNStatusType.STATUS_NONE, null);
103: } else if (!hasTarget() && myDirectoryInfo.myParent == null) {
104: myDirectoryInfo.myIsContentsChanged = true;
105: }
106: }
107:
108: public void addDir(String path, String copyFromPath,
109: long copyFromRevision) throws SVNException {
110: myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
111: myDirectoryInfo.myIsAdded = true;
112: myDirectoryInfo.myParent.myIsContentsChanged = true;
113: }
114:
115: public void openDir(String path, long revision) throws SVNException {
116: myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
117: }
118:
119: public void changeDirProperty(String name, String value)
120: throws SVNException {
121: if (name != null
122: && !name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
123: && !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
124: myDirectoryInfo.myIsPropertiesChanged = true;
125: }
126: if (SVNProperty.COMMITTED_REVISION.equals(name)
127: && value != null) {
128: try {
129: long number = Long.parseLong(value);
130: myDirectoryInfo.myRemoteRevision = SVNRevision
131: .create(number);
132: } catch (NumberFormatException nfe) {
133: myDirectoryInfo.myRemoteRevision = SVNRevision.UNDEFINED;
134: }
135: } else if (SVNProperty.COMMITTED_DATE.equals(name)
136: && value != null) {
137: myDirectoryInfo.myRemoteDate = SVNTimeUtil.parseDate(value);
138: } else if (SVNProperty.LAST_AUTHOR.equals(name)) {
139: myDirectoryInfo.myRemoteAuthor = value;
140: }
141: }
142:
143: public void closeDir() throws SVNException {
144: DirectoryInfo parent = myDirectoryInfo.myParent;
145: if (myDirectoryInfo.myIsAdded
146: || myDirectoryInfo.myIsPropertiesChanged
147: || myDirectoryInfo.myIsContentsChanged) {
148: SVNStatusType contentsStatus;
149: SVNStatusType propertiesStatus;
150: if (myDirectoryInfo.myIsAdded) {
151: contentsStatus = SVNStatusType.STATUS_ADDED;
152: propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED
153: : SVNStatusType.STATUS_NONE;
154: } else {
155: contentsStatus = myDirectoryInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED
156: : SVNStatusType.STATUS_NONE;
157: propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED
158: : SVNStatusType.STATUS_NONE;
159: }
160: if (parent != null) {
161: tweakStatusHash(parent, myDirectoryInfo,
162: myDirectoryInfo.myPath, contentsStatus,
163: propertiesStatus, null);
164: }
165: }
166: if (parent != null && isDescend()) {
167: boolean wasDeleted = false;
168: SVNStatus dirStatus = (SVNStatus) parent.myChildrenStatuses
169: .get(myDirectoryInfo.myPath);
170: if (dirStatus != null
171: && (dirStatus.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED || dirStatus
172: .getRemoteContentsStatus() == SVNStatusType.STATUS_REPLACED)) {
173: wasDeleted = true;
174: }
175: handleStatusHash(dirStatus != null ? dirStatus.getEntry()
176: : null, myDirectoryInfo.myChildrenStatuses,
177: wasDeleted, true);
178: if (isSendableStatus(dirStatus)) {
179: getDefaultHandler().handleStatus(dirStatus);
180: }
181: parent.myChildrenStatuses.remove(myDirectoryInfo.myPath);
182: } else if (parent == null) {
183: if (hasTarget()) {
184: File targetPath = getAnchor().getFile(
185: getAdminAreaInfo().getTargetName());
186: SVNStatus tgtStatus = (SVNStatus) myDirectoryInfo.myChildrenStatuses
187: .get(targetPath);
188: if (tgtStatus != null) {
189: if (tgtStatus.getKind() == SVNNodeKind.DIR) {
190: SVNAdminArea dir = getWCAccess().retrieve(
191: targetPath);
192: getDirStatus(null, dir, null, isDescend(),
193: isReportAll(), isNoIgnore(), null,
194: true, getDefaultHandler());
195: }
196: if (isSendableStatus(tgtStatus)) {
197: getDefaultHandler().handleStatus(tgtStatus);
198: }
199: }
200: } else {
201: handleStatusHash(myAnchorStatus.getEntry(),
202: myDirectoryInfo.myChildrenStatuses, false,
203: isDescend());
204: if (myDirectoryInfo != null
205: && myDirectoryInfo.myParent == null) {
206: tweakAnchorStatus(myDirectoryInfo);
207: }
208: if (isSendableStatus(myAnchorStatus)) {
209: getDefaultHandler().handleStatus(myAnchorStatus);
210: }
211: }
212: }
213: myDirectoryInfo = myDirectoryInfo.myParent;
214: }
215:
216: public void addFile(String path, String copyFromPath,
217: long copyFromRevision) throws SVNException {
218: myFileInfo = new FileInfo(myDirectoryInfo, path, true);
219: myDirectoryInfo.myIsContentsChanged = true;
220: }
221:
222: public void openFile(String path, long revision)
223: throws SVNException {
224: myFileInfo = new FileInfo(myDirectoryInfo, path, false);
225: }
226:
227: public void changeFileProperty(String path, String name,
228: String value) throws SVNException {
229: if (name != null
230: && !name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
231: && !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
232: myFileInfo.myIsPropertiesChanged = true;
233: }
234: if (SVNProperty.COMMITTED_REVISION.equals(name)
235: && value != null) {
236: try {
237: long number = Long.parseLong(value);
238: myFileInfo.myRemoteRevision = SVNRevision
239: .create(number);
240: } catch (NumberFormatException nfe) {
241: myFileInfo.myRemoteRevision = SVNRevision.UNDEFINED;
242: }
243: } else if (SVNProperty.COMMITTED_DATE.equals(name)
244: && value != null) {
245: myFileInfo.myRemoteDate = SVNTimeUtil.parseDate(value);
246: } else if (SVNProperty.LAST_AUTHOR.equals(name)) {
247: myFileInfo.myRemoteAuthor = value;
248: }
249: }
250:
251: public void applyTextDelta(String path, String baseChecksum)
252: throws SVNException {
253: myFileInfo.myIsContentsChanged = true;
254: }
255:
256: public void closeFile(String path, String textChecksum)
257: throws SVNException {
258: if (!(myFileInfo.myIsAdded || myFileInfo.myIsPropertiesChanged || myFileInfo.myIsContentsChanged)) {
259: return;
260: }
261: SVNStatusType contentsStatus;
262: SVNStatusType propertiesStatus;
263: SVNLock remoteLock = null;
264:
265: if (myFileInfo.myIsAdded) {
266: contentsStatus = SVNStatusType.STATUS_ADDED;
267: propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED
268: : SVNStatusType.STATUS_NONE;
269: } else {
270: contentsStatus = myFileInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED
271: : SVNStatusType.STATUS_NONE;
272: propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED
273: : SVNStatusType.STATUS_NONE;
274: remoteLock = getLock(myFileInfo.myURL);
275: }
276: tweakStatusHash(myFileInfo, myFileInfo.myPath, contentsStatus,
277: propertiesStatus, remoteLock);
278: myFileInfo = null;
279: }
280:
281: public OutputStream textDeltaChunk(String path,
282: SVNDiffWindow diffWindow) throws SVNException {
283: return null;
284: }
285:
286: public void textDeltaEnd(String path) throws SVNException {
287: }
288:
289: public SVNCommitInfo closeEdit() throws SVNException {
290: if (myIsRootOpen) {
291: cleanup();
292: } else {
293: super .closeEdit();
294: }
295: return new SVNCommitInfo(getTargetRevision(), null, null);
296: }
297:
298: public void abortEdit() throws SVNException {
299: }
300:
301: public void absentDir(String path) throws SVNException {
302: }
303:
304: public void absentFile(String path) throws SVNException {
305: }
306:
307: private void handleStatusHash(SVNEntry dirEntry, Map hash,
308: boolean deleted, boolean descend) throws SVNException {
309: ISVNStatusHandler handler = deleted ? this
310: : getDefaultHandler();
311: for (Iterator paths = hash.keySet().iterator(); paths.hasNext();) {
312: File path = (File) paths.next();
313: SVNStatus status = (SVNStatus) hash.get(path);
314:
315: if (getWCAccess().isMissing(path)) {
316: status.setContentsStatus(SVNStatusType.STATUS_MISSING);
317: } else if (descend && status.getEntry() != null
318: && status.getKind() == SVNNodeKind.DIR) {
319: SVNAdminArea dir = getWCAccess().retrieve(path);
320: getDirStatus(dirEntry, dir, null, true, isReportAll(),
321: isNoIgnore(), null, true, handler);
322: }
323: if (deleted) {
324: status.setRemoteStatus(SVNStatusType.STATUS_DELETED,
325: null, null, null);
326: }
327: if (isSendableStatus(status)) {
328: handler.handleStatus(status);
329: }
330: }
331: }
332:
333: private void tweakStatusHash(FileInfo fileInfo, File path,
334: SVNStatusType text, SVNStatusType props, SVNLock lock)
335: throws SVNException {
336: Map hash = fileInfo.myParent.myChildrenStatuses;
337: SVNStatus status = (SVNStatus) hash.get(fileInfo.myPath);
338: if (status == null) {
339: if (text != SVNStatusType.STATUS_ADDED) {
340: return;
341: }
342: status = createStatus(path);
343: hash.put(fileInfo.myPath, status);
344: }
345: if (text == SVNStatusType.STATUS_ADDED
346: && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
347: text = SVNStatusType.STATUS_REPLACED;
348: }
349: status.setRemoteStatus(fileInfo.myURL, text, props, lock,
350: fileInfo.myRemoteKind, fileInfo.myRemoteRevision,
351: fileInfo.myRemoteDate, fileInfo.myRemoteAuthor);
352: }
353:
354: private void tweakStatusHash(DirectoryInfo dirInfo,
355: DirectoryInfo childDir, File path, SVNStatusType text,
356: SVNStatusType props, SVNLock lock) throws SVNException {
357: Map hash = dirInfo.myChildrenStatuses;
358: SVNStatus status = (SVNStatus) hash.get(path);
359: if (status == null) {
360: if (text != SVNStatusType.STATUS_ADDED) {
361: return;
362: }
363: status = createStatus(path);
364: hash.put(path, status);
365: }
366: if (text == SVNStatusType.STATUS_ADDED
367: && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
368: text = SVNStatusType.STATUS_REPLACED;
369: }
370: if (text == SVNStatusType.STATUS_DELETED) {
371: // remote kind is NONE because entry is deleted in repository.
372: status.setRemoteStatus(dirInfo.myURL, text, props, lock,
373: SVNNodeKind.NONE, null, null, null);
374: } else if (childDir == null) {
375: status.setRemoteStatus(dirInfo.myURL, text, props, lock,
376: dirInfo.myRemoteKind, dirInfo.myRemoteRevision,
377: dirInfo.myRemoteDate, dirInfo.myRemoteAuthor);
378: } else {
379: status.setRemoteStatus(childDir.myURL, text, props, lock,
380: childDir.myRemoteKind, childDir.myRemoteRevision,
381: childDir.myRemoteDate, childDir.myRemoteAuthor);
382: }
383: }
384:
385: private void tweakAnchorStatus(DirectoryInfo anchorInfo) {
386: if (anchorInfo != null
387: && (anchorInfo.myIsContentsChanged || anchorInfo.myIsPropertiesChanged)) {
388: SVNStatusType text = anchorInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED
389: : SVNStatusType.STATUS_NONE;
390: SVNStatusType props = anchorInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED
391: : SVNStatusType.STATUS_NONE;
392: myAnchorStatus.setRemoteStatus(myDirectoryInfo.myURL, text,
393: props, null, SVNNodeKind.DIR,
394: myDirectoryInfo.myRemoteRevision,
395: myDirectoryInfo.myRemoteDate,
396: myDirectoryInfo.myRemoteAuthor);
397: }
398: }
399:
400: private boolean isSendableStatus(SVNStatus status) {
401: if (status == null) {
402: return false;
403: }
404: if (status.getRemoteContentsStatus() != SVNStatusType.STATUS_NONE) {
405: return true;
406: }
407: if (status.getRemotePropertiesStatus() != SVNStatusType.STATUS_NONE) {
408: return true;
409: }
410: if (status.getRemoteLock() != null) {
411: return true;
412: }
413: if (status.getContentsStatus() == SVNStatusType.STATUS_IGNORED
414: && !isNoIgnore()) {
415: return false;
416: }
417: if (isReportAll()) {
418: return true;
419: }
420: if (status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED) {
421: return true;
422: }
423: if (status.getContentsStatus() != SVNStatusType.STATUS_NONE
424: && status.getContentsStatus() != SVNStatusType.STATUS_NORMAL) {
425: return true;
426: }
427: if (status.getPropertiesStatus() != SVNStatusType.STATUS_NONE
428: && status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL) {
429: return true;
430: }
431: return status.isLocked() || status.isSwitched()
432: || status.getLocalLock() != null;
433: }
434:
435: private SVNStatus createStatus(File path) throws SVNException {
436: SVNEntry entry = getWCAccess().getEntry(path, false);
437: SVNEntry parentEntry = null;
438: if (entry != null) {
439: SVNAdminArea parentDir = getWCAccess().getAdminArea(
440: path.getParentFile());
441: if (parentDir != null) {
442: parentEntry = getWCAccess().getEntry(
443: path.getParentFile(), false);
444: }
445: }
446: return assembleStatus(path, entry != null ? getWCAccess()
447: .probeRetrieve(path) : null, entry, parentEntry,
448: SVNNodeKind.UNKNOWN, false, true, false);
449: }
450:
451: public void handleStatus(SVNStatus status) throws SVNException {
452: status.setContentsStatus(SVNStatusType.STATUS_DELETED);
453: getDefaultHandler().handleStatus(status);
454: }
455:
456: private class DirectoryInfo implements ISVNStatusHandler {
457:
458: public DirectoryInfo(String path, DirectoryInfo parent)
459: throws SVNException {
460: myParent = parent;
461: if (myParent != null) {
462: myPath = getAnchor().getFile(path);
463: } else {
464: myPath = getAnchor().getRoot();
465: }
466: myName = path != null ? SVNPathUtil.tail(path) : null;
467: myChildrenStatuses = new TreeMap();
468: myURL = computeURL();
469: myRemoteRevision = SVNRevision.UNDEFINED;
470: myRemoteKind = SVNNodeKind.DIR;
471:
472: // this dir's status in parent.
473: SVNStatus parentStatus = null;
474: if (myParent != null) {
475: parentStatus = (SVNStatus) myParent.myChildrenStatuses
476: .get(myPath);
477: } else {
478: parentStatus = myAnchorStatus;
479: }
480: if (parentStatus != null) {
481: SVNStatusType textStatus = parentStatus
482: .getContentsStatus();
483: if (textStatus != SVNStatusType.STATUS_UNVERSIONED
484: && textStatus != SVNStatusType.STATUS_DELETED
485: && textStatus != SVNStatusType.STATUS_MISSING
486: && textStatus != SVNStatusType.STATUS_OBSTRUCTED
487: && textStatus != SVNStatusType.STATUS_EXTERNAL
488: && textStatus != SVNStatusType.STATUS_IGNORED
489: && parentStatus.getKind() == SVNNodeKind.DIR
490: && (isDescend() || myParent == null)) {
491: SVNAdminArea dir = getWCAccess().retrieve(myPath);
492: getDirStatus(null, dir, null, false, true, true,
493: null, true, this );
494: }
495: }
496: }
497:
498: private SVNURL computeURL() throws SVNException {
499: if (myURL != null) {
500: return myURL;
501: }
502: if (myName == null) {
503: return myAnchorStatus.getURL();
504: }
505: SVNStatus status = (SVNStatus) myParent.myChildrenStatuses
506: .get(myPath);
507: if (status != null && status.getEntry() != null
508: && status.getEntry().getSVNURL() != null) {
509: return status.getEntry().getSVNURL();
510: }
511: SVNURL url = myParent.computeURL();
512: return url != null ? url.appendPath(myName, false) : null;
513: }
514:
515: public void handleStatus(SVNStatus status) throws SVNException {
516: myChildrenStatuses.put(status.getFile(), status);
517: }
518:
519: public File myPath;
520: public String myName;
521: public SVNURL myURL;
522: public DirectoryInfo myParent;
523:
524: public SVNRevision myRemoteRevision;
525: public Date myRemoteDate;
526: public String myRemoteAuthor;
527: public SVNNodeKind myRemoteKind;
528:
529: public boolean myIsAdded;
530: public boolean myIsPropertiesChanged;
531: public boolean myIsContentsChanged;
532:
533: public Map myChildrenStatuses;
534: }
535:
536: private class FileInfo {
537:
538: public FileInfo(DirectoryInfo parent, String path, boolean added)
539: throws SVNException {
540: myPath = getAnchor().getFile(path);
541: myName = myPath.getName();
542: myParent = parent;
543: myURL = myParent.computeURL().appendPath(myName, false);
544:
545: myRemoteRevision = SVNRevision.UNDEFINED;
546: myRemoteKind = SVNNodeKind.FILE;
547:
548: myIsAdded = added;
549: }
550:
551: public DirectoryInfo myParent;
552: public File myPath;
553: public String myName;
554: public SVNURL myURL;
555:
556: public boolean myIsAdded;
557: public boolean myIsContentsChanged;
558: public boolean myIsPropertiesChanged;
559:
560: public SVNRevision myRemoteRevision;
561: public Date myRemoteDate;
562: public String myRemoteAuthor;
563: public SVNNodeKind myRemoteKind;
564: }
565: }
|