Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_client / info.c
blob494da910060435b45ddd480eacd44b4d79d64abb
1 /*
2 * info.c: return system-generated metadata about paths or URLs.
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 #include "client.h"
24 #include "svn_client.h"
25 #include "svn_pools.h"
26 #include "svn_path.h"
27 #include "svn_hash.h"
28 #include "svn_wc.h"
30 #include "svn_private_config.h"
31 #include "private/svn_wc_private.h"
34 /* Helper: build an svn_info_t *INFO struct from svn_dirent_t DIRENT
35 and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
36 Pointer fields are copied by reference, not dup'd. */
37 static svn_error_t *
38 build_info_from_dirent(svn_info_t **info,
39 const svn_dirent_t *dirent,
40 svn_lock_t *lock,
41 const char *URL,
42 svn_revnum_t revision,
43 const char *repos_UUID,
44 const char *repos_root,
45 apr_pool_t *pool)
47 svn_info_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
49 tmpinfo->URL = URL;
50 tmpinfo->rev = revision;
51 tmpinfo->kind = dirent->kind;
52 tmpinfo->repos_UUID = repos_UUID;
53 tmpinfo->repos_root_URL = repos_root;
54 tmpinfo->last_changed_rev = dirent->created_rev;
55 tmpinfo->last_changed_date = dirent->time;
56 tmpinfo->last_changed_author = dirent->last_author;
57 tmpinfo->lock = lock;
58 tmpinfo->depth = svn_depth_unknown;
59 tmpinfo->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
60 tmpinfo->size = dirent->size;
62 *info = tmpinfo;
63 return SVN_NO_ERROR;
67 /* Helper: build an svn_info_t *INFO struct from svn_wc_entry_t ENTRY,
68 allocated in POOL. Pointer fields are copied by reference, not dup'd. */
69 static svn_error_t *
70 build_info_from_entry(svn_info_t **info,
71 const svn_wc_entry_t *entry,
72 apr_pool_t *pool)
74 svn_info_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
76 tmpinfo->URL = entry->url;
77 tmpinfo->rev = entry->revision;
78 tmpinfo->kind = entry->kind;
79 tmpinfo->repos_UUID = entry->uuid;
80 tmpinfo->repos_root_URL = entry->repos;
81 tmpinfo->last_changed_rev = entry->cmt_rev;
82 tmpinfo->last_changed_date = entry->cmt_date;
83 tmpinfo->last_changed_author = entry->cmt_author;
85 /* entry-specific stuff */
86 tmpinfo->has_wc_info = TRUE;
87 tmpinfo->schedule = entry->schedule;
88 tmpinfo->depth = entry->depth;
89 tmpinfo->copyfrom_url = entry->copyfrom_url;
90 tmpinfo->copyfrom_rev = entry->copyfrom_rev;
91 tmpinfo->text_time = entry->text_time;
92 tmpinfo->prop_time = entry->prop_time;
93 tmpinfo->checksum = entry->checksum;
94 tmpinfo->conflict_old = entry->conflict_old;
95 tmpinfo->conflict_new = entry->conflict_new;
96 tmpinfo->conflict_wrk = entry->conflict_wrk;
97 tmpinfo->prejfile = entry->prejfile;
98 tmpinfo->changelist = entry->changelist;
99 tmpinfo->working_size = entry->working_size;
100 tmpinfo->size = SVN_INFO_SIZE_UNKNOWN;
102 /* lock stuff */
103 if (entry->lock_token) /* the token is the critical bit. */
105 tmpinfo->lock = apr_pcalloc(pool, sizeof(*(tmpinfo->lock)));
107 tmpinfo->lock->token = entry->lock_token;
108 tmpinfo->lock->owner = entry->lock_owner;
109 tmpinfo->lock->comment = entry->lock_comment;
110 tmpinfo->lock->creation_date = entry->lock_creation_date;
113 *info = tmpinfo;
114 return SVN_NO_ERROR;
118 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
119 #define DIRENT_FIELDS (SVN_DIRENT_KIND | \
120 SVN_DIRENT_CREATED_REV | \
121 SVN_DIRENT_TIME | \
122 SVN_DIRENT_LAST_AUTHOR)
125 /* Helper func for recursively fetching svn_dirent_t's from a remote
126 directory and pushing them at an info-receiver callback.
128 DEPTH is the depth starting at DIR, even though RECEIVER is never
129 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
130 RECEIVER on all children of DIR, but none of their children; if
131 svn_depth_files, then invoke RECEIVER on file children of DIR but
132 not on subdirectories; if svn_depth_infinity, recurse fully.
134 static svn_error_t *
135 push_dir_info(svn_ra_session_t *ra_session,
136 const char *session_URL,
137 const char *dir,
138 svn_revnum_t rev,
139 const char *repos_UUID,
140 const char *repos_root,
141 svn_info_receiver_t receiver,
142 void *receiver_baton,
143 svn_depth_t depth,
144 svn_client_ctx_t *ctx,
145 apr_hash_t *locks,
146 apr_pool_t *pool)
148 apr_hash_t *tmpdirents;
149 svn_dirent_t *the_ent;
150 svn_info_t *info;
151 apr_hash_index_t *hi;
152 apr_pool_t *subpool = svn_pool_create(pool);
154 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
155 dir, rev, DIRENT_FIELDS, pool));
157 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
159 const char *path, *URL, *fs_path;
160 const void *key;
161 svn_lock_t *lock;
162 void *val;
164 svn_pool_clear(subpool);
166 if (ctx->cancel_func)
167 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
169 apr_hash_this(hi, &key, NULL, &val);
170 the_ent = val;
172 path = svn_path_join(dir, key, subpool);
173 URL = svn_path_url_add_component(session_URL, key, subpool);
175 fs_path = svn_path_is_child(repos_root, URL, subpool);
176 fs_path = apr_pstrcat(subpool, "/", fs_path, NULL);
177 fs_path = svn_path_uri_decode(fs_path, subpool);
179 lock = apr_hash_get(locks, fs_path, APR_HASH_KEY_STRING);
181 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, URL, rev,
182 repos_UUID, repos_root, subpool));
184 if (depth >= svn_depth_immediates
185 || (depth == svn_depth_files && the_ent->kind == svn_node_file))
187 SVN_ERR(receiver(receiver_baton, path, info, subpool));
190 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
192 SVN_ERR(push_dir_info(ra_session, URL, path,
193 rev, repos_UUID, repos_root,
194 receiver, receiver_baton,
195 depth, ctx, locks, subpool));
199 svn_pool_destroy(subpool);
201 return SVN_NO_ERROR;
206 /* Callback and baton for crawl_entries() walk over entries files. */
207 struct found_entry_baton
209 apr_hash_t *changelist_hash;
210 svn_info_receiver_t receiver;
211 void *receiver_baton;
214 static svn_error_t *
215 info_found_entry_callback(const char *path,
216 const svn_wc_entry_t *entry,
217 void *walk_baton,
218 apr_pool_t *pool)
220 struct found_entry_baton *fe_baton = walk_baton;
222 /* We're going to receive dirents twice; we want to ignore the
223 first one (where it's a child of a parent dir), and only print
224 the second one (where we're looking at THIS_DIR.) */
225 if ((entry->kind == svn_node_dir)
226 && (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR)))
227 return SVN_NO_ERROR;
229 if (SVN_WC__CL_MATCH(fe_baton->changelist_hash, entry))
231 svn_info_t *info;
232 SVN_ERR(build_info_from_entry(&info, entry, pool));
233 SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, path, info, pool));
235 return SVN_NO_ERROR;
240 static const svn_wc_entry_callbacks2_t
241 entry_walk_callbacks =
243 info_found_entry_callback,
244 svn_client__default_walker_error_handler
248 /* Helper function: push the svn_wc_entry_t for WCPATH at
249 RECEIVER/BATON, and possibly recurse over more entries. */
250 static svn_error_t *
251 crawl_entries(const char *wcpath,
252 svn_info_receiver_t receiver,
253 void *receiver_baton,
254 svn_depth_t depth,
255 apr_hash_t *changelist_hash,
256 svn_client_ctx_t *ctx,
257 apr_pool_t *pool)
259 svn_wc_adm_access_t *adm_access;
260 const svn_wc_entry_t *entry;
261 int adm_lock_level = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
263 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, wcpath, FALSE,
264 adm_lock_level, ctx->cancel_func,
265 ctx->cancel_baton, pool));
266 SVN_ERR(svn_wc__entry_versioned(&entry, wcpath, adm_access, FALSE, pool));
269 if (entry->kind == svn_node_file)
271 if (SVN_WC__CL_MATCH(changelist_hash, entry))
273 svn_info_t *info;
274 SVN_ERR(build_info_from_entry(&info, entry, pool));
275 return receiver(receiver_baton, wcpath, info, pool);
278 else if (entry->kind == svn_node_dir)
280 struct found_entry_baton fe_baton;
281 fe_baton.changelist_hash = changelist_hash;
282 fe_baton.receiver = receiver;
283 fe_baton.receiver_baton = receiver_baton;
284 SVN_ERR(svn_wc_walk_entries3(wcpath, adm_access,
285 &entry_walk_callbacks, &fe_baton,
286 depth, FALSE, ctx->cancel_func,
287 ctx->cancel_baton, pool));
290 return SVN_NO_ERROR;
293 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
294 refers to the same resource as it does in REV, using POOL for
295 temporary allocations. RA_SESSION is an open RA session for URL. */
296 static svn_error_t *
297 same_resource_in_head(svn_boolean_t *same_p,
298 const char *url,
299 svn_revnum_t rev,
300 svn_ra_session_t *ra_session,
301 svn_client_ctx_t *ctx,
302 apr_pool_t *pool)
304 svn_error_t *err;
305 svn_opt_revision_t start_rev, end_rev, peg_rev;
306 svn_opt_revision_t *ignored_rev;
307 const char *head_url, *ignored_url;
309 start_rev.kind = svn_opt_revision_head;
310 peg_rev.kind = svn_opt_revision_number;
311 peg_rev.value.number = rev;
312 end_rev.kind = svn_opt_revision_unspecified;
314 err = svn_client__repos_locations(&head_url, &ignored_rev,
315 &ignored_url, &ignored_rev,
316 ra_session,
317 url, &peg_rev,
318 &start_rev, &end_rev,
319 ctx, pool);
320 if (err &&
321 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
322 (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
324 svn_error_clear(err);
325 *same_p = FALSE;
327 else if (err)
328 return err;
329 else
331 /* ### Currently, the URLs should always be equal, since we can't
332 ### walk forwards in history. */
333 if (strcmp(url, head_url) == 0)
334 *same_p = TRUE;
335 else
336 *same_p = FALSE;
339 return SVN_NO_ERROR;
342 svn_error_t *
343 svn_client_info2(const char *path_or_url,
344 const svn_opt_revision_t *peg_revision,
345 const svn_opt_revision_t *revision,
346 svn_info_receiver_t receiver,
347 void *receiver_baton,
348 svn_depth_t depth,
349 const apr_array_header_t *changelists,
350 svn_client_ctx_t *ctx,
351 apr_pool_t *pool)
353 svn_ra_session_t *ra_session, *parent_ra_session;
354 svn_revnum_t rev;
355 const char *url;
356 svn_node_kind_t url_kind;
357 const char *repos_root_URL, *repos_UUID;
358 svn_lock_t *lock;
359 svn_boolean_t related;
360 apr_hash_t *parent_ents;
361 const char *parent_url, *base_name;
362 svn_dirent_t *the_ent;
363 svn_info_t *info;
364 svn_error_t *err;
366 if ((revision == NULL
367 || revision->kind == svn_opt_revision_unspecified)
368 && (peg_revision == NULL
369 || peg_revision->kind == svn_opt_revision_unspecified))
371 /* Do all digging in the working copy. */
372 apr_hash_t *changelist_hash = NULL;
373 if (changelists && changelists->nelts)
374 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
375 changelists, pool));
376 return crawl_entries(path_or_url, receiver, receiver_baton,
377 depth, changelist_hash, ctx, pool);
380 /* Go repository digging instead. */
382 /* Trace rename history (starting at path_or_url@peg_revision) and
383 return RA session to the possibly-renamed URL as it exists in REVISION.
384 The ra_session returned will be anchored on this "final" URL. */
385 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev,
386 &url, path_or_url, NULL,
387 peg_revision,
388 revision, ctx, pool));
390 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool));
391 SVN_ERR(svn_ra_get_uuid2(ra_session, &repos_UUID, pool));
393 svn_path_split(url, &parent_url, &base_name, pool);
394 base_name = svn_path_uri_decode(base_name, pool);
396 /* Get the dirent for the URL itself. */
397 err = svn_ra_stat(ra_session, "", rev, &the_ent, pool);
399 /* svn_ra_stat() will work against old versions of mod_dav_svn, but
400 not old versions of svnserve. In the case of a pre-1.2 svnserve,
401 catch the specific error it throws:*/
402 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
404 /* Fall back to pre-1.2 strategy for fetching dirent's URL. */
405 svn_error_clear(err);
407 if (strcmp(url, repos_root_URL) == 0)
409 /* In this universe, there's simply no way to fetch
410 information about the repository's root directory!
411 If we're recursing, degrade gracefully: rather than
412 throw an error, return no information about the
413 repos root. */
414 if (depth > svn_depth_empty)
415 goto pre_1_2_recurse;
417 /* Otherwise, we really are stuck. Better tell the user
418 what's going on. */
419 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
420 _("Server does not support retrieving "
421 "information about the repository root"));
424 SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool));
425 if (url_kind == svn_node_none)
426 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
427 _("URL '%s' non-existent in revision %ld"),
428 url, rev);
430 /* Open a new RA session to the item's parent. */
431 SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session,
432 parent_url, NULL,
433 NULL, NULL, FALSE, TRUE,
434 ctx, pool));
436 /* Get all parent's entries, and find the item's dirent in the hash. */
437 SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL,
438 "", rev, DIRENT_FIELDS, pool));
439 the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING);
440 if (the_ent == NULL)
441 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
442 _("URL '%s' non-existent in revision %ld"),
443 url, rev);
445 else if (err)
447 return err;
450 if (! the_ent)
451 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
452 _("URL '%s' non-existent in revision %ld"),
453 url, rev);
455 /* Check if the URL exists in HEAD and refers to the same resource.
456 In this case, we check the repository for a lock on this URL.
458 ### There is a possible race here, since HEAD might have changed since
459 ### we checked it. A solution to this problem could be to do the below
460 ### check in a loop which only terminates if the HEAD revision is the same
461 ### before and after this check. That could, however, lead to a
462 ### starvation situation instead. */
463 SVN_ERR(same_resource_in_head(&related, url, rev, ra_session, ctx, pool));
464 if (related)
466 err = svn_ra_get_lock(ra_session, &lock, "", pool);
468 /* An old mod_dav_svn will always work; there's nothing wrong with
469 doing a PROPFIND for a property named "DAV:supportedlock". But
470 an old svnserve will error. */
471 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
473 svn_error_clear(err);
474 lock = NULL;
476 else if (err)
477 return err;
479 else
480 lock = NULL;
482 /* Push the URL's dirent (and lock) at the callback.*/
483 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, url, rev,
484 repos_UUID, repos_root_URL, pool));
485 SVN_ERR(receiver(receiver_baton, base_name, info, pool));
487 /* Possibly recurse, using the original RA session. */
488 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
490 apr_hash_t *locks;
492 pre_1_2_recurse:
493 if (peg_revision->kind == svn_opt_revision_head)
495 err = svn_ra_get_locks(ra_session, &locks, "", pool);
497 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
498 if (err &&
499 (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED
500 || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
502 svn_error_clear(err);
503 locks = apr_hash_make(pool); /* use an empty hash */
505 else if (err)
506 return err;
508 else
509 locks = apr_hash_make(pool); /* use an empty hash */
511 SVN_ERR(push_dir_info(ra_session, url, "", rev,
512 repos_UUID, repos_root_URL,
513 receiver, receiver_baton,
514 depth, ctx, locks, pool));
517 return SVN_NO_ERROR;
521 svn_error_t *
522 svn_client_info(const char *path_or_url,
523 const svn_opt_revision_t *peg_revision,
524 const svn_opt_revision_t *revision,
525 svn_info_receiver_t receiver,
526 void *receiver_baton,
527 svn_boolean_t recurse,
528 svn_client_ctx_t *ctx,
529 apr_pool_t *pool)
531 return svn_client_info2(path_or_url, peg_revision, revision,
532 receiver, receiver_baton,
533 SVN_DEPTH_INFINITY_OR_EMPTY(recurse),
534 NULL, ctx, pool);
537 svn_info_t *
538 svn_info_dup(const svn_info_t *info, apr_pool_t *pool)
540 svn_info_t *dupinfo = apr_palloc(pool, sizeof(*dupinfo));
542 /* Perform a trivial copy ... */
543 *dupinfo = *info;
545 /* ...and then re-copy stuff that needs to be duped into our pool. */
546 if (info->URL)
547 dupinfo->URL = apr_pstrdup(pool, info->URL);
548 if (info->repos_root_URL)
549 dupinfo->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
550 if (info->repos_UUID)
551 dupinfo->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
552 if (info->last_changed_author)
553 dupinfo->last_changed_author = apr_pstrdup(pool,
554 info->last_changed_author);
555 if (info->lock)
556 dupinfo->lock = svn_lock_dup(info->lock, pool);
557 if (info->copyfrom_url)
558 dupinfo->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url);
559 if (info->checksum)
560 dupinfo->checksum = apr_pstrdup(pool, info->checksum);
561 if (info->conflict_old)
562 dupinfo->conflict_old = apr_pstrdup(pool, info->conflict_old);
563 if (info->conflict_new)
564 dupinfo->conflict_new = apr_pstrdup(pool, info->conflict_new);
565 if (info->conflict_wrk)
566 dupinfo->conflict_wrk = apr_pstrdup(pool, info->conflict_wrk);
567 if (info->prejfile)
568 dupinfo->prejfile = apr_pstrdup(pool, info->prejfile);
570 return dupinfo;