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"
29 #include "svn_private_config.h"
30 #include "private/svn_wc_private.h"
33 /* Helper: build an svn_info_t *INFO struct from svn_dirent_t DIRENT
34 and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
35 Pointer fields are copied by reference, not dup'd. */
37 build_info_from_dirent(svn_info_t
**info
,
38 const svn_dirent_t
*dirent
,
41 svn_revnum_t revision
,
42 const char *repos_UUID
,
43 const char *repos_root
,
46 svn_info_t
*tmpinfo
= apr_pcalloc(pool
, sizeof(*tmpinfo
));
49 tmpinfo
->rev
= revision
;
50 tmpinfo
->kind
= dirent
->kind
;
51 tmpinfo
->repos_UUID
= repos_UUID
;
52 tmpinfo
->repos_root_URL
= repos_root
;
53 tmpinfo
->last_changed_rev
= dirent
->created_rev
;
54 tmpinfo
->last_changed_date
= dirent
->time
;
55 tmpinfo
->last_changed_author
= dirent
->last_author
;
57 tmpinfo
->depth
= svn_depth_unknown
;
58 tmpinfo
->working_size
= SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN
;
59 tmpinfo
->size
= dirent
->size
;
66 /* Helper: build an svn_info_t *INFO struct from svn_wc_entry_t ENTRY,
67 allocated in POOL. Pointer fields are copied by reference, not dup'd. */
69 build_info_from_entry(svn_info_t
**info
,
70 const svn_wc_entry_t
*entry
,
73 svn_info_t
*tmpinfo
= apr_pcalloc(pool
, sizeof(*tmpinfo
));
75 tmpinfo
->URL
= entry
->url
;
76 tmpinfo
->rev
= entry
->revision
;
77 tmpinfo
->kind
= entry
->kind
;
78 tmpinfo
->repos_UUID
= entry
->uuid
;
79 tmpinfo
->repos_root_URL
= entry
->repos
;
80 tmpinfo
->last_changed_rev
= entry
->cmt_rev
;
81 tmpinfo
->last_changed_date
= entry
->cmt_date
;
82 tmpinfo
->last_changed_author
= entry
->cmt_author
;
84 /* entry-specific stuff */
85 tmpinfo
->has_wc_info
= TRUE
;
86 tmpinfo
->schedule
= entry
->schedule
;
87 tmpinfo
->depth
= entry
->depth
;
88 tmpinfo
->copyfrom_url
= entry
->copyfrom_url
;
89 tmpinfo
->copyfrom_rev
= entry
->copyfrom_rev
;
90 tmpinfo
->text_time
= entry
->text_time
;
91 tmpinfo
->prop_time
= entry
->prop_time
;
92 tmpinfo
->checksum
= entry
->checksum
;
93 tmpinfo
->conflict_old
= entry
->conflict_old
;
94 tmpinfo
->conflict_new
= entry
->conflict_new
;
95 tmpinfo
->conflict_wrk
= entry
->conflict_wrk
;
96 tmpinfo
->prejfile
= entry
->prejfile
;
97 tmpinfo
->changelist
= entry
->changelist
;
98 tmpinfo
->working_size
= entry
->working_size
;
99 tmpinfo
->size
= SVN_INFO_SIZE_UNKNOWN
;
102 if (entry
->lock_token
) /* the token is the critical bit. */
104 tmpinfo
->lock
= apr_pcalloc(pool
, sizeof(*(tmpinfo
->lock
)));
106 tmpinfo
->lock
->token
= entry
->lock_token
;
107 tmpinfo
->lock
->owner
= entry
->lock_owner
;
108 tmpinfo
->lock
->comment
= entry
->lock_comment
;
109 tmpinfo
->lock
->creation_date
= entry
->lock_creation_date
;
117 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
118 #define DIRENT_FIELDS (SVN_DIRENT_KIND | \
119 SVN_DIRENT_CREATED_REV | \
121 SVN_DIRENT_LAST_AUTHOR)
124 /* Helper func for recursively fetching svn_dirent_t's from a remote
125 directory and pushing them at an info-receiver callback.
127 DEPTH is the depth starting at DIR, even though RECEIVER is never
128 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
129 RECEIVER on all children of DIR, but none of their children; if
130 svn_depth_files, then invoke RECEIVER on file children of DIR but
131 not on subdirectories; if svn_depth_infinity, recurse fully.
134 push_dir_info(svn_ra_session_t
*ra_session
,
135 const char *session_URL
,
138 const char *repos_UUID
,
139 const char *repos_root
,
140 svn_info_receiver_t receiver
,
141 void *receiver_baton
,
143 svn_client_ctx_t
*ctx
,
147 apr_hash_t
*tmpdirents
;
148 svn_dirent_t
*the_ent
;
150 apr_hash_index_t
*hi
;
151 apr_pool_t
*subpool
= svn_pool_create(pool
);
153 SVN_ERR(svn_ra_get_dir2(ra_session
, &tmpdirents
, NULL
, NULL
,
154 dir
, rev
, DIRENT_FIELDS
, pool
));
156 for (hi
= apr_hash_first(pool
, tmpdirents
); hi
; hi
= apr_hash_next(hi
))
158 const char *path
, *URL
, *fs_path
;
163 svn_pool_clear(subpool
);
165 if (ctx
->cancel_func
)
166 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
168 apr_hash_this(hi
, &key
, NULL
, &val
);
171 path
= svn_path_join(dir
, key
, subpool
);
172 URL
= svn_path_url_add_component(session_URL
, key
, subpool
);
174 fs_path
= svn_path_is_child(repos_root
, URL
, subpool
);
175 fs_path
= apr_pstrcat(subpool
, "/", fs_path
, NULL
);
176 fs_path
= svn_path_uri_decode(fs_path
, subpool
);
178 lock
= apr_hash_get(locks
, fs_path
, APR_HASH_KEY_STRING
);
180 SVN_ERR(build_info_from_dirent(&info
, the_ent
, lock
, URL
, rev
,
181 repos_UUID
, repos_root
, subpool
));
183 if (depth
>= svn_depth_immediates
184 || (depth
== svn_depth_files
&& the_ent
->kind
== svn_node_file
))
186 SVN_ERR(receiver(receiver_baton
, path
, info
, subpool
));
189 if (depth
== svn_depth_infinity
&& the_ent
->kind
== svn_node_dir
)
191 SVN_ERR(push_dir_info(ra_session
, URL
, path
,
192 rev
, repos_UUID
, repos_root
,
193 receiver
, receiver_baton
,
194 depth
, ctx
, locks
, subpool
));
198 svn_pool_destroy(subpool
);
205 /* Callback and baton for crawl_entries() walk over entries files. */
206 struct found_entry_baton
208 svn_info_receiver_t receiver
;
209 void *receiver_baton
;
213 info_found_entry_callback(const char *path
,
214 const svn_wc_entry_t
*entry
,
218 struct found_entry_baton
*fe_baton
= walk_baton
;
221 /* We're going to receive dirents twice; we want to ignore the
222 first one (where it's a child of a parent dir), and only print
223 the second one (where we're looking at THIS_DIR.) */
224 if ((entry
->kind
== svn_node_dir
)
225 && (strcmp(entry
->name
, SVN_WC_ENTRY_THIS_DIR
)))
228 SVN_ERR(build_info_from_entry(&info
, entry
, pool
));
230 return fe_baton
->receiver(fe_baton
->receiver_baton
, path
, info
, pool
);
235 static const svn_wc_entry_callbacks2_t
236 entry_walk_callbacks
=
238 info_found_entry_callback
,
239 svn_client__default_walker_error_handler
243 /* Helper function: push the svn_wc_entry_t for WCPATH at
244 RECEIVER/BATON, and possibly recurse over more entries. */
246 crawl_entries(const char *wcpath
,
247 svn_info_receiver_t receiver
,
248 void *receiver_baton
,
250 svn_client_ctx_t
*ctx
,
253 svn_wc_adm_access_t
*adm_access
;
254 const svn_wc_entry_t
*entry
;
256 struct found_entry_baton fe_baton
;
257 int adm_lock_level
= SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth
);
259 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, wcpath
, FALSE
,
261 ctx
->cancel_func
, ctx
->cancel_baton
,
263 SVN_ERR(svn_wc__entry_versioned(&entry
, wcpath
, adm_access
, FALSE
, pool
));
265 SVN_ERR(build_info_from_entry(&info
, entry
, pool
));
266 fe_baton
.receiver
= receiver
;
267 fe_baton
.receiver_baton
= receiver_baton
;
269 if (entry
->kind
== svn_node_file
)
271 return receiver(receiver_baton
, wcpath
, info
, pool
);
273 else if (entry
->kind
== svn_node_dir
)
275 SVN_ERR(svn_wc_walk_entries3(wcpath
, adm_access
,
276 &entry_walk_callbacks
, &fe_baton
,
277 depth
, FALSE
, ctx
->cancel_func
,
278 ctx
->cancel_baton
, pool
));
284 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
285 refers to the same resource as it does in REV, using POOL for
286 temporary allocations. RA_SESSION is an open RA session for URL. */
288 same_resource_in_head(svn_boolean_t
*same_p
,
291 svn_ra_session_t
*ra_session
,
292 svn_client_ctx_t
*ctx
,
296 svn_opt_revision_t start_rev
, end_rev
, peg_rev
;
297 svn_opt_revision_t
*ignored_rev
;
298 const char *head_url
, *ignored_url
;
300 start_rev
.kind
= svn_opt_revision_head
;
301 peg_rev
.kind
= svn_opt_revision_number
;
302 peg_rev
.value
.number
= rev
;
303 end_rev
.kind
= svn_opt_revision_unspecified
;
305 err
= svn_client__repos_locations(&head_url
, &ignored_rev
,
306 &ignored_url
, &ignored_rev
,
309 &start_rev
, &end_rev
,
312 ((err
->apr_err
== SVN_ERR_CLIENT_UNRELATED_RESOURCES
) ||
313 (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)))
315 svn_error_clear(err
);
322 /* ### Currently, the URLs should always be equal, since we can't
323 ### walk forwards in history. */
324 if (strcmp(url
, head_url
) == 0)
334 svn_client_info2(const char *path_or_url
,
335 const svn_opt_revision_t
*peg_revision
,
336 const svn_opt_revision_t
*revision
,
337 svn_info_receiver_t receiver
,
338 void *receiver_baton
,
340 svn_client_ctx_t
*ctx
,
343 svn_ra_session_t
*ra_session
, *parent_ra_session
;
346 svn_node_kind_t url_kind
;
347 const char *repos_root_URL
, *repos_UUID
;
349 svn_boolean_t related
;
350 apr_hash_t
*parent_ents
;
351 const char *parent_url
, *base_name
;
352 svn_dirent_t
*the_ent
;
356 if ((revision
== NULL
357 || revision
->kind
== svn_opt_revision_unspecified
)
358 && (peg_revision
== NULL
359 || peg_revision
->kind
== svn_opt_revision_unspecified
))
361 /* Do all digging in the working copy. */
362 return crawl_entries(path_or_url
,
363 receiver
, receiver_baton
,
367 /* Go repository digging instead. */
369 /* Trace rename history (starting at path_or_url@peg_revision) and
370 return RA session to the possibly-renamed URL as it exists in REVISION.
371 The ra_session returned will be anchored on this "final" URL. */
372 SVN_ERR(svn_client__ra_session_from_path(&ra_session
, &rev
,
373 &url
, path_or_url
, NULL
,
375 revision
, ctx
, pool
));
377 SVN_ERR(svn_ra_get_repos_root(ra_session
, &repos_root_URL
, pool
));
378 SVN_ERR(svn_ra_get_uuid(ra_session
, &repos_UUID
, pool
));
380 svn_path_split(url
, &parent_url
, &base_name
, pool
);
381 base_name
= svn_path_uri_decode(base_name
, pool
);
383 /* Get the dirent for the URL itself. */
384 err
= svn_ra_stat(ra_session
, "", rev
, &the_ent
, pool
);
386 /* svn_ra_stat() will work against old versions of mod_dav_svn, but
387 not old versions of svnserve. In the case of a pre-1.2 svnserve,
388 catch the specific error it throws:*/
389 if (err
&& err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
391 /* Fall back to pre-1.2 strategy for fetching dirent's URL. */
392 svn_error_clear(err
);
394 if (strcmp(url
, repos_root_URL
) == 0)
396 /* In this universe, there's simply no way to fetch
397 information about the repository's root directory!
398 If we're recursing, degrade gracefully: rather than
399 throw an error, return no information about the
401 if (depth
> svn_depth_empty
)
402 goto pre_1_2_recurse
;
404 /* Otherwise, we really are stuck. Better tell the user
406 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
407 _("Server does not support retrieving "
408 "information about the repository root"));
411 SVN_ERR(svn_ra_check_path(ra_session
, "", rev
, &url_kind
, pool
));
412 if (url_kind
== svn_node_none
)
413 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
414 _("URL '%s' non-existent in revision %ld"),
417 /* Open a new RA session to the item's parent. */
418 SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session
,
420 NULL
, NULL
, FALSE
, TRUE
,
423 /* Get all parent's entries, and find the item's dirent in the hash. */
424 SVN_ERR(svn_ra_get_dir2(parent_ra_session
, &parent_ents
, NULL
, NULL
,
425 "", rev
, DIRENT_FIELDS
, pool
));
426 the_ent
= apr_hash_get(parent_ents
, base_name
, APR_HASH_KEY_STRING
);
428 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
429 _("URL '%s' non-existent in revision %ld"),
438 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
439 _("URL '%s' non-existent in revision %ld"),
442 /* Check if the URL exists in HEAD and refers to the same resource.
443 In this case, we check the repository for a lock on this URL.
445 ### There is a possible race here, since HEAD might have changed since
446 ### we checked it. A solution to this problem could be to do the below
447 ### check in a loop which only terminates if the HEAD revision is the same
448 ### before and after this check. That could, however, lead to a
449 ### starvation situation instead. */
450 SVN_ERR(same_resource_in_head(&related
, url
, rev
, ra_session
, ctx
, pool
));
453 err
= svn_ra_get_lock(ra_session
, &lock
, "", pool
);
455 /* An old mod_dav_svn will always work; there's nothing wrong with
456 doing a PROPFIND for a property named "DAV:supportedlock". But
457 an old svnserve will error. */
458 if (err
&& err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
460 svn_error_clear(err
);
469 /* Push the URL's dirent (and lock) at the callback.*/
470 SVN_ERR(build_info_from_dirent(&info
, the_ent
, lock
, url
, rev
,
471 repos_UUID
, repos_root_URL
, pool
));
472 SVN_ERR(receiver(receiver_baton
, base_name
, info
, pool
));
474 /* Possibly recurse, using the original RA session. */
475 if (depth
> svn_depth_empty
&& (the_ent
->kind
== svn_node_dir
))
480 if (peg_revision
->kind
== svn_opt_revision_head
)
482 err
= svn_ra_get_locks(ra_session
, &locks
, "", pool
);
484 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
486 (err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
487 || err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
))
489 svn_error_clear(err
);
490 locks
= apr_hash_make(pool
); /* use an empty hash */
496 locks
= apr_hash_make(pool
); /* use an empty hash */
498 SVN_ERR(push_dir_info(ra_session
, url
, "", rev
,
499 repos_UUID
, repos_root_URL
,
500 receiver
, receiver_baton
,
501 depth
, ctx
, locks
, pool
));
509 svn_client_info(const char *path_or_url
,
510 const svn_opt_revision_t
*peg_revision
,
511 const svn_opt_revision_t
*revision
,
512 svn_info_receiver_t receiver
,
513 void *receiver_baton
,
514 svn_boolean_t recurse
,
515 svn_client_ctx_t
*ctx
,
518 return svn_client_info2(path_or_url
, peg_revision
, revision
,
519 receiver
, receiver_baton
,
520 SVN_DEPTH_INFINITY_OR_EMPTY(recurse
),
525 svn_info_dup(const svn_info_t
*info
, apr_pool_t
*pool
)
527 svn_info_t
*dupinfo
= apr_palloc(pool
, sizeof(*dupinfo
));
529 /* Perform a trivial copy ... */
532 /* ...and then re-copy stuff that needs to be duped into our pool. */
534 dupinfo
->URL
= apr_pstrdup(pool
, info
->URL
);
535 if (info
->repos_root_URL
)
536 dupinfo
->repos_root_URL
= apr_pstrdup(pool
, info
->repos_root_URL
);
537 if (info
->repos_UUID
)
538 dupinfo
->repos_UUID
= apr_pstrdup(pool
, info
->repos_UUID
);
539 if (info
->last_changed_author
)
540 dupinfo
->last_changed_author
= apr_pstrdup(pool
,
541 info
->last_changed_author
);
543 dupinfo
->lock
= svn_lock_dup(info
->lock
, pool
);
544 if (info
->copyfrom_url
)
545 dupinfo
->copyfrom_url
= apr_pstrdup(pool
, info
->copyfrom_url
);
547 dupinfo
->checksum
= apr_pstrdup(pool
, info
->checksum
);
548 if (info
->conflict_old
)
549 dupinfo
->conflict_old
= apr_pstrdup(pool
, info
->conflict_old
);
550 if (info
->conflict_new
)
551 dupinfo
->conflict_new
= apr_pstrdup(pool
, info
->conflict_new
);
552 if (info
->conflict_wrk
)
553 dupinfo
->conflict_wrk
= apr_pstrdup(pool
, info
->conflict_wrk
);
555 dupinfo
->prejfile
= apr_pstrdup(pool
, info
->prejfile
);