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 /* ==================================================================== */
24 #include "svn_client.h"
25 #include "svn_pools.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. */
38 build_info_from_dirent(svn_info_t
**info
,
39 const svn_dirent_t
*dirent
,
42 svn_revnum_t revision
,
43 const char *repos_UUID
,
44 const char *repos_root
,
47 svn_info_t
*tmpinfo
= apr_pcalloc(pool
, sizeof(*tmpinfo
));
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
;
58 tmpinfo
->depth
= svn_depth_unknown
;
59 tmpinfo
->working_size
= SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN
;
60 tmpinfo
->size
= dirent
->size
;
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. */
70 build_info_from_entry(svn_info_t
**info
,
71 const svn_wc_entry_t
*entry
,
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
;
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
;
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 | \
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.
135 push_dir_info(svn_ra_session_t
*ra_session
,
136 const char *session_URL
,
139 const char *repos_UUID
,
140 const char *repos_root
,
141 svn_info_receiver_t receiver
,
142 void *receiver_baton
,
144 svn_client_ctx_t
*ctx
,
148 apr_hash_t
*tmpdirents
;
149 svn_dirent_t
*the_ent
;
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
;
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
);
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
);
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
;
215 info_found_entry_callback(const char *path
,
216 const svn_wc_entry_t
*entry
,
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
)))
229 if (SVN_WC__CL_MATCH(fe_baton
->changelist_hash
, entry
))
232 SVN_ERR(build_info_from_entry(&info
, entry
, pool
));
233 SVN_ERR(fe_baton
->receiver(fe_baton
->receiver_baton
, path
, info
, pool
));
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. */
251 crawl_entries(const char *wcpath
,
252 svn_info_receiver_t receiver
,
253 void *receiver_baton
,
255 apr_hash_t
*changelist_hash
,
256 svn_client_ctx_t
*ctx
,
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
))
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
));
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. */
297 same_resource_in_head(svn_boolean_t
*same_p
,
300 svn_ra_session_t
*ra_session
,
301 svn_client_ctx_t
*ctx
,
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
,
318 &start_rev
, &end_rev
,
321 ((err
->apr_err
== SVN_ERR_CLIENT_UNRELATED_RESOURCES
) ||
322 (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)))
324 svn_error_clear(err
);
331 /* ### Currently, the URLs should always be equal, since we can't
332 ### walk forwards in history. */
333 if (strcmp(url
, head_url
) == 0)
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
,
349 const apr_array_header_t
*changelists
,
350 svn_client_ctx_t
*ctx
,
353 svn_ra_session_t
*ra_session
, *parent_ra_session
;
356 svn_node_kind_t url_kind
;
357 const char *repos_root_URL
, *repos_UUID
;
359 svn_boolean_t related
;
360 apr_hash_t
*parent_ents
;
361 const char *parent_url
, *base_name
;
362 svn_dirent_t
*the_ent
;
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
,
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
,
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
414 if (depth
> svn_depth_empty
)
415 goto pre_1_2_recurse
;
417 /* Otherwise, we really are stuck. Better tell the user
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"),
430 /* Open a new RA session to the item's parent. */
431 SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session
,
433 NULL
, NULL
, FALSE
, TRUE
,
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
);
441 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
442 _("URL '%s' non-existent in revision %ld"),
451 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
452 _("URL '%s' non-existent in revision %ld"),
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
));
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
);
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
))
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. */
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 */
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
));
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
,
531 return svn_client_info2(path_or_url
, peg_revision
, revision
,
532 receiver
, receiver_baton
,
533 SVN_DEPTH_INFINITY_OR_EMPTY(recurse
),
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 ... */
545 /* ...and then re-copy stuff that needs to be duped into our pool. */
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
);
556 dupinfo
->lock
= svn_lock_dup(info
->lock
, pool
);
557 if (info
->copyfrom_url
)
558 dupinfo
->copyfrom_url
= apr_pstrdup(pool
, info
->copyfrom_url
);
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
);
568 dupinfo
->prejfile
= apr_pstrdup(pool
, info
->prejfile
);