1/* 2 * info.c: return system-generated metadata about paths or URLs. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28#include "client.h" 29#include "svn_client.h" 30#include "svn_dirent_uri.h" 31#include "svn_hash.h" 32#include "svn_pools.h" 33#include "svn_sorts.h" 34 35#include "svn_wc.h" 36 37#include "svn_private_config.h" 38#include "private/svn_fspath.h" 39#include "private/svn_sorts_private.h" 40#include "private/svn_wc_private.h" 41 42 43svn_client_info2_t * 44svn_client_info2_dup(const svn_client_info2_t *info, 45 apr_pool_t *pool) 46{ 47 svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info)); 48 49 if (new_info->URL) 50 new_info->URL = apr_pstrdup(pool, info->URL); 51 if (new_info->repos_root_URL) 52 new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL); 53 if (new_info->repos_UUID) 54 new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID); 55 if (info->last_changed_author) 56 new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author); 57 if (new_info->lock) 58 new_info->lock = svn_lock_dup(info->lock, pool); 59 if (new_info->wc_info) 60 new_info->wc_info = svn_wc_info_dup(info->wc_info, pool); 61 return new_info; 62} 63 64/* Handle externals for svn_client_info4() */ 65 66static svn_error_t * 67do_external_info(apr_hash_t *external_map, 68 svn_depth_t depth, 69 svn_boolean_t fetch_excluded, 70 svn_boolean_t fetch_actual_only, 71 const apr_array_header_t *changelists, 72 svn_client_info_receiver2_t receiver, 73 void *receiver_baton, 74 svn_client_ctx_t *ctx, 75 apr_pool_t *scratch_pool) 76{ 77 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 78 apr_array_header_t *externals; 79 int i; 80 81 externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically, 82 scratch_pool); 83 84 /* Loop over the hash of new values (we don't care about the old 85 ones). This is a mapping of versioned directories to property 86 values. */ 87 for (i = 0; i < externals->nelts; i++) 88 { 89 svn_node_kind_t external_kind; 90 svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t); 91 const char *local_abspath = item.key; 92 const char *defining_abspath = item.value; 93 svn_opt_revision_t opt_rev; 94 svn_node_kind_t kind; 95 96 svn_pool_clear(iterpool); 97 98 /* Obtain information on the expected external. */ 99 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, 100 &opt_rev.value.number, 101 ctx->wc_ctx, defining_abspath, 102 local_abspath, FALSE, 103 iterpool, iterpool)); 104 105 if (external_kind != svn_node_dir) 106 continue; 107 108 SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool)); 109 if (kind != svn_node_dir) 110 continue; 111 112 /* Tell the client we're starting an external info. */ 113 if (ctx->notify_func2) 114 ctx->notify_func2( 115 ctx->notify_baton2, 116 svn_wc_create_notify(local_abspath, 117 svn_wc_notify_info_external, 118 iterpool), iterpool); 119 120 SVN_ERR(svn_client_info4(local_abspath, 121 NULL /* peg_revision */, 122 NULL /* revision */, 123 depth, 124 fetch_excluded, 125 fetch_actual_only, 126 TRUE /* include_externals */, 127 changelists, 128 receiver, receiver_baton, 129 ctx, iterpool)); 130 } 131 132 svn_pool_destroy(iterpool); 133 return SVN_NO_ERROR; 134} 135 136/* Set *INFO to a new info struct built from DIRENT 137 and (possibly NULL) svn_lock_t LOCK, all allocated in POOL. 138 Pointer fields are copied by reference, not dup'd. */ 139static svn_error_t * 140build_info_from_dirent(svn_client_info2_t **info, 141 const svn_dirent_t *dirent, 142 svn_lock_t *lock, 143 const svn_client__pathrev_t *pathrev, 144 apr_pool_t *pool) 145{ 146 svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); 147 148 tmpinfo->URL = pathrev->url; 149 tmpinfo->rev = pathrev->rev; 150 tmpinfo->kind = dirent->kind; 151 tmpinfo->repos_UUID = pathrev->repos_uuid; 152 tmpinfo->repos_root_URL = pathrev->repos_root_url; 153 tmpinfo->last_changed_rev = dirent->created_rev; 154 tmpinfo->last_changed_date = dirent->time; 155 tmpinfo->last_changed_author = dirent->last_author; 156 tmpinfo->lock = lock; 157 tmpinfo->size = dirent->size; 158 159 tmpinfo->wc_info = NULL; 160 161 *info = tmpinfo; 162 return SVN_NO_ERROR; 163} 164 165 166/* The dirent fields we care about for our calls to svn_ra_get_dir2. */ 167#define DIRENT_FIELDS (SVN_DIRENT_KIND | \ 168 SVN_DIRENT_CREATED_REV | \ 169 SVN_DIRENT_TIME | \ 170 SVN_DIRENT_LAST_AUTHOR | \ 171 SVN_DIRENT_SIZE) 172 173 174/* Helper func for recursively fetching svn_dirent_t's from a remote 175 directory and pushing them at an info-receiver callback. 176 177 DEPTH is the depth starting at DIR, even though RECEIVER is never 178 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke 179 RECEIVER on all children of DIR, but none of their children; if 180 svn_depth_files, then invoke RECEIVER on file children of DIR but 181 not on subdirectories; if svn_depth_infinity, recurse fully. 182 DIR is a relpath, relative to the root of RA_SESSION. 183*/ 184static svn_error_t * 185push_dir_info(svn_ra_session_t *ra_session, 186 const svn_client__pathrev_t *pathrev, 187 const char *dir, 188 svn_client_info_receiver2_t receiver, 189 void *receiver_baton, 190 svn_depth_t depth, 191 svn_client_ctx_t *ctx, 192 apr_hash_t *locks, 193 apr_pool_t *pool) 194{ 195 apr_hash_t *tmpdirents; 196 apr_hash_index_t *hi; 197 apr_pool_t *subpool = svn_pool_create(pool); 198 199 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL, 200 dir, pathrev->rev, DIRENT_FIELDS, pool)); 201 202 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi)) 203 { 204 const char *path, *fs_path; 205 svn_lock_t *lock; 206 svn_client_info2_t *info; 207 const char *name = apr_hash_this_key(hi); 208 svn_dirent_t *the_ent = apr_hash_this_val(hi); 209 svn_client__pathrev_t *child_pathrev; 210 211 svn_pool_clear(subpool); 212 213 if (ctx->cancel_func) 214 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 215 216 path = svn_relpath_join(dir, name, subpool); 217 child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool); 218 fs_path = svn_client__pathrev_fspath(child_pathrev, subpool); 219 220 lock = svn_hash_gets(locks, fs_path); 221 222 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev, 223 subpool)); 224 225 if (depth >= svn_depth_immediates 226 || (depth == svn_depth_files && the_ent->kind == svn_node_file)) 227 { 228 SVN_ERR(receiver(receiver_baton, path, info, subpool)); 229 } 230 231 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir) 232 { 233 SVN_ERR(push_dir_info(ra_session, child_pathrev, path, 234 receiver, receiver_baton, 235 depth, ctx, locks, subpool)); 236 } 237 } 238 239 svn_pool_destroy(subpool); 240 241 return SVN_NO_ERROR; 242} 243 244 245/* Set *SAME_P to TRUE if URL exists in the head of the repository and 246 refers to the same resource as it does in REV, using POOL for 247 temporary allocations. RA_SESSION is an open RA session for URL. */ 248static svn_error_t * 249same_resource_in_head(svn_boolean_t *same_p, 250 const char *url, 251 svn_revnum_t rev, 252 svn_ra_session_t *ra_session, 253 svn_client_ctx_t *ctx, 254 apr_pool_t *pool) 255{ 256 svn_error_t *err; 257 svn_opt_revision_t operative_rev, peg_rev; 258 const char *head_url; 259 260 peg_rev.kind = svn_opt_revision_head; 261 operative_rev.kind = svn_opt_revision_number; 262 operative_rev.value.number = rev; 263 264 err = svn_client__repos_locations(&head_url, NULL, NULL, NULL, 265 ra_session, 266 url, &peg_rev, 267 &operative_rev, NULL, 268 ctx, pool); 269 if (err && 270 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) || 271 (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY) || 272 (err->apr_err == SVN_ERR_FS_NOT_FOUND))) 273 { 274 svn_error_clear(err); 275 *same_p = FALSE; 276 return SVN_NO_ERROR; 277 } 278 else 279 SVN_ERR(err); 280 281 /* ### Currently, the URLs should always be equal, since we can't 282 ### walk forwards in history. */ 283 *same_p = (strcmp(url, head_url) == 0); 284 285 return SVN_NO_ERROR; 286} 287 288/* A baton for wc_info_receiver(), containing the wrapped receiver. */ 289typedef struct wc_info_receiver_baton_t 290{ 291 svn_client_info_receiver2_t client_receiver_func; 292 void *client_receiver_baton; 293} wc_info_receiver_baton_t; 294 295/* A receiver for WC info, implementing svn_client_info_receiver2_t. 296 * Convert the WC info to client info and pass it to the client info 297 * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */ 298static svn_error_t * 299wc_info_receiver(void *baton, 300 const char *abspath_or_url, 301 const svn_wc__info2_t *wc_info, 302 apr_pool_t *scratch_pool) 303{ 304 wc_info_receiver_baton_t *b = baton; 305 svn_client_info2_t client_info; 306 307 /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */ 308 client_info.repos_root_URL = wc_info->repos_root_URL; 309 client_info.repos_UUID = wc_info->repos_UUID; 310 client_info.rev = wc_info->rev; 311 client_info.URL = wc_info->URL; 312 313 client_info.kind = wc_info->kind; 314 client_info.size = wc_info->size; 315 client_info.last_changed_rev = wc_info->last_changed_rev; 316 client_info.last_changed_date = wc_info->last_changed_date; 317 client_info.last_changed_author = wc_info->last_changed_author; 318 319 client_info.lock = wc_info->lock; 320 321 client_info.wc_info = wc_info->wc_info; 322 323 return b->client_receiver_func(b->client_receiver_baton, 324 abspath_or_url, &client_info, scratch_pool); 325} 326 327svn_error_t * 328svn_client_info4(const char *abspath_or_url, 329 const svn_opt_revision_t *peg_revision, 330 const svn_opt_revision_t *revision, 331 svn_depth_t depth, 332 svn_boolean_t fetch_excluded, 333 svn_boolean_t fetch_actual_only, 334 svn_boolean_t include_externals, 335 const apr_array_header_t *changelists, 336 svn_client_info_receiver2_t receiver, 337 void *receiver_baton, 338 svn_client_ctx_t *ctx, 339 apr_pool_t *pool) 340{ 341 svn_ra_session_t *ra_session; 342 svn_client__pathrev_t *pathrev; 343 svn_lock_t *lock; 344 svn_boolean_t related; 345 const char *base_name; 346 svn_dirent_t *the_ent; 347 svn_client_info2_t *info; 348 svn_error_t *err; 349 350 if (depth == svn_depth_unknown) 351 depth = svn_depth_empty; 352 353 if ((revision == NULL 354 || revision->kind == svn_opt_revision_unspecified) 355 && (peg_revision == NULL 356 || peg_revision->kind == svn_opt_revision_unspecified)) 357 { 358 /* Do all digging in the working copy. */ 359 wc_info_receiver_baton_t b; 360 361 b.client_receiver_func = receiver; 362 b.client_receiver_baton = receiver_baton; 363 SVN_ERR(svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth, 364 fetch_excluded, fetch_actual_only, changelists, 365 wc_info_receiver, &b, 366 ctx->cancel_func, ctx->cancel_baton, pool)); 367 368 if (include_externals && SVN_DEPTH_IS_RECURSIVE(depth)) 369 { 370 apr_hash_t *external_map; 371 372 SVN_ERR(svn_wc__externals_defined_below(&external_map, 373 ctx->wc_ctx, abspath_or_url, 374 pool, pool)); 375 376 SVN_ERR(do_external_info(external_map, 377 depth, fetch_excluded, fetch_actual_only, 378 changelists, 379 receiver, receiver_baton, ctx, pool)); 380 } 381 382 return SVN_NO_ERROR; 383 } 384 385 /* Go repository digging instead. */ 386 387 /* Trace rename history (starting at path_or_url@peg_revision) and 388 return RA session to the possibly-renamed URL as it exists in REVISION. 389 The ra_session returned will be anchored on this "final" URL. */ 390 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev, 391 abspath_or_url, NULL, peg_revision, 392 revision, ctx, pool)); 393 base_name = svn_uri_basename(pathrev->url, pool); 394 395 /* Get the dirent for the URL itself. */ 396 SVN_ERR(svn_ra_stat(ra_session, "", pathrev->rev, &the_ent, pool)); 397 398 if (! the_ent) 399 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 400 _("URL '%s' non-existent in revision %ld"), 401 pathrev->url, pathrev->rev); 402 403 /* Check if the URL exists in HEAD and refers to the same resource. 404 In this case, we check the repository for a lock on this URL. 405 406 ### There is a possible race here, since HEAD might have changed since 407 ### we checked it. A solution to this problem could be to do the below 408 ### check in a loop which only terminates if the HEAD revision is the same 409 ### before and after this check. That could, however, lead to a 410 ### starvation situation instead. */ 411 SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev, 412 ra_session, ctx, pool)); 413 if (related) 414 { 415 err = svn_ra_get_lock(ra_session, &lock, "", pool); 416 417 /* An old mod_dav_svn will always work; there's nothing wrong with 418 doing a PROPFIND for a property named "DAV:supportedlock". But 419 an old svnserve will error. */ 420 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) 421 { 422 svn_error_clear(err); 423 lock = NULL; 424 } 425 else if (err) 426 return svn_error_trace(err); 427 } 428 else 429 lock = NULL; 430 431 /* Push the URL's dirent (and lock) at the callback.*/ 432 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool)); 433 SVN_ERR(receiver(receiver_baton, base_name, info, pool)); 434 435 /* Possibly recurse, using the original RA session. */ 436 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir)) 437 { 438 apr_hash_t *locks; 439 440 if (peg_revision->kind == svn_opt_revision_head) 441 { 442 err = svn_ra_get_locks2(ra_session, &locks, "", depth, 443 pool); 444 445 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */ 446 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) 447 { 448 svn_error_clear(err); 449 locks = apr_hash_make(pool); /* use an empty hash */ 450 } 451 else if (err) 452 return svn_error_trace(err); 453 } 454 else 455 locks = apr_hash_make(pool); /* use an empty hash */ 456 457 SVN_ERR(push_dir_info(ra_session, pathrev, "", 458 receiver, receiver_baton, 459 depth, ctx, locks, pool)); 460 } 461 462 return SVN_NO_ERROR; 463} 464 465 466svn_error_t * 467svn_client_get_wc_root(const char **wcroot_abspath, 468 const char *local_abspath, 469 svn_client_ctx_t *ctx, 470 apr_pool_t *result_pool, 471 apr_pool_t *scratch_pool) 472{ 473 return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath, 474 result_pool, scratch_pool); 475} 476 477 478/* NOTE: This function was requested by the TortoiseSVN project. See 479 issue #3927. */ 480svn_error_t * 481svn_client_min_max_revisions(svn_revnum_t *min_revision, 482 svn_revnum_t *max_revision, 483 const char *local_abspath, 484 svn_boolean_t committed, 485 svn_client_ctx_t *ctx, 486 apr_pool_t *scratch_pool) 487{ 488 return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx, 489 local_abspath, committed, scratch_pool); 490} 491