1/** 2 * @copyright 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 * @endcopyright 22 */ 23 24#include "svn_dirent_uri.h" 25#include "svn_hash.h" 26#include "svn_path.h" 27#include "svn_pools.h" 28#include "svn_wc.h" 29 30#include "wc.h" 31 32#include "svn_private_config.h" 33#include "private/svn_wc_private.h" 34 35 36 37svn_wc_info_t * 38svn_wc_info_dup(const svn_wc_info_t *info, 39 apr_pool_t *pool) 40{ 41 svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info)); 42 43 if (info->changelist) 44 new_info->changelist = apr_pstrdup(pool, info->changelist); 45 new_info->checksum = svn_checksum_dup(info->checksum, pool); 46 if (info->conflicts) 47 { 48 int i; 49 50 apr_array_header_t *new_conflicts 51 = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size); 52 for (i = 0; i < info->conflicts->nelts; i++) 53 { 54 APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *) 55 = svn_wc__conflict_description2_dup( 56 APR_ARRAY_IDX(info->conflicts, i, 57 const svn_wc_conflict_description2_t *), 58 pool); 59 } 60 new_info->conflicts = new_conflicts; 61 } 62 if (info->copyfrom_url) 63 new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url); 64 if (info->wcroot_abspath) 65 new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath); 66 if (info->moved_from_abspath) 67 new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath); 68 if (info->moved_to_abspath) 69 new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath); 70 71 return new_info; 72} 73 74 75/* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC 76 metadata of LOCAL_ABSPATH. Pointer fields are copied by reference, not 77 dup'd. */ 78static svn_error_t * 79build_info_for_node(svn_wc__info2_t **info, 80 svn_wc__db_t *db, 81 const char *local_abspath, 82 svn_node_kind_t kind, 83 apr_pool_t *result_pool, 84 apr_pool_t *scratch_pool) 85{ 86 svn_wc__info2_t *tmpinfo; 87 const char *repos_relpath; 88 svn_wc__db_status_t status; 89 svn_node_kind_t db_kind; 90 const char *original_repos_relpath; 91 const char *original_repos_root_url; 92 const char *original_uuid; 93 svn_revnum_t original_revision; 94 svn_wc__db_lock_t *lock; 95 svn_boolean_t conflicted; 96 svn_boolean_t op_root; 97 svn_boolean_t have_base; 98 svn_boolean_t have_more_work; 99 svn_wc_info_t *wc_info; 100 101 tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo)); 102 tmpinfo->kind = kind; 103 104 wc_info = apr_pcalloc(result_pool, sizeof(*wc_info)); 105 tmpinfo->wc_info = wc_info; 106 107 wc_info->copyfrom_rev = SVN_INVALID_REVNUM; 108 109 SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev, 110 &repos_relpath, 111 &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID, 112 &tmpinfo->last_changed_rev, 113 &tmpinfo->last_changed_date, 114 &tmpinfo->last_changed_author, 115 &wc_info->depth, &wc_info->checksum, NULL, 116 &original_repos_relpath, 117 &original_repos_root_url, &original_uuid, 118 &original_revision, &lock, 119 &wc_info->recorded_size, 120 &wc_info->recorded_time, 121 &wc_info->changelist, 122 &conflicted, &op_root, NULL, NULL, 123 &have_base, &have_more_work, NULL, 124 db, local_abspath, 125 result_pool, scratch_pool)); 126 127 if (original_repos_root_url != NULL) 128 { 129 tmpinfo->repos_root_URL = original_repos_root_url; 130 tmpinfo->repos_UUID = original_uuid; 131 } 132 133 if (status == svn_wc__db_status_added) 134 { 135 /* ### We should also just be fetching the true BASE revision 136 ### here, which means copied items would also not have a 137 ### revision to display. But WC-1 wants to show the revision of 138 ### copy targets as the copyfrom-rev. *sigh* */ 139 140 if (original_repos_relpath) 141 { 142 /* Root or child of copy */ 143 tmpinfo->rev = original_revision; 144 repos_relpath = original_repos_relpath; 145 146 if (op_root) 147 { 148 svn_error_t *err; 149 wc_info->copyfrom_url = 150 svn_path_url_add_component2(tmpinfo->repos_root_URL, 151 original_repos_relpath, 152 result_pool); 153 154 wc_info->copyfrom_rev = original_revision; 155 156 err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath, 157 NULL, NULL, NULL, 158 db, local_abspath, 159 result_pool, scratch_pool); 160 161 if (err) 162 { 163 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 164 return svn_error_trace(err); 165 svn_error_clear(err); 166 wc_info->moved_from_abspath = NULL; 167 } 168 } 169 } 170 else if (op_root) 171 { 172 /* Local addition */ 173 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, 174 &tmpinfo->repos_root_URL, 175 &tmpinfo->repos_UUID, 176 NULL, NULL, NULL, NULL, 177 db, local_abspath, 178 result_pool, scratch_pool)); 179 180 if (have_base) 181 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL, 182 NULL, NULL, NULL, NULL, NULL, 183 NULL, NULL, NULL, NULL, NULL, 184 NULL, NULL, 185 db, local_abspath, 186 scratch_pool, scratch_pool)); 187 } 188 else 189 { 190 /* Child of copy. ### Not WC-NG like */ 191 SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev, 192 &repos_relpath, 193 &tmpinfo->repos_root_URL, 194 &tmpinfo->repos_UUID, NULL, 195 db, local_abspath, TRUE, 196 result_pool, scratch_pool)); 197 } 198 199 /* ### We should be able to avoid both these calls with the information 200 from read_info() in most cases */ 201 if (! op_root) 202 wc_info->schedule = svn_wc_schedule_normal; 203 else if (! have_more_work && ! have_base) 204 wc_info->schedule = svn_wc_schedule_add; 205 else 206 { 207 svn_wc__db_status_t below_working; 208 svn_boolean_t have_work; 209 210 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, 211 &below_working, 212 db, local_abspath, 213 scratch_pool)); 214 215 /* If the node is not present or deleted (read: not present 216 in working), then the node is not a replacement */ 217 if (below_working != svn_wc__db_status_not_present 218 && below_working != svn_wc__db_status_deleted) 219 { 220 wc_info->schedule = svn_wc_schedule_replace; 221 } 222 else 223 wc_info->schedule = svn_wc_schedule_add; 224 } 225 SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath, 226 result_pool, scratch_pool)); 227 } 228 else if (status == svn_wc__db_status_deleted) 229 { 230 const char *work_del_abspath; 231 232 SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, 233 &tmpinfo->last_changed_rev, 234 &tmpinfo->last_changed_date, 235 &tmpinfo->last_changed_author, 236 &wc_info->depth, 237 &wc_info->checksum, 238 NULL, NULL, NULL, 239 db, local_abspath, 240 result_pool, scratch_pool)); 241 242 /* And now fetch the url and revision of what will be deleted */ 243 SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath, 244 &work_del_abspath, NULL, 245 db, local_abspath, 246 scratch_pool, scratch_pool)); 247 if (work_del_abspath != NULL) 248 { 249 /* This is a deletion within a copied subtree. Get the copied-from 250 * revision. */ 251 const char *added_abspath = svn_dirent_dirname(work_del_abspath, 252 scratch_pool); 253 254 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, 255 &tmpinfo->repos_root_URL, 256 &tmpinfo->repos_UUID, 257 NULL, NULL, NULL, 258 &tmpinfo->rev, 259 db, added_abspath, 260 result_pool, scratch_pool)); 261 262 tmpinfo->URL = svn_path_url_add_component2( 263 tmpinfo->repos_root_URL, 264 svn_relpath_join(repos_relpath, 265 svn_dirent_skip_ancestor(added_abspath, 266 local_abspath), 267 scratch_pool), 268 result_pool); 269 } 270 else 271 { 272 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, 273 &repos_relpath, 274 &tmpinfo->repos_root_URL, 275 &tmpinfo->repos_UUID, NULL, NULL, 276 NULL, NULL, NULL, NULL, 277 NULL, NULL, NULL, NULL, 278 db, local_abspath, 279 result_pool, scratch_pool)); 280 281 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 282 repos_relpath, 283 result_pool); 284 } 285 286 wc_info->schedule = svn_wc_schedule_delete; 287 } 288 else if (status == svn_wc__db_status_not_present 289 || status == svn_wc__db_status_server_excluded) 290 { 291 *info = NULL; 292 return SVN_NO_ERROR; 293 } 294 else 295 { 296 /* Just a BASE node. We have all the info we need */ 297 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 298 repos_relpath, 299 result_pool); 300 wc_info->schedule = svn_wc_schedule_normal; 301 } 302 303 if (status == svn_wc__db_status_excluded) 304 tmpinfo->wc_info->depth = svn_depth_exclude; 305 306 /* A default */ 307 tmpinfo->size = SVN_INVALID_FILESIZE; 308 309 SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db, 310 local_abspath, result_pool, scratch_pool)); 311 312 if (conflicted) 313 SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db, 314 local_abspath, 315 TRUE /* ### create tempfiles */, 316 result_pool, scratch_pool)); 317 else 318 wc_info->conflicts = NULL; 319 320 /* lock stuff */ 321 if (lock != NULL) 322 { 323 tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock))); 324 tmpinfo->lock->token = lock->token; 325 tmpinfo->lock->owner = lock->owner; 326 tmpinfo->lock->comment = lock->comment; 327 tmpinfo->lock->creation_date = lock->date; 328 } 329 330 *info = tmpinfo; 331 return SVN_NO_ERROR; 332} 333 334 335/* Set *INFO to a new struct with minimal content, to be 336 used in reporting info for unversioned tree conflict victims. */ 337/* ### Some fields we could fill out based on the parent dir's entry 338 or by looking at an obstructing item. */ 339static svn_error_t * 340build_info_for_unversioned(svn_wc__info2_t **info, 341 apr_pool_t *pool) 342{ 343 svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); 344 svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info)); 345 346 tmpinfo->URL = NULL; 347 tmpinfo->repos_UUID = NULL; 348 tmpinfo->repos_root_URL = NULL; 349 tmpinfo->rev = SVN_INVALID_REVNUM; 350 tmpinfo->kind = svn_node_none; 351 tmpinfo->size = SVN_INVALID_FILESIZE; 352 tmpinfo->last_changed_rev = SVN_INVALID_REVNUM; 353 tmpinfo->last_changed_date = 0; 354 tmpinfo->last_changed_author = NULL; 355 tmpinfo->lock = NULL; 356 357 tmpinfo->wc_info = wc_info; 358 359 wc_info->copyfrom_rev = SVN_INVALID_REVNUM; 360 wc_info->depth = svn_depth_unknown; 361 wc_info->recorded_size = SVN_INVALID_FILESIZE; 362 363 *info = tmpinfo; 364 return SVN_NO_ERROR; 365} 366 367/* Callback and baton for crawl_entries() walk over entries files. */ 368struct found_entry_baton 369{ 370 svn_wc__info_receiver2_t receiver; 371 void *receiver_baton; 372 svn_wc__db_t *db; 373 svn_boolean_t actual_only; 374 svn_boolean_t first; 375 /* The set of tree conflicts that have been found but not (yet) visited by 376 * the tree walker. Map of abspath -> svn_wc_conflict_description2_t. */ 377 apr_hash_t *tree_conflicts; 378 apr_pool_t *pool; 379}; 380 381/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it 382 * info about the path LOCAL_ABSPATH. 383 * An svn_wc__node_found_func_t callback function. */ 384static svn_error_t * 385info_found_node_callback(const char *local_abspath, 386 svn_node_kind_t kind, 387 void *walk_baton, 388 apr_pool_t *scratch_pool) 389{ 390 struct found_entry_baton *fe_baton = walk_baton; 391 svn_wc__info2_t *info; 392 393 SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath, 394 kind, scratch_pool, scratch_pool)); 395 396 if (info == NULL) 397 { 398 if (!fe_baton->first) 399 return SVN_NO_ERROR; /* not present or server excluded descendant */ 400 401 /* If the info root is not found, that is an error */ 402 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 403 _("The node '%s' was not found."), 404 svn_dirent_local_style(local_abspath, 405 scratch_pool)); 406 } 407 408 fe_baton->first = FALSE; 409 410 SVN_ERR_ASSERT(info->wc_info != NULL); 411 SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath, 412 info, scratch_pool)); 413 414 /* If this node is a versioned directory, make a note of any tree conflicts 415 * on all immediate children. Some of these may be visited later in this 416 * walk, at which point they will be removed from the list, while any that 417 * are not visited will remain in the list. */ 418 if (fe_baton->actual_only && kind == svn_node_dir) 419 { 420 const apr_array_header_t *victims; 421 int i; 422 423 SVN_ERR(svn_wc__db_read_conflict_victims(&victims, 424 fe_baton->db, local_abspath, 425 scratch_pool, scratch_pool)); 426 427 for (i = 0; i < victims->nelts; i++) 428 { 429 const char *this_basename = APR_ARRAY_IDX(victims, i, const char *); 430 431 svn_hash_sets(fe_baton->tree_conflicts, 432 svn_dirent_join(local_abspath, this_basename, 433 fe_baton->pool), 434 ""); 435 } 436 } 437 438 /* Delete this path which we are currently visiting from the list of tree 439 * conflicts. This relies on the walker visiting a directory before visiting 440 * its children. */ 441 svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL); 442 443 return SVN_NO_ERROR; 444} 445 446 447/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH, 448 * would include the path CHILD_ABSPATH of kind CHILD_KIND. */ 449static svn_boolean_t 450depth_includes(const char *root_abspath, 451 svn_depth_t depth, 452 const char *child_abspath, 453 svn_node_kind_t child_kind, 454 apr_pool_t *scratch_pool) 455{ 456 const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool); 457 458 return (depth == svn_depth_infinity 459 || ((depth == svn_depth_immediates 460 || (depth == svn_depth_files && child_kind == svn_node_file)) 461 && strcmp(root_abspath, parent_abspath) == 0) 462 || strcmp(root_abspath, child_abspath) == 0); 463} 464 465 466svn_error_t * 467svn_wc__get_info(svn_wc_context_t *wc_ctx, 468 const char *local_abspath, 469 svn_depth_t depth, 470 svn_boolean_t fetch_excluded, 471 svn_boolean_t fetch_actual_only, 472 const apr_array_header_t *changelist_filter, 473 svn_wc__info_receiver2_t receiver, 474 void *receiver_baton, 475 svn_cancel_func_t cancel_func, 476 void *cancel_baton, 477 apr_pool_t *scratch_pool) 478{ 479 struct found_entry_baton fe_baton; 480 svn_error_t *err; 481 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 482 apr_hash_index_t *hi; 483 const char *repos_root_url = NULL; 484 const char *repos_uuid = NULL; 485 486 fe_baton.receiver = receiver; 487 fe_baton.receiver_baton = receiver_baton; 488 fe_baton.db = wc_ctx->db; 489 fe_baton.actual_only = fetch_actual_only; 490 fe_baton.first = TRUE; 491 fe_baton.tree_conflicts = apr_hash_make(scratch_pool); 492 fe_baton.pool = scratch_pool; 493 494 err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath, 495 fetch_excluded, 496 changelist_filter, 497 info_found_node_callback, 498 &fe_baton, depth, 499 cancel_func, cancel_baton, 500 iterpool); 501 502 /* If the target root node is not present, svn_wc__internal_walk_children() 503 returns a PATH_NOT_FOUND error and doesn't call the callback. If there 504 is a tree conflict on this node, that is not an error. */ 505 if (fe_baton.first /* not visited by walk_children */ 506 && fetch_actual_only 507 && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 508 { 509 svn_boolean_t tree_conflicted; 510 svn_error_t *err2; 511 512 err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted, 513 wc_ctx->db, local_abspath, 514 iterpool); 515 516 if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) 517 { 518 svn_error_clear(err2); 519 return svn_error_trace(err); 520 } 521 else if (err2 || !tree_conflicted) 522 return svn_error_compose_create(err, err2); 523 524 svn_error_clear(err); 525 526 svn_hash_sets(fe_baton.tree_conflicts, local_abspath, ""); 527 } 528 else 529 SVN_ERR(err); 530 531 /* If there are any tree conflicts that we have found but have not reported, 532 * send a minimal info struct for each one now. */ 533 for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi; 534 hi = apr_hash_next(hi)) 535 { 536 const char *this_abspath = svn__apr_hash_index_key(hi); 537 const svn_wc_conflict_description2_t *tree_conflict; 538 svn_wc__info2_t *info; 539 540 svn_pool_clear(iterpool); 541 542 SVN_ERR(build_info_for_unversioned(&info, iterpool)); 543 544 if (!repos_root_url) 545 { 546 SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL, 547 &repos_root_url, 548 &repos_uuid, 549 wc_ctx->db, 550 svn_dirent_dirname( 551 this_abspath, 552 iterpool), 553 scratch_pool, 554 iterpool)); 555 } 556 557 info->repos_root_URL = repos_root_url; 558 info->repos_UUID = repos_uuid; 559 560 SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts, 561 wc_ctx->db, this_abspath, 562 TRUE /* ### create tempfiles */, 563 iterpool, iterpool)); 564 565 if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts) 566 continue; 567 568 tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0, 569 svn_wc_conflict_description2_t *); 570 571 if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, 572 tree_conflict->node_kind, iterpool)) 573 continue; 574 575 SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); 576 } 577 svn_pool_destroy(iterpool); 578 579 return SVN_NO_ERROR; 580} 581