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 172 173/* Helper func for recursively fetching svn_dirent_t's from a remote 174 directory and pushing them at an info-receiver callback. 175 176 DEPTH is the depth starting at DIR, even though RECEIVER is never 177 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke 178 RECEIVER on all children of DIR, but none of their children; if 179 svn_depth_files, then invoke RECEIVER on file children of DIR but 180 not on subdirectories; if svn_depth_infinity, recurse fully. 181 DIR is a relpath, relative to the root of RA_SESSION. 182*/ 183static svn_error_t * 184push_dir_info(svn_ra_session_t *ra_session, 185 const svn_client__pathrev_t *pathrev, 186 const char *dir, 187 svn_client_info_receiver2_t receiver, 188 void *receiver_baton, 189 svn_depth_t depth, 190 svn_client_ctx_t *ctx, 191 apr_hash_t *locks, 192 apr_pool_t *pool) 193{ 194 apr_hash_t *tmpdirents; 195 apr_hash_index_t *hi; 196 apr_pool_t *subpool = svn_pool_create(pool); 197 198 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL, 199 dir, pathrev->rev, DIRENT_FIELDS, pool)); 200 201 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi)) 202 { 203 const char *path, *fs_path; 204 svn_lock_t *lock; 205 svn_client_info2_t *info; 206 const char *name = apr_hash_this_key(hi); 207 svn_dirent_t *the_ent = apr_hash_this_val(hi); 208 svn_client__pathrev_t *child_pathrev; 209 210 svn_pool_clear(subpool); 211 212 if (ctx->cancel_func) 213 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 214 215 path = svn_relpath_join(dir, name, subpool); 216 child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool); 217 fs_path = svn_client__pathrev_fspath(child_pathrev, subpool); 218 219 lock = svn_hash_gets(locks, fs_path); 220 221 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev, 222 subpool)); 223 224 if (depth >= svn_depth_immediates 225 || (depth == svn_depth_files && the_ent->kind == svn_node_file)) 226 { 227 SVN_ERR(receiver(receiver_baton, path, info, subpool)); 228 } 229 230 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir) 231 { 232 SVN_ERR(push_dir_info(ra_session, child_pathrev, path, 233 receiver, receiver_baton, 234 depth, ctx, locks, subpool)); 235 } 236 } 237 238 svn_pool_destroy(subpool); 239 240 return SVN_NO_ERROR; 241} 242 243 244/* Set *SAME_P to TRUE if URL exists in the head of the repository and 245 refers to the same resource as it does in REV, using POOL for 246 temporary allocations. RA_SESSION is an open RA session for URL. */ 247static svn_error_t * 248same_resource_in_head(svn_boolean_t *same_p, 249 const char *url, 250 svn_revnum_t rev, 251 svn_ra_session_t *ra_session, 252 svn_client_ctx_t *ctx, 253 apr_pool_t *pool) 254{ 255 svn_error_t *err; 256 svn_opt_revision_t start_rev, peg_rev; 257 const char *head_url; 258 259 start_rev.kind = svn_opt_revision_head; 260 peg_rev.kind = svn_opt_revision_number; 261 peg_rev.value.number = rev; 262 263 err = svn_client__repos_locations(&head_url, NULL, NULL, NULL, 264 ra_session, 265 url, &peg_rev, 266 &start_rev, NULL, 267 ctx, pool); 268 if (err && 269 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) || 270 (err->apr_err == SVN_ERR_FS_NOT_FOUND))) 271 { 272 svn_error_clear(err); 273 *same_p = FALSE; 274 return SVN_NO_ERROR; 275 } 276 else 277 SVN_ERR(err); 278 279 /* ### Currently, the URLs should always be equal, since we can't 280 ### walk forwards in history. */ 281 *same_p = (strcmp(url, head_url) == 0); 282 283 return SVN_NO_ERROR; 284} 285 286/* A baton for wc_info_receiver(), containing the wrapped receiver. */ 287typedef struct wc_info_receiver_baton_t 288{ 289 svn_client_info_receiver2_t client_receiver_func; 290 void *client_receiver_baton; 291} wc_info_receiver_baton_t; 292 293/* A receiver for WC info, implementing svn_client_info_receiver2_t. 294 * Convert the WC info to client info and pass it to the client info 295 * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */ 296static svn_error_t * 297wc_info_receiver(void *baton, 298 const char *abspath_or_url, 299 const svn_wc__info2_t *wc_info, 300 apr_pool_t *scratch_pool) 301{ 302 wc_info_receiver_baton_t *b = baton; 303 svn_client_info2_t client_info; 304 305 /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */ 306 client_info.repos_root_URL = wc_info->repos_root_URL; 307 client_info.repos_UUID = wc_info->repos_UUID; 308 client_info.rev = wc_info->rev; 309 client_info.URL = wc_info->URL; 310 311 client_info.kind = wc_info->kind; 312 client_info.size = wc_info->size; 313 client_info.last_changed_rev = wc_info->last_changed_rev; 314 client_info.last_changed_date = wc_info->last_changed_date; 315 client_info.last_changed_author = wc_info->last_changed_author; 316 317 client_info.lock = wc_info->lock; 318 319 client_info.wc_info = wc_info->wc_info; 320 321 return b->client_receiver_func(b->client_receiver_baton, 322 abspath_or_url, &client_info, scratch_pool); 323} 324 325svn_error_t * 326svn_client_info4(const char *abspath_or_url, 327 const svn_opt_revision_t *peg_revision, 328 const svn_opt_revision_t *revision, 329 svn_depth_t depth, 330 svn_boolean_t fetch_excluded, 331 svn_boolean_t fetch_actual_only, 332 svn_boolean_t include_externals, 333 const apr_array_header_t *changelists, 334 svn_client_info_receiver2_t receiver, 335 void *receiver_baton, 336 svn_client_ctx_t *ctx, 337 apr_pool_t *pool) 338{ 339 svn_ra_session_t *ra_session; 340 svn_client__pathrev_t *pathrev; 341 svn_lock_t *lock; 342 svn_boolean_t related; 343 const char *base_name; 344 svn_dirent_t *the_ent; 345 svn_client_info2_t *info; 346 svn_error_t *err; 347 348 if (depth == svn_depth_unknown) 349 depth = svn_depth_empty; 350 351 if ((revision == NULL 352 || revision->kind == svn_opt_revision_unspecified) 353 && (peg_revision == NULL 354 || peg_revision->kind == svn_opt_revision_unspecified)) 355 { 356 /* Do all digging in the working copy. */ 357 wc_info_receiver_baton_t b; 358 359 b.client_receiver_func = receiver; 360 b.client_receiver_baton = receiver_baton; 361 SVN_ERR(svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth, 362 fetch_excluded, fetch_actual_only, changelists, 363 wc_info_receiver, &b, 364 ctx->cancel_func, ctx->cancel_baton, pool)); 365 366 if (include_externals && SVN_DEPTH_IS_RECURSIVE(depth)) 367 { 368 apr_hash_t *external_map; 369 370 SVN_ERR(svn_wc__externals_defined_below(&external_map, 371 ctx->wc_ctx, abspath_or_url, 372 pool, pool)); 373 374 SVN_ERR(do_external_info(external_map, 375 depth, fetch_excluded, fetch_actual_only, 376 changelists, 377 receiver, receiver_baton, ctx, pool)); 378 } 379 380 return SVN_NO_ERROR; 381 } 382 383 /* Go repository digging instead. */ 384 385 /* Trace rename history (starting at path_or_url@peg_revision) and 386 return RA session to the possibly-renamed URL as it exists in REVISION. 387 The ra_session returned will be anchored on this "final" URL. */ 388 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev, 389 abspath_or_url, NULL, peg_revision, 390 revision, ctx, pool)); 391 base_name = svn_uri_basename(pathrev->url, pool); 392 393 /* Get the dirent for the URL itself. */ 394 SVN_ERR(svn_ra_stat(ra_session, "", pathrev->rev, &the_ent, pool)); 395 396 if (! the_ent) 397 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 398 _("URL '%s' non-existent in revision %ld"), 399 pathrev->url, pathrev->rev); 400 401 /* Check if the URL exists in HEAD and refers to the same resource. 402 In this case, we check the repository for a lock on this URL. 403 404 ### There is a possible race here, since HEAD might have changed since 405 ### we checked it. A solution to this problem could be to do the below 406 ### check in a loop which only terminates if the HEAD revision is the same 407 ### before and after this check. That could, however, lead to a 408 ### starvation situation instead. */ 409 SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev, 410 ra_session, ctx, pool)); 411 if (related) 412 { 413 err = svn_ra_get_lock(ra_session, &lock, "", pool); 414 415 /* An old mod_dav_svn will always work; there's nothing wrong with 416 doing a PROPFIND for a property named "DAV:supportedlock". But 417 an old svnserve will error. */ 418 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) 419 { 420 svn_error_clear(err); 421 lock = NULL; 422 } 423 else if (err) 424 return svn_error_trace(err); 425 } 426 else 427 lock = NULL; 428 429 /* Push the URL's dirent (and lock) at the callback.*/ 430 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool)); 431 SVN_ERR(receiver(receiver_baton, base_name, info, pool)); 432 433 /* Possibly recurse, using the original RA session. */ 434 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir)) 435 { 436 apr_hash_t *locks; 437 438 if (peg_revision->kind == svn_opt_revision_head) 439 { 440 err = svn_ra_get_locks2(ra_session, &locks, "", depth, 441 pool); 442 443 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */ 444 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) 445 { 446 svn_error_clear(err); 447 locks = apr_hash_make(pool); /* use an empty hash */ 448 } 449 else if (err) 450 return svn_error_trace(err); 451 } 452 else 453 locks = apr_hash_make(pool); /* use an empty hash */ 454 455 SVN_ERR(push_dir_info(ra_session, pathrev, "", 456 receiver, receiver_baton, 457 depth, ctx, locks, pool)); 458 } 459 460 return SVN_NO_ERROR; 461} 462 463 464svn_error_t * 465svn_client_get_wc_root(const char **wcroot_abspath, 466 const char *local_abspath, 467 svn_client_ctx_t *ctx, 468 apr_pool_t *result_pool, 469 apr_pool_t *scratch_pool) 470{ 471 return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath, 472 result_pool, scratch_pool); 473} 474 475 476/* NOTE: This function was requested by the TortoiseSVN project. See 477 issue #3927. */ 478svn_error_t * 479svn_client_min_max_revisions(svn_revnum_t *min_revision, 480 svn_revnum_t *max_revision, 481 const char *local_abspath, 482 svn_boolean_t committed, 483 svn_client_ctx_t *ctx, 484 apr_pool_t *scratch_pool) 485{ 486 return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx, 487 local_abspath, committed, scratch_pool); 488} 489