info.c revision 299742
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 145 if (op_root) 146 { 147 svn_error_t *err; 148 wc_info->copyfrom_url = 149 svn_path_url_add_component2(tmpinfo->repos_root_URL, 150 original_repos_relpath, 151 result_pool); 152 153 wc_info->copyfrom_rev = original_revision; 154 155 err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath, 156 NULL, NULL, NULL, 157 db, local_abspath, 158 result_pool, scratch_pool); 159 160 if (err) 161 { 162 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 163 return svn_error_trace(err); 164 svn_error_clear(err); 165 wc_info->moved_from_abspath = NULL; 166 } 167 } 168 } 169 170 /* ### We should be able to avoid both these calls with the information 171 from read_info() in most cases */ 172 if (! op_root) 173 wc_info->schedule = svn_wc_schedule_normal; 174 else if (! have_more_work && ! have_base) 175 wc_info->schedule = svn_wc_schedule_add; 176 else 177 { 178 svn_wc__db_status_t below_working; 179 svn_boolean_t have_work; 180 181 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, 182 &below_working, 183 db, local_abspath, 184 scratch_pool)); 185 186 /* If the node is not present or deleted (read: not present 187 in working), then the node is not a replacement */ 188 if (below_working != svn_wc__db_status_not_present 189 && below_working != svn_wc__db_status_deleted) 190 { 191 wc_info->schedule = svn_wc_schedule_replace; 192 } 193 else 194 wc_info->schedule = svn_wc_schedule_add; 195 } 196 SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath, 197 &tmpinfo->repos_root_URL, 198 &tmpinfo->repos_UUID, 199 db, local_abspath, 200 result_pool, scratch_pool)); 201 202 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 203 repos_relpath, result_pool); 204 } 205 else if (status == svn_wc__db_status_deleted) 206 { 207 svn_wc__db_status_t w_status; 208 209 SVN_ERR(svn_wc__db_read_pristine_info(&w_status, &tmpinfo->kind, 210 &tmpinfo->last_changed_rev, 211 &tmpinfo->last_changed_date, 212 &tmpinfo->last_changed_author, 213 &wc_info->depth, 214 &wc_info->checksum, 215 NULL, NULL, NULL, 216 db, local_abspath, 217 result_pool, scratch_pool)); 218 219 if (w_status == svn_wc__db_status_deleted) 220 { 221 /* We have a working not-present status. We don't know anything 222 about this node, but it *is visible* in STATUS. 223 224 Let's tell that it is excluded */ 225 226 wc_info->depth = svn_depth_exclude; 227 tmpinfo->kind = svn_node_unknown; 228 } 229 230 /* And now fetch the url and revision of what will be deleted */ 231 SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath, 232 NULL, NULL, 233 db, local_abspath, 234 scratch_pool, scratch_pool)); 235 236 SVN_ERR(svn_wc__db_read_repos_info(&tmpinfo->rev, &repos_relpath, 237 &tmpinfo->repos_root_URL, 238 &tmpinfo->repos_UUID, 239 db, local_abspath, 240 result_pool, scratch_pool)); 241 242 wc_info->schedule = svn_wc_schedule_delete; 243 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 244 repos_relpath, result_pool); 245 } 246 else if (status == svn_wc__db_status_not_present 247 || status == svn_wc__db_status_server_excluded) 248 { 249 *info = NULL; 250 return SVN_NO_ERROR; 251 } 252 else if (status == svn_wc__db_status_excluded && !repos_relpath) 253 { 254 /* We have a WORKING exclude. Avoid segfault on no repos info */ 255 256 SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath, 257 &tmpinfo->repos_root_URL, 258 &tmpinfo->repos_UUID, 259 db, local_abspath, 260 result_pool, scratch_pool)); 261 262 wc_info->schedule = svn_wc_schedule_normal; 263 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 264 repos_relpath, result_pool); 265 tmpinfo->wc_info->depth = svn_depth_exclude; 266 } 267 else 268 { 269 /* Just a BASE node. We have all the info we need */ 270 tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, 271 repos_relpath, 272 result_pool); 273 wc_info->schedule = svn_wc_schedule_normal; 274 275 if (status == svn_wc__db_status_excluded) 276 wc_info->depth = svn_depth_exclude; 277 } 278 279 /* A default */ 280 tmpinfo->size = SVN_INVALID_FILESIZE; 281 282 SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db, 283 local_abspath, result_pool, scratch_pool)); 284 285 if (conflicted) 286 SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, NULL, 287 db, local_abspath, 288 FALSE /* create tempfiles */, 289 FALSE /* only tree conflicts */, 290 result_pool, scratch_pool)); 291 else 292 wc_info->conflicts = NULL; 293 294 /* lock stuff */ 295 if (lock != NULL) 296 { 297 tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock))); 298 tmpinfo->lock->token = lock->token; 299 tmpinfo->lock->owner = lock->owner; 300 tmpinfo->lock->comment = lock->comment; 301 tmpinfo->lock->creation_date = lock->date; 302 } 303 304 *info = tmpinfo; 305 return SVN_NO_ERROR; 306} 307 308 309/* Set *INFO to a new struct with minimal content, to be 310 used in reporting info for unversioned tree conflict victims. */ 311/* ### Some fields we could fill out based on the parent dir's entry 312 or by looking at an obstructing item. */ 313static svn_error_t * 314build_info_for_unversioned(svn_wc__info2_t **info, 315 apr_pool_t *pool) 316{ 317 svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); 318 svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info)); 319 320 tmpinfo->URL = NULL; 321 tmpinfo->repos_UUID = NULL; 322 tmpinfo->repos_root_URL = NULL; 323 tmpinfo->rev = SVN_INVALID_REVNUM; 324 tmpinfo->kind = svn_node_none; 325 tmpinfo->size = SVN_INVALID_FILESIZE; 326 tmpinfo->last_changed_rev = SVN_INVALID_REVNUM; 327 tmpinfo->last_changed_date = 0; 328 tmpinfo->last_changed_author = NULL; 329 tmpinfo->lock = NULL; 330 331 tmpinfo->wc_info = wc_info; 332 333 wc_info->copyfrom_rev = SVN_INVALID_REVNUM; 334 wc_info->depth = svn_depth_unknown; 335 wc_info->recorded_size = SVN_INVALID_FILESIZE; 336 337 *info = tmpinfo; 338 return SVN_NO_ERROR; 339} 340 341/* Callback and baton for crawl_entries() walk over entries files. */ 342struct found_entry_baton 343{ 344 svn_wc__info_receiver2_t receiver; 345 void *receiver_baton; 346 svn_wc__db_t *db; 347 svn_boolean_t actual_only; 348 svn_boolean_t first; 349 /* The set of tree conflicts that have been found but not (yet) visited by 350 * the tree walker. Map of abspath -> empty string. */ 351 apr_hash_t *tree_conflicts; 352 apr_pool_t *pool; 353}; 354 355/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it 356 * info about the path LOCAL_ABSPATH. 357 * An svn_wc__node_found_func_t callback function. */ 358static svn_error_t * 359info_found_node_callback(const char *local_abspath, 360 svn_node_kind_t kind, 361 void *walk_baton, 362 apr_pool_t *scratch_pool) 363{ 364 struct found_entry_baton *fe_baton = walk_baton; 365 svn_wc__info2_t *info; 366 367 SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath, 368 kind, scratch_pool, scratch_pool)); 369 370 if (info == NULL) 371 { 372 if (!fe_baton->first) 373 return SVN_NO_ERROR; /* not present or server excluded descendant */ 374 375 /* If the info root is not found, that is an error */ 376 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 377 _("The node '%s' was not found."), 378 svn_dirent_local_style(local_abspath, 379 scratch_pool)); 380 } 381 382 fe_baton->first = FALSE; 383 384 SVN_ERR_ASSERT(info->wc_info != NULL); 385 SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath, 386 info, scratch_pool)); 387 388 /* If this node is a versioned directory, make a note of any tree conflicts 389 * on all immediate children. Some of these may be visited later in this 390 * walk, at which point they will be removed from the list, while any that 391 * are not visited will remain in the list. */ 392 if (fe_baton->actual_only && kind == svn_node_dir) 393 { 394 const apr_array_header_t *victims; 395 int i; 396 397 SVN_ERR(svn_wc__db_read_conflict_victims(&victims, 398 fe_baton->db, local_abspath, 399 scratch_pool, scratch_pool)); 400 401 for (i = 0; i < victims->nelts; i++) 402 { 403 const char *this_basename = APR_ARRAY_IDX(victims, i, const char *); 404 405 svn_hash_sets(fe_baton->tree_conflicts, 406 svn_dirent_join(local_abspath, this_basename, 407 fe_baton->pool), 408 ""); 409 } 410 } 411 412 /* Delete this path which we are currently visiting from the list of tree 413 * conflicts. This relies on the walker visiting a directory before visiting 414 * its children. */ 415 svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL); 416 417 return SVN_NO_ERROR; 418} 419 420 421/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH, 422 * would include the path CHILD_ABSPATH of kind CHILD_KIND. */ 423static svn_boolean_t 424depth_includes(const char *root_abspath, 425 svn_depth_t depth, 426 const char *child_abspath, 427 svn_node_kind_t child_kind, 428 apr_pool_t *scratch_pool) 429{ 430 const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool); 431 432 return (depth == svn_depth_infinity 433 || ((depth == svn_depth_immediates 434 || (depth == svn_depth_files && child_kind == svn_node_file)) 435 && strcmp(root_abspath, parent_abspath) == 0) 436 || strcmp(root_abspath, child_abspath) == 0); 437} 438 439 440svn_error_t * 441svn_wc__get_info(svn_wc_context_t *wc_ctx, 442 const char *local_abspath, 443 svn_depth_t depth, 444 svn_boolean_t fetch_excluded, 445 svn_boolean_t fetch_actual_only, 446 const apr_array_header_t *changelist_filter, 447 svn_wc__info_receiver2_t receiver, 448 void *receiver_baton, 449 svn_cancel_func_t cancel_func, 450 void *cancel_baton, 451 apr_pool_t *scratch_pool) 452{ 453 struct found_entry_baton fe_baton; 454 svn_error_t *err; 455 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 456 apr_hash_index_t *hi; 457 const char *repos_root_url = NULL; 458 const char *repos_uuid = NULL; 459 460 fe_baton.receiver = receiver; 461 fe_baton.receiver_baton = receiver_baton; 462 fe_baton.db = wc_ctx->db; 463 fe_baton.actual_only = fetch_actual_only; 464 fe_baton.first = TRUE; 465 fe_baton.tree_conflicts = apr_hash_make(scratch_pool); 466 fe_baton.pool = scratch_pool; 467 468 err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath, 469 fetch_excluded, 470 changelist_filter, 471 info_found_node_callback, 472 &fe_baton, depth, 473 cancel_func, cancel_baton, 474 iterpool); 475 476 /* If the target root node is not present, svn_wc__internal_walk_children() 477 returns a PATH_NOT_FOUND error and doesn't call the callback. If there 478 is a tree conflict on this node, that is not an error. */ 479 if (fe_baton.first /* not visited by walk_children */ 480 && fetch_actual_only 481 && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 482 { 483 svn_boolean_t tree_conflicted; 484 svn_error_t *err2; 485 486 err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted, 487 wc_ctx->db, local_abspath, 488 iterpool); 489 490 if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) 491 { 492 svn_error_clear(err2); 493 return svn_error_trace(err); 494 } 495 else if (err2 || !tree_conflicted) 496 return svn_error_compose_create(err, err2); 497 498 svn_error_clear(err); 499 500 svn_hash_sets(fe_baton.tree_conflicts, local_abspath, ""); 501 } 502 else 503 SVN_ERR(err); 504 505 /* If there are any tree conflicts that we have found but have not reported, 506 * send a minimal info struct for each one now. */ 507 for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi; 508 hi = apr_hash_next(hi)) 509 { 510 const char *this_abspath = apr_hash_this_key(hi); 511 const svn_wc_conflict_description2_t *tree_conflict; 512 svn_wc__info2_t *info; 513 const apr_array_header_t *conflicts; 514 515 svn_pool_clear(iterpool); 516 517 SVN_ERR(build_info_for_unversioned(&info, iterpool)); 518 519 if (!repos_root_url) 520 { 521 SVN_ERR(svn_wc__db_read_repos_info(NULL, NULL, 522 &repos_root_url, 523 &repos_uuid, 524 wc_ctx->db, 525 svn_dirent_dirname( 526 this_abspath, 527 iterpool), 528 scratch_pool, iterpool)); 529 } 530 531 info->repos_root_URL = repos_root_url; 532 info->repos_UUID = repos_uuid; 533 534 SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL, 535 wc_ctx->db, this_abspath, 536 FALSE /* create tempfiles */, 537 FALSE /* only tree conflicts */, 538 iterpool, iterpool)); 539 if (! conflicts || ! conflicts->nelts) 540 continue; 541 542 tree_conflict = APR_ARRAY_IDX(conflicts, 0, 543 const svn_wc_conflict_description2_t *); 544 545 if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, 546 tree_conflict->node_kind, iterpool)) 547 continue; 548 549 info->wc_info->conflicts = conflicts; 550 SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); 551 } 552 svn_pool_destroy(iterpool); 553 554 return SVN_NO_ERROR; 555} 556