In the command-line client, forbid
[svn.git] / subversion / libsvn_client / status.c
blob97f6d0c4f18db581b0f934d3a80e7356f44057e9
1 /*
2 * status.c: return the status of a working copy dirent
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
24 #include <assert.h>
25 #include <apr_strings.h>
26 #include <apr_pools.h>
28 #include "svn_pools.h"
29 #include "client.h"
31 #include "svn_path.h"
32 #include "svn_delta.h"
33 #include "svn_client.h"
34 #include "svn_error.h"
36 #include "svn_private_config.h"
37 #include "private/svn_wc_private.h"
40 /*** Getting update information ***/
42 /* Baton for tweak_status. It wraps a bit of extra functionality
43 around the received status func/baton, so we can remember if the
44 target was deleted in HEAD and tweak incoming status structures
45 accordingly. */
46 struct status_baton
48 svn_boolean_t deleted_in_repos; /* target is deleted in repos */
49 svn_wc_status_func2_t real_status_func; /* real status function */
50 void *real_status_baton; /* real status baton */
53 /* A status callback function which wraps the *real* status
54 function/baton. This sucker takes care of any status tweaks we
55 need to make (such as noting that the target of the status is
56 missing from HEAD in the repository).
58 This implements the 'svn_wc_status_func2_t' function type. */
59 static void
60 tweak_status(void *baton,
61 const char *path,
62 svn_wc_status2_t *status)
64 struct status_baton *sb = baton;
66 /* If we know that the target was deleted in HEAD of the repository,
67 we need to note that fact in all the status structures that come
68 through here. */
69 if (sb->deleted_in_repos)
70 status->repos_text_status = svn_wc_status_deleted;
72 /* Call the real status function/baton. */
73 sb->real_status_func(sb->real_status_baton, path, status);
76 /* A baton for our reporter that is used to collect locks. */
77 typedef struct report_baton_t {
78 const svn_ra_reporter3_t* wrapped_reporter;
79 void *wrapped_report_baton;
80 /* The common ancestor URL of all paths included in the report. */
81 char *ancestor;
82 void *set_locks_baton;
83 svn_client_ctx_t *ctx;
84 /* Pool to store locks in. */
85 apr_pool_t *pool;
86 } report_baton_t;
88 /* Implements svn_ra_reporter3_t->set_path. */
89 static svn_error_t *
90 reporter_set_path(void *report_baton, const char *path,
91 svn_revnum_t revision, svn_depth_t depth,
92 svn_boolean_t start_empty, const char *lock_token,
93 apr_pool_t *pool)
95 report_baton_t *rb = report_baton;
97 return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
98 revision, depth, start_empty,
99 lock_token, pool);
102 /* Implements svn_ra_reporter3_t->delete_path. */
103 static svn_error_t *
104 reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool)
106 report_baton_t *rb = report_baton;
108 return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path,
109 pool);
112 /* Implements svn_ra_reporter3_t->link_path. */
113 static svn_error_t *
114 reporter_link_path(void *report_baton, const char *path, const char *url,
115 svn_revnum_t revision, svn_depth_t depth,
116 svn_boolean_t start_empty,
117 const char *lock_token, apr_pool_t *pool)
119 report_baton_t *rb = report_baton;
120 const char *ancestor;
121 apr_size_t len;
123 ancestor = svn_path_get_longest_ancestor(url, rb->ancestor, pool);
125 /* If we got a shorter ancestor, truncate our current ancestor.
126 Note that svn_path_get_longest_ancestor will allocate its return
127 value even if it identical to one of its arguments. */
128 len = strlen(ancestor);
129 if (len < strlen(rb->ancestor))
130 rb->ancestor[len] = '\0';
132 return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
133 revision, depth, start_empty,
134 lock_token, pool);
137 /* Implements svn_ra_reporter3_t->finish_report. */
138 static svn_error_t *
139 reporter_finish_report(void *report_baton, apr_pool_t *pool)
141 report_baton_t *rb = report_baton;
142 svn_ra_session_t *ras;
143 apr_hash_t *locks;
144 const char *repos_root;
145 apr_pool_t *subpool = svn_pool_create(pool);
146 svn_error_t *err = SVN_NO_ERROR;
148 /* Open an RA session to our common ancestor and grab the locks under it.
150 SVN_ERR(svn_client__open_ra_session_internal(&ras, rb->ancestor, NULL,
151 NULL, NULL, FALSE, TRUE,
152 rb->ctx, subpool));
154 /* The locks need to live throughout the edit. Note that if the
155 server doesn't support lock discovery, we'll just not do locky
156 stuff. */
157 err = svn_ra_get_locks(ras, &locks, "", rb->pool);
158 if (err && ((err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
159 || (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)))
161 svn_error_clear(err);
162 err = SVN_NO_ERROR;
163 locks = apr_hash_make(rb->pool);
165 SVN_ERR(err);
167 SVN_ERR(svn_ra_get_repos_root(ras, &repos_root, subpool));
168 repos_root = apr_pstrdup(rb->pool, repos_root);
170 /* Close the RA session. */
171 svn_pool_destroy(subpool);
173 SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks,
174 repos_root, rb->pool));
176 return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool);
179 /* Implements svn_ra_reporter3_t->abort_report. */
180 static svn_error_t *
181 reporter_abort_report(void *report_baton, apr_pool_t *pool)
183 report_baton_t *rb = report_baton;
185 return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool);
188 /* A reporter that keeps track of the common URL ancestor of all paths in
189 the WC and fetches repository locks for all paths under this ancestor. */
190 static svn_ra_reporter3_t lock_fetch_reporter = {
191 reporter_set_path,
192 reporter_delete_path,
193 reporter_link_path,
194 reporter_finish_report,
195 reporter_abort_report
199 /*** Public Interface. ***/
202 svn_error_t *
203 svn_client_status3(svn_revnum_t *result_rev,
204 const char *path,
205 const svn_opt_revision_t *revision,
206 svn_wc_status_func2_t status_func,
207 void *status_baton,
208 svn_depth_t depth,
209 svn_boolean_t get_all,
210 svn_boolean_t update,
211 svn_boolean_t no_ignore,
212 svn_boolean_t ignore_externals,
213 svn_client_ctx_t *ctx,
214 apr_pool_t *pool)
216 svn_wc_adm_access_t *anchor_access, *target_access;
217 svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool);
218 const char *anchor, *target;
219 const svn_delta_editor_t *editor;
220 void *edit_baton, *set_locks_baton;
221 const svn_wc_entry_t *entry = NULL;
222 struct status_baton sb;
223 apr_array_header_t *ignores;
224 svn_error_t *err;
226 svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
228 sb.real_status_func = status_func;
229 sb.real_status_baton = status_baton;
230 sb.deleted_in_repos = FALSE;
232 /* Try to open the target directory. If the target is a file or an
233 unversioned directory, open the parent directory instead */
234 err = svn_wc_adm_open3(&anchor_access, NULL, path, FALSE,
235 SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1,
236 ctx->cancel_func, ctx->cancel_baton,
237 pool);
238 if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
240 svn_error_clear(err);
241 SVN_ERR(svn_wc_adm_open_anchor(&anchor_access, &target_access, &target,
242 path, FALSE,
243 SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1,
244 ctx->cancel_func, ctx->cancel_baton,
245 pool));
247 else if (!err)
249 target = "";
250 target_access = anchor_access;
252 else
253 return err;
255 anchor = svn_wc_adm_access_path(anchor_access);
257 /* Get the status edit, and use our wrapping status function/baton
258 as the callback pair. */
259 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
260 SVN_ERR(svn_wc_get_status_editor3(&editor, &edit_baton, &set_locks_baton,
261 &edit_revision, anchor_access, target,
262 depth, get_all, no_ignore, ignores,
263 tweak_status, &sb, ctx->cancel_func,
264 ctx->cancel_baton, traversal_info,
265 pool));
267 /* If we want to know about out-of-dateness, we crawl the working copy and
268 let the RA layer drive the editor for real. Otherwise, we just close the
269 edit. :-) */
270 if (update)
272 svn_ra_session_t *ra_session;
273 const char *URL;
274 svn_node_kind_t kind;
275 svn_boolean_t server_supports_depth;
277 /* Get full URL from the ANCHOR. */
278 if (! entry)
279 SVN_ERR(svn_wc__entry_versioned(&entry, anchor, anchor_access, FALSE,
280 pool));
281 if (! entry->url)
282 return svn_error_createf
283 (SVN_ERR_ENTRY_MISSING_URL, NULL,
284 _("Entry '%s' has no URL"),
285 svn_path_local_style(anchor, pool));
286 URL = apr_pstrdup(pool, entry->url);
288 /* Open a repository session to the URL. */
289 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, anchor,
290 anchor_access, NULL,
291 FALSE, TRUE,
292 ctx, pool));
294 /* Verify that URL exists in HEAD. If it doesn't, this can save
295 us a whole lot of hassle; if it does, the cost of this
296 request should be minimal compared to the size of getting
297 back the average amount of "out-of-date" information. */
298 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM,
299 &kind, pool));
300 if (kind == svn_node_none)
302 /* Our status target does not exist in HEAD of the
303 repository. If we're just adding this thing, that's
304 fine. But if it was previously versioned, then it must
305 have been deleted from the repository. */
306 if (entry->schedule != svn_wc_schedule_add)
307 sb.deleted_in_repos = TRUE;
309 /* And now close the edit. */
310 SVN_ERR(editor->close_edit(edit_baton, pool));
312 else
314 svn_revnum_t revnum;
315 report_baton_t rb;
317 if (revision->kind == svn_opt_revision_head)
319 /* Cause the revision number to be omitted from the request,
320 which implies HEAD. */
321 revnum = SVN_INVALID_REVNUM;
323 else
325 /* Get a revision number for our status operation. */
326 SVN_ERR(svn_client__get_revision_number
327 (&revnum, NULL, ra_session, revision, target, pool));
330 /* Do the deed. Let the RA layer drive the status editor. */
331 SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter,
332 &rb.wrapped_report_baton,
333 target, revnum, depth, editor,
334 edit_baton, pool));
336 /* Init the report baton. */
337 rb.ancestor = apr_pstrdup(pool, URL);
338 rb.set_locks_baton = set_locks_baton;
339 rb.ctx = ctx;
340 rb.pool = pool;
342 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
343 SVN_RA_CAPABILITY_DEPTH, pool));
345 /* Drive the reporter structure, describing the revisions
346 within PATH. When we call reporter->finish_report,
347 EDITOR will be driven to describe differences between our
348 working copy and HEAD. */
349 SVN_ERR(svn_wc_crawl_revisions3(path, target_access,
350 &lock_fetch_reporter, &rb, FALSE,
351 depth, (! server_supports_depth),
352 FALSE, NULL, NULL, NULL, pool));
355 else
357 SVN_ERR(editor->close_edit(edit_baton, pool));
360 if (ctx->notify_func2 && update)
362 svn_wc_notify_t *notify
363 = svn_wc_create_notify(path, svn_wc_notify_status_completed, pool);
364 notify->revision = edit_revision;
365 (ctx->notify_func2)(ctx->notify_baton2, notify, pool);
368 /* If the caller wants the result revision, give it to them. */
369 if (result_rev)
370 *result_rev = edit_revision;
372 /* Close the access baton here, as svn_client__do_external_status()
373 calls back into this function and thus will be re-opening the
374 working copy. */
375 SVN_ERR(svn_wc_adm_close(anchor_access));
377 /* If there are svn:externals set, we don't want those to show up as
378 unversioned or unrecognized, so patch up the hash. If caller wants
379 all the statuses, we will change unversioned status items that
380 are interesting to an svn:externals property to
381 svn_wc_status_unversioned, otherwise we'll just remove the status
382 item altogether.
384 We only descend into an external if depth is svn_depth_infinity or
385 svn_depth_unknown. However, there are conceivable behaviors that
386 would involve descending under other circumstances; thus, we pass
387 depth anyway, so the code will DTRT if we change the conditional
388 in the future.
390 if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
391 SVN_ERR(svn_client__do_external_status(traversal_info, status_func,
392 status_baton, depth, get_all,
393 update, no_ignore, ctx, pool));
395 return SVN_NO_ERROR;
398 svn_error_t *
399 svn_client_status2(svn_revnum_t *result_rev,
400 const char *path,
401 const svn_opt_revision_t *revision,
402 svn_wc_status_func2_t status_func,
403 void *status_baton,
404 svn_boolean_t recurse,
405 svn_boolean_t get_all,
406 svn_boolean_t update,
407 svn_boolean_t no_ignore,
408 svn_boolean_t ignore_externals,
409 svn_client_ctx_t *ctx,
410 apr_pool_t *pool)
412 return svn_client_status3(result_rev, path, revision,
413 status_func, status_baton,
414 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
415 get_all, update,
416 no_ignore, ignore_externals,
417 ctx, pool);
421 /* Baton for old_status_func_cb; does what you think it does. */
422 struct old_status_func_cb_baton
424 svn_wc_status_func_t original_func;
425 void *original_baton;
428 /* Help svn_client_status() accept an old-style status func and baton,
429 by wrapping them before passing along to svn_client_status2().
431 This implements the 'svn_wc_status_func2_t' function type. */
432 static void old_status_func_cb(void *baton,
433 const char *path,
434 svn_wc_status2_t *status)
436 struct old_status_func_cb_baton *b = baton;
437 svn_wc_status_t *stat = (svn_wc_status_t *) status;
439 b->original_func(b->original_baton, path, stat);
443 svn_error_t *
444 svn_client_status(svn_revnum_t *result_rev,
445 const char *path,
446 svn_opt_revision_t *revision,
447 svn_wc_status_func_t status_func,
448 void *status_baton,
449 svn_boolean_t recurse,
450 svn_boolean_t get_all,
451 svn_boolean_t update,
452 svn_boolean_t no_ignore,
453 svn_client_ctx_t *ctx,
454 apr_pool_t *pool)
456 struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b));
457 b->original_func = status_func;
458 b->original_baton = status_baton;
460 return svn_client_status2(result_rev, path, revision,
461 old_status_func_cb, b,
462 recurse, get_all, update, no_ignore, FALSE,
463 ctx, pool);