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.wc;
013:
014: import java.io.File;
015: import java.util.HashMap;
016: import java.util.Iterator;
017: import java.util.Map;
018:
019: import org.tmatesoft.svn.core.SVNCancelException;
020: import org.tmatesoft.svn.core.SVNErrorCode;
021: import org.tmatesoft.svn.core.SVNErrorMessage;
022: import org.tmatesoft.svn.core.SVNException;
023: import org.tmatesoft.svn.core.SVNNodeKind;
024: import org.tmatesoft.svn.core.SVNProperty;
025: import org.tmatesoft.svn.core.SVNURL;
026: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
027: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
028: import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor;
029: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
030: import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
031: import org.tmatesoft.svn.core.internal.wc.SVNExternalInfo;
032: import org.tmatesoft.svn.core.internal.wc.SVNFileType;
033: import org.tmatesoft.svn.core.internal.wc.SVNRemoteStatusEditor;
034: import org.tmatesoft.svn.core.internal.wc.SVNStatusEditor;
035: import org.tmatesoft.svn.core.internal.wc.SVNStatusReporter;
036: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
037: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
038: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
039: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
040: import org.tmatesoft.svn.core.internal.wc.admin.SVNReporter;
041: import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
042: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
043: import org.tmatesoft.svn.core.io.ISVNEditor;
044: import org.tmatesoft.svn.core.io.SVNRepository;
045:
046: /**
047: * The <b>SVNStatusClient</b> class provides methods for obtaining information on the
048: * status of Working Copy items.
049: * The functionality of <b>SVNStatusClient</b> corresponds to the <code>'svn status'</code> command
050: * of the native SVN command line client.
051: *
052: * <p>
053: * One of the main advantages of <b>SVNStatusClient</b> lies in that fact
054: * that for each processed item the status information is collected and put into
055: * an <b>SVNStatus</b> object. Further there are two ways how this object
056: * can be passed to a developer (depending on the version of the <b>doStatus()</b>
057: * method that was invoked):
058: * <ol>
059: * <li>the <b>SVNStatus</b> can be passed to a
060: * developer's status handler (that should implement <b>ISVNStatusHandler</b>)
061: * in which the developer retrieves status information and decides how to interprete that
062: * info;
063: * <li> another way is that an appropriate <b>doStatus()</b> method
064: * just returns that <b>SVNStatus</b> object.
065: * </ol>
066: * Those methods that match the first variant can be called recursively - obtaining
067: * status information for all child entries, the second variant just the reverse -
068: * methods are called non-recursively and allow to get status info on a single
069: * item.
070: *
071: * @version 1.1.1
072: * @author TMate Software Ltd.
073: * @see ISVNStatusHandler
074: * @see SVNStatus
075: * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
076: */
077: public class SVNStatusClient extends SVNBasicClient {
078:
079: /**
080: * Constructs and initializes an <b>SVNStatusClient</b> object
081: * with the specified run-time configuration and authentication
082: * drivers.
083: *
084: * <p>
085: * If <code>options</code> is <span class="javakeyword">null</span>,
086: * then this <b>SVNStatusClient</b> will be using a default run-time
087: * configuration driver which takes client-side settings from the
088: * default SVN's run-time configuration area but is not able to
089: * change those settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
090: *
091: * <p>
092: * If <code>authManager</code> is <span class="javakeyword">null</span>,
093: * then this <b>SVNStatusClient</b> will be using a default authentication
094: * and network layers driver (see {@link SVNWCUtil#createDefaultAuthenticationManager()})
095: * which uses server-side settings and auth storage from the
096: * default SVN's run-time configuration area (or system properties
097: * if that area is not found).
098: *
099: * @param authManager an authentication and network layers driver
100: * @param options a run-time configuration options driver
101: */
102: public SVNStatusClient(ISVNAuthenticationManager authManager,
103: ISVNOptions options) {
104: super (authManager, options);
105: }
106:
107: public SVNStatusClient(ISVNRepositoryPool repositoryPool,
108: ISVNOptions options) {
109: super (repositoryPool, options);
110: }
111:
112: /**
113: * Collects status information on Working Copy items and passes
114: * it to a <code>handler</code>.
115: *
116: * @param path local item's path
117: * @param recursive relevant only if <code>path</code> denotes a directory:
118: * <span class="javakeyword">true</span> to obtain status info recursively for all
119: * child entries, <span class="javakeyword">false</span> only for items located immediately
120: * in the directory itself
121: * @param remote <span class="javakeyword">true</span> to check up the status of the item in the repository,
122: * that will tell if the local item is out-of-date (like <i>'-u'</i> option in the
123: * SVN client's <code>'svn status'</code> command), otherwise
124: * <span class="javakeyword">false</span>
125: * @param reportAll <span class="javakeyword">true</span> to collect status information on those items that are in a
126: * <i>'normal'</i> state (unchanged), otherwise <span class="javakeyword">false</span>
127: * @param includeIgnored <span class="javakeyword">true</span> to force the operation to collect information
128: * on items that were set to be ignored (like <i>'--no-ignore'</i> option in the SVN
129: * client's <i>'svn status'</i> command to disregard default and <i>'svn:ignore'</i> property
130: * ignores), otherwise <span class="javakeyword">false</span>
131: * @param handler a caller's status handler that will be involved
132: * in processing status information
133: * @return the revision number the status information was collected
134: * against
135: * @throws SVNException
136: * @see ISVNStatusHandler
137: */
138: public long doStatus(File path, boolean recursive, boolean remote,
139: boolean reportAll, boolean includeIgnored,
140: ISVNStatusHandler handler) throws SVNException {
141: return doStatus(path, recursive, remote, reportAll,
142: includeIgnored, false, handler);
143: }
144:
145: /**
146: * Collects status information on Working Copy items and passes
147: * it to a <code>handler</code>.
148: *
149: * <p>
150: * Calling this method is equivalent to
151: * <code>doStatus(path, SVNRevision.HEAD, recursive, remote, reportAll, includeIgnored, collectParentExternals, handler)</code>.
152: *
153: * @param path local item's path
154: * @param recursive relevant only if <code>path</code> denotes a directory:
155: * <span class="javakeyword">true</span> to obtain status info recursively for all
156: * child entries, <span class="javakeyword">false</span> only for items located
157: * immediately in the directory itself
158: * @param remote <span class="javakeyword">true</span> to check up the status of the item in the repository,
159: * that will tell if the local item is out-of-date (like <i>'-u'</i> option in the
160: * SVN client's <code>'svn status'</code> command),
161: * otherwise <span class="javakeyword">false</span>
162: * @param reportAll <span class="javakeyword">true</span> to collect status information on all items including those ones that are in a
163: * <i>'normal'</i> state (unchanged), otherwise <span class="javakeyword">false</span>
164: * @param includeIgnored <span class="javakeyword">true</span> to force the operation to collect information
165: * on items that were set to be ignored (like <i>'--no-ignore'</i> option in the SVN
166: * client's <code>'svn status'</code> command to disregard default and <i>'svn:ignore'</i> property
167: * ignores), otherwise <span class="javakeyword">false</span>
168: * @param collectParentExternals <span class="javakeyword">false</span> to make the operation ignore information
169: * on externals definitions (like <i>'--ignore-externals'</i> option in the SVN
170: * client's <code>'svn status'</code> command), otherwise <span class="javakeyword">true</span>
171: * @param handler a caller's status handler that will be involved
172: * in processing status information
173: * @return the revision number the status information was collected
174: * against
175: * @throws SVNException
176: */
177: public long doStatus(File path, boolean recursive, boolean remote,
178: boolean reportAll, boolean includeIgnored,
179: boolean collectParentExternals,
180: final ISVNStatusHandler handler) throws SVNException {
181: return doStatus(path, SVNRevision.HEAD, recursive, remote,
182: reportAll, includeIgnored, collectParentExternals,
183: handler);
184: }
185:
186: /**
187: * Collects status information on Working Copy items and passes
188: * it to a <code>handler</code>.
189: *
190: * @param path local item's path
191: * @param revision if <code>remote</code> is <span class="javakeyword">true</span>
192: * this revision is used to calculate status against
193: * @param recursive relevant only if <code>path</code> denotes a directory:
194: * <span class="javakeyword">true</span> to obtain status info recursively for all
195: * child entries, <span class="javakeyword">false</span> only for items located
196: * immediately in the directory itself
197: * @param remote <span class="javakeyword">true</span> to check up the status of the item in the repository,
198: * that will tell if the local item is out-of-date (like <i>'-u'</i> option in the
199: * SVN client's <code>'svn status'</code> command),
200: * otherwise <span class="javakeyword">false</span>
201: * @param reportAll <span class="javakeyword">true</span> to collect status information on all items including those ones that are in a
202: * <i>'normal'</i> state (unchanged), otherwise <span class="javakeyword">false</span>
203: * @param includeIgnored <span class="javakeyword">true</span> to force the operation to collect information
204: * on items that were set to be ignored (like <i>'--no-ignore'</i> option in the SVN
205: * client's <code>'svn status'</code> command to disregard default and <i>'svn:ignore'</i> property
206: * ignores), otherwise <span class="javakeyword">false</span>
207: * @param collectParentExternals <span class="javakeyword">false</span> to make the operation ignore information
208: * on externals definitions (like <i>'--ignore-externals'</i> option in the SVN
209: * client's <code>'svn status'</code> command), otherwise <span class="javakeyword">true</span>
210: * @param handler a caller's status handler that will be involved
211: * in processing status information
212: * @return the revision number the status information was collected
213: * against
214: * @throws SVNException
215: */
216: public long doStatus(File path, SVNRevision revision,
217: boolean recursive, boolean remote, boolean reportAll,
218: boolean includeIgnored, boolean collectParentExternals,
219: final ISVNStatusHandler handler) throws SVNException {
220: if (handler == null) {
221: return -1;
222: }
223: SVNWCAccess wcAccess = createWCAccess();
224: SVNStatusEditor editor = null;
225: final boolean[] deletedInRepository = new boolean[] { false };
226: ISVNStatusHandler realHandler = new ISVNStatusHandler() {
227: public void handleStatus(SVNStatus status)
228: throws SVNException {
229: if (deletedInRepository[0] && status.getEntry() != null) {
230: status.setRemoteStatus(
231: SVNStatusType.STATUS_DELETED, null, null,
232: null);
233: }
234: handler.handleStatus(status);
235: }
236: };
237: try {
238: SVNAdminAreaInfo info = wcAccess.openAnchor(path, false,
239: recursive ? -1 : 1);
240: Map externals = null;
241: if (collectParentExternals) {
242: // prefetch externals from parent dirs, and pass it to the editor.
243: externals = collectParentExternals(path, info
244: .getAnchor().getRoot());
245: }
246: if (remote) {
247: SVNEntry entry = wcAccess.getEntry(info.getAnchor()
248: .getRoot(), false);
249: if (entry == null) {
250: SVNErrorMessage error = SVNErrorMessage.create(
251: SVNErrorCode.UNVERSIONED_RESOURCE,
252: "''{0}'' is not under version control",
253: path);
254: SVNErrorManager.error(error);
255: }
256: if (entry.getURL() == null) {
257: SVNErrorMessage error = SVNErrorMessage.create(
258: SVNErrorCode.ENTRY_MISSING_URL,
259: "Entry ''{0}'' has no URL", info
260: .getAnchor().getRoot());
261: SVNErrorManager.error(error);
262: }
263: SVNURL url = entry.getSVNURL();
264: SVNRepository repository = createRepository(url, true);
265: long rev;
266: if (revision == SVNRevision.HEAD) {
267: rev = -1;
268: } else {
269: rev = getRevisionNumber(revision, repository, path);
270: }
271: SVNNodeKind kind = repository.checkPath("", rev);
272: checkCancelled();
273: if (kind == SVNNodeKind.NONE) {
274: if (!entry.isScheduledForAddition()) {
275: deletedInRepository[0] = true;
276: }
277: editor = new SVNStatusEditor(getOptions(),
278: wcAccess, info, includeIgnored, reportAll,
279: recursive, realHandler);
280: editor.setExternals(externals);
281: checkCancelled();
282: editor.closeEdit();
283: } else {
284: editor = new SVNRemoteStatusEditor(getOptions(),
285: wcAccess, info, includeIgnored, reportAll,
286: recursive, realHandler);
287: editor.setExternals(externals);
288: // session is closed in SVNStatusReporter.
289: SVNRepository locksRepos = createRepository(url,
290: false);
291: checkCancelled();
292: SVNReporter reporter = new SVNReporter(info, path,
293: false, recursive, getDebugLog());
294: SVNStatusReporter statusReporter = new SVNStatusReporter(
295: locksRepos, reporter, editor);
296: String target = "".equals(info.getTargetName()) ? null
297: : info.getTargetName();
298: repository.status(rev, target, recursive,
299: statusReporter, SVNCancellableEditor
300: .newInstance((ISVNEditor) editor,
301: getEventDispatcher(),
302: getDebugLog()));
303: }
304: if (getEventDispatcher() != null) {
305: SVNEvent event = SVNEventFactory
306: .createStatusCompletedEvent(info, editor
307: .getTargetRevision());
308: getEventDispatcher().handleEvent(event,
309: ISVNEventHandler.UNKNOWN);
310: }
311: } else {
312: editor = new SVNStatusEditor(getOptions(), wcAccess,
313: info, includeIgnored, reportAll, recursive,
314: handler);
315: editor.setExternals(externals);
316: editor.closeEdit();
317: }
318: if (!isIgnoreExternals() && recursive) {
319: externals = editor.getExternals();
320: for (Iterator paths = externals.keySet().iterator(); paths
321: .hasNext();) {
322: String externalPath = (String) paths.next();
323: File externalFile = info.getAnchor().getFile(
324: externalPath);
325: if (SVNFileType.getType(externalFile) != SVNFileType.DIRECTORY) {
326: continue;
327: }
328: try {
329: int format = SVNAdminAreaFactory.checkWC(
330: externalFile, true);
331: if (format == 0) {
332: // something unversioned instead of external.
333: continue;
334: }
335: } catch (SVNException e) {
336: continue;
337: }
338: handleEvent(SVNEventFactory
339: .createStatusExternalEvent(info,
340: externalPath),
341: ISVNEventHandler.UNKNOWN);
342: setEventPathPrefix(externalPath);
343: try {
344: doStatus(externalFile, recursive, remote,
345: reportAll, includeIgnored, false,
346: handler);
347: } catch (SVNException e) {
348: if (e instanceof SVNCancelException) {
349: throw e;
350: }
351: } finally {
352: setEventPathPrefix(null);
353: }
354: }
355: }
356: } finally {
357: wcAccess.close();
358: }
359: return editor.getTargetRevision();
360: }
361:
362: /**
363: * Collects status information on a single Working Copy item.
364: *
365: * @param path local item's path
366: * @param remote <span class="javakeyword">true</span> to check up the status of the item in the repository,
367: * that will tell if the local item is out-of-date (like <i>'-u'</i> option in the
368: * SVN client's <code>'svn status'</code> command),
369: * otherwise <span class="javakeyword">false</span>
370: * @return an <b>SVNStatus</b> object representing status information
371: * for the item
372: * @throws SVNException
373: */
374: public SVNStatus doStatus(final File path, boolean remote)
375: throws SVNException {
376: return doStatus(path, remote, false);
377: }
378:
379: /**
380: * Collects status information on a single Working Copy item.
381: *
382: * @param path local item's path
383: * @param remote <span class="javakeyword">true</span> to check up the status of the item in the repository,
384: * that will tell if the local item is out-of-date (like <i>'-u'</i> option in the
385: * SVN client's <code>'svn status'</code> command),
386: * otherwise <span class="javakeyword">false</span>
387: * @param collectParentExternals <span class="javakeyword">false</span> to make the operation ignore information
388: * on externals definitions (like <i>'--ignore-externals'</i> option in the SVN
389: * client's <code>'svn status'</code> command), otherwise <span class="javakeyword">false</span>
390: * @return an <b>SVNStatus</b> object representing status information
391: * for the item
392: * @throws SVNException
393: */
394: public SVNStatus doStatus(File path, boolean remote,
395: boolean collectParentExternals) throws SVNException {
396: final SVNStatus[] result = new SVNStatus[] { null };
397: final File absPath = path.getAbsoluteFile();
398: ISVNStatusHandler handler = new ISVNStatusHandler() {
399: public void handleStatus(SVNStatus status) {
400: if (absPath.equals(status.getFile())) {
401: if (result[0] != null
402: && result[0].getContentsStatus() == SVNStatusType.STATUS_EXTERNAL
403: && absPath.isDirectory()) {
404: result[0] = status;
405: result[0].markExternal();
406: } else if (result[0] == null) {
407: result[0] = status;
408: }
409: }
410: }
411: };
412: doStatus(absPath, false, remote, true, true,
413: collectParentExternals, handler);
414: return result[0];
415: }
416:
417: private Map collectParentExternals(File path, File root)
418: throws SVNException {
419: Map externals = new HashMap();
420: SVNFileType type = SVNFileType.getType(path);
421: if (type != SVNFileType.DIRECTORY) {
422: return externals;
423: }
424: File target = path;
425: SVNWCAccess wcAccess = createWCAccess();
426: while (true) {
427: path = path.getParentFile();
428: if (path == null) {
429: break;
430: }
431: SVNAdminArea area = null;
432: try {
433: area = wcAccess.open(path, false, 0);
434: } catch (SVNException e) {
435: break;
436: }
437: try {
438: SVNVersionedProperties properties = area
439: .getProperties("");
440: String external = properties
441: .getPropertyValue(SVNProperty.EXTERNALS);
442: if (externals != null) {
443: SVNExternalInfo[] infos = SVNWCAccess
444: .parseExternals("", external);
445: for (int i = 0; i < infos.length; i++) {
446: // info's path is relative to path, we should make it relative to the root,
447: // and only if it is child of the root.
448: File ext = new File(path, infos[i].getPath());
449: if (SVNPathUtil.isChildOf(target, ext)) {
450: // put into the map - path relative to root is a key.
451: String extPath = ext.getAbsolutePath()
452: .replace(File.separatorChar, '/');
453: String rootPath = root.getAbsolutePath()
454: .replace(File.separatorChar, '/');
455: String relativePath = extPath
456: .substring(rootPath.length() + 1);
457: externals.put(relativePath, infos[i]);
458: }
459: }
460: }
461: } finally {
462: wcAccess.closeAdminArea(path);
463: }
464: }
465: return externals;
466: }
467:
468: }
|