old-and-busted.c revision 299742
1/* 2 * old-and-busted.c: routines for reading pre-1.7 working copies. 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#include "svn_time.h" 27#include "svn_xml.h" 28#include "svn_dirent_uri.h" 29#include "svn_hash.h" 30#include "svn_path.h" 31#include "svn_ctype.h" 32#include "svn_pools.h" 33 34#include "wc.h" 35#include "adm_files.h" 36#include "entries.h" 37#include "lock.h" 38 39#include "private/svn_wc_private.h" 40#include "svn_private_config.h" 41 42 43/* Within the (old) entries file, boolean values have a specific string 44 value (thus, TRUE), or they are missing (for FALSE). Below are the 45 values for each of the booleans stored. */ 46#define ENTRIES_BOOL_COPIED "copied" 47#define ENTRIES_BOOL_DELETED "deleted" 48#define ENTRIES_BOOL_ABSENT "absent" 49#define ENTRIES_BOOL_INCOMPLETE "incomplete" 50#define ENTRIES_BOOL_KEEP_LOCAL "keep-local" 51 52/* Tag names used in our old XML entries file. */ 53#define ENTRIES_TAG_ENTRY "entry" 54 55/* Attribute names used in our old XML entries file. */ 56#define ENTRIES_ATTR_NAME "name" 57#define ENTRIES_ATTR_REPOS "repos" 58#define ENTRIES_ATTR_UUID "uuid" 59#define ENTRIES_ATTR_INCOMPLETE "incomplete" 60#define ENTRIES_ATTR_LOCK_TOKEN "lock-token" 61#define ENTRIES_ATTR_LOCK_OWNER "lock-owner" 62#define ENTRIES_ATTR_LOCK_COMMENT "lock-comment" 63#define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date" 64#define ENTRIES_ATTR_DELETED "deleted" 65#define ENTRIES_ATTR_ABSENT "absent" 66#define ENTRIES_ATTR_CMT_REV "committed-rev" 67#define ENTRIES_ATTR_CMT_DATE "committed-date" 68#define ENTRIES_ATTR_CMT_AUTHOR "last-author" 69#define ENTRIES_ATTR_REVISION "revision" 70#define ENTRIES_ATTR_URL "url" 71#define ENTRIES_ATTR_KIND "kind" 72#define ENTRIES_ATTR_SCHEDULE "schedule" 73#define ENTRIES_ATTR_COPIED "copied" 74#define ENTRIES_ATTR_COPYFROM_URL "copyfrom-url" 75#define ENTRIES_ATTR_COPYFROM_REV "copyfrom-rev" 76#define ENTRIES_ATTR_CHECKSUM "checksum" 77#define ENTRIES_ATTR_WORKING_SIZE "working-size" 78#define ENTRIES_ATTR_TEXT_TIME "text-time" 79#define ENTRIES_ATTR_CONFLICT_OLD "conflict-old" /* saved old file */ 80#define ENTRIES_ATTR_CONFLICT_NEW "conflict-new" /* saved new file */ 81#define ENTRIES_ATTR_CONFLICT_WRK "conflict-wrk" /* saved wrk file */ 82#define ENTRIES_ATTR_PREJFILE "prop-reject-file" 83 84/* Attribute values used in our old XML entries file. */ 85#define ENTRIES_VALUE_FILE "file" 86#define ENTRIES_VALUE_DIR "dir" 87#define ENTRIES_VALUE_ADD "add" 88#define ENTRIES_VALUE_DELETE "delete" 89#define ENTRIES_VALUE_REPLACE "replace" 90 91 92/* */ 93static svn_wc_entry_t * 94alloc_entry(apr_pool_t *pool) 95{ 96 svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry)); 97 entry->revision = SVN_INVALID_REVNUM; 98 entry->copyfrom_rev = SVN_INVALID_REVNUM; 99 entry->cmt_rev = SVN_INVALID_REVNUM; 100 entry->kind = svn_node_none; 101 entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN; 102 entry->depth = svn_depth_infinity; 103 entry->file_external_path = NULL; 104 entry->file_external_peg_rev.kind = svn_opt_revision_unspecified; 105 entry->file_external_rev.kind = svn_opt_revision_unspecified; 106 return entry; 107} 108 109 110 111/* Read an escaped byte on the form 'xHH' from [*BUF, END), placing 112 the byte in *RESULT. Advance *BUF to point after the escape 113 sequence. */ 114static svn_error_t * 115read_escaped(char *result, char **buf, const char *end) 116{ 117 apr_uint64_t val; 118 char digits[3]; 119 120 if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1]) 121 || ! svn_ctype_isxdigit((*buf)[2])) 122 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 123 _("Invalid escape sequence")); 124 (*buf)++; 125 digits[0] = *((*buf)++); 126 digits[1] = *((*buf)++); 127 digits[2] = 0; 128 if ((val = apr_strtoi64(digits, NULL, 16)) == 0) 129 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 130 _("Invalid escaped character")); 131 *result = (char) val; 132 return SVN_NO_ERROR; 133} 134 135/* Read a field, possibly with escaped bytes, from [*BUF, END), 136 stopping at the terminator. Place the read string in *RESULT, or set 137 *RESULT to NULL if it is the empty string. Allocate the returned string 138 in POOL. Advance *BUF to point after the terminator. */ 139static svn_error_t * 140read_str(const char **result, 141 char **buf, const char *end, 142 apr_pool_t *pool) 143{ 144 svn_stringbuf_t *s = NULL; 145 const char *start; 146 if (*buf == end) 147 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 148 _("Unexpected end of entry")); 149 if (**buf == '\n') 150 { 151 *result = NULL; 152 (*buf)++; 153 return SVN_NO_ERROR; 154 } 155 156 start = *buf; 157 while (*buf != end && **buf != '\n') 158 { 159 if (**buf == '\\') 160 { 161 char c; 162 if (! s) 163 s = svn_stringbuf_ncreate(start, *buf - start, pool); 164 else 165 svn_stringbuf_appendbytes(s, start, *buf - start); 166 (*buf)++; 167 SVN_ERR(read_escaped(&c, buf, end)); 168 svn_stringbuf_appendbyte(s, c); 169 start = *buf; 170 } 171 else 172 (*buf)++; 173 } 174 175 if (*buf == end) 176 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 177 _("Unexpected end of entry")); 178 179 if (s) 180 { 181 svn_stringbuf_appendbytes(s, start, *buf - start); 182 *result = s->data; 183 } 184 else 185 *result = apr_pstrndup(pool, start, *buf - start); 186 (*buf)++; 187 return SVN_NO_ERROR; 188} 189 190/* This is wrapper around read_str() (which see for details); it 191 simply asks svn_path_is_canonical() of the string it reads, 192 returning an error if the test fails. 193 ### It seems this is only called for entrynames now 194 */ 195static svn_error_t * 196read_path(const char **result, 197 char **buf, const char *end, 198 apr_pool_t *pool) 199{ 200 SVN_ERR(read_str(result, buf, end, pool)); 201 if (*result && **result && !svn_relpath_is_canonical(*result)) 202 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 203 _("Entry contains non-canonical path '%s'"), 204 *result); 205 return SVN_NO_ERROR; 206} 207 208/* This is read_path() for urls. This function does not do the is_canonical 209 test for entries from working copies older than version 10, as since that 210 version the canonicalization of urls has been changed. See issue #2475. 211 If the test is done and fails, read_url returs an error. */ 212static svn_error_t * 213read_url(const char **result, 214 char **buf, const char *end, 215 int wc_format, 216 apr_pool_t *pool) 217{ 218 SVN_ERR(read_str(result, buf, end, pool)); 219 220 /* Always canonicalize the url, as we have stricter canonicalization rules 221 in 1.7+ then before */ 222 if (*result && **result) 223 *result = svn_uri_canonicalize(*result, pool); 224 225 return SVN_NO_ERROR; 226} 227 228/* Read a field from [*BUF, END), terminated by a newline character. 229 The field may not contain escape sequences. The field is not 230 copied and the buffer is modified in place, by replacing the 231 terminator with a NUL byte. Make *BUF point after the original 232 terminator. */ 233static svn_error_t * 234read_val(const char **result, 235 char **buf, const char *end) 236{ 237 const char *start = *buf; 238 239 if (*buf == end) 240 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 241 _("Unexpected end of entry")); 242 if (**buf == '\n') 243 { 244 (*buf)++; 245 *result = NULL; 246 return SVN_NO_ERROR; 247 } 248 249 while (*buf != end && **buf != '\n') 250 (*buf)++; 251 if (*buf == end) 252 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 253 _("Unexpected end of entry")); 254 **buf = '\0'; 255 *result = start; 256 (*buf)++; 257 return SVN_NO_ERROR; 258} 259 260/* Read a boolean field from [*BUF, END), placing the result in 261 *RESULT. If there is no boolean value (just a terminator), it 262 defaults to false. Else, the value must match FIELD_NAME, in which 263 case *RESULT will be set to true. Advance *BUF to point after the 264 terminator. */ 265static svn_error_t * 266read_bool(svn_boolean_t *result, const char *field_name, 267 char **buf, const char *end) 268{ 269 const char *val; 270 SVN_ERR(read_val(&val, buf, end)); 271 if (val) 272 { 273 if (strcmp(val, field_name) != 0) 274 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 275 _("Invalid value for field '%s'"), 276 field_name); 277 *result = TRUE; 278 } 279 else 280 *result = FALSE; 281 return SVN_NO_ERROR; 282} 283 284/* Read a revision number from [*BUF, END) stopping at the 285 terminator. Set *RESULT to the revision number, or 286 SVN_INVALID_REVNUM if there is none. Use POOL for temporary 287 allocations. Make *BUF point after the terminator. */ 288static svn_error_t * 289read_revnum(svn_revnum_t *result, 290 char **buf, 291 const char *end, 292 apr_pool_t *pool) 293{ 294 const char *val; 295 296 SVN_ERR(read_val(&val, buf, end)); 297 298 if (val) 299 *result = SVN_STR_TO_REV(val); 300 else 301 *result = SVN_INVALID_REVNUM; 302 303 return SVN_NO_ERROR; 304} 305 306/* Read a timestamp from [*BUF, END) stopping at the terminator. 307 Set *RESULT to the resulting timestamp, or 0 if there is none. Use 308 POOL for temporary allocations. Make *BUF point after the 309 terminator. */ 310static svn_error_t * 311read_time(apr_time_t *result, 312 char **buf, const char *end, 313 apr_pool_t *pool) 314{ 315 const char *val; 316 317 SVN_ERR(read_val(&val, buf, end)); 318 if (val) 319 SVN_ERR(svn_time_from_cstring(result, val, pool)); 320 else 321 *result = 0; 322 323 return SVN_NO_ERROR; 324} 325 326/** 327 * Parse the string at *STR as an revision and save the result in 328 * *OPT_REV. After returning successfully, *STR points at next 329 * character in *STR where further parsing can be done. 330 */ 331static svn_error_t * 332string_to_opt_revision(svn_opt_revision_t *opt_rev, 333 const char **str, 334 apr_pool_t *pool) 335{ 336 const char *s = *str; 337 338 SVN_ERR_ASSERT(opt_rev); 339 340 while (*s && *s != ':') 341 ++s; 342 343 /* Should not find a \0. */ 344 if (!*s) 345 return svn_error_createf 346 (SVN_ERR_INCORRECT_PARAMS, NULL, 347 _("Found an unexpected \\0 in the file external '%s'"), *str); 348 349 if (0 == strncmp(*str, "HEAD:", 5)) 350 { 351 opt_rev->kind = svn_opt_revision_head; 352 } 353 else 354 { 355 svn_revnum_t rev; 356 const char *endptr; 357 358 SVN_ERR(svn_revnum_parse(&rev, *str, &endptr)); 359 SVN_ERR_ASSERT(endptr == s); 360 opt_rev->kind = svn_opt_revision_number; 361 opt_rev->value.number = rev; 362 } 363 364 *str = s + 1; 365 366 return SVN_NO_ERROR; 367} 368 369/** 370 * Given a revision, return a string for the revision, either "HEAD" 371 * or a string representation of the revision value. All other 372 * revision kinds return an error. 373 */ 374static svn_error_t * 375opt_revision_to_string(const char **str, 376 const char *path, 377 const svn_opt_revision_t *rev, 378 apr_pool_t *pool) 379{ 380 switch (rev->kind) 381 { 382 case svn_opt_revision_head: 383 *str = apr_pstrmemdup(pool, "HEAD", 4); 384 break; 385 case svn_opt_revision_number: 386 *str = apr_ltoa(pool, rev->value.number); 387 break; 388 default: 389 return svn_error_createf 390 (SVN_ERR_INCORRECT_PARAMS, NULL, 391 _("Illegal file external revision kind %d for path '%s'"), 392 rev->kind, path); 393 break; 394 } 395 396 return SVN_NO_ERROR; 397} 398 399svn_error_t * 400svn_wc__unserialize_file_external(const char **path_result, 401 svn_opt_revision_t *peg_rev_result, 402 svn_opt_revision_t *rev_result, 403 const char *str, 404 apr_pool_t *pool) 405{ 406 if (str) 407 { 408 svn_opt_revision_t peg_rev; 409 svn_opt_revision_t op_rev; 410 const char *s = str; 411 412 SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool)); 413 SVN_ERR(string_to_opt_revision(&op_rev, &s, pool)); 414 415 *path_result = apr_pstrdup(pool, s); 416 *peg_rev_result = peg_rev; 417 *rev_result = op_rev; 418 } 419 else 420 { 421 *path_result = NULL; 422 peg_rev_result->kind = svn_opt_revision_unspecified; 423 rev_result->kind = svn_opt_revision_unspecified; 424 } 425 426 return SVN_NO_ERROR; 427} 428 429svn_error_t * 430svn_wc__serialize_file_external(const char **str, 431 const char *path, 432 const svn_opt_revision_t *peg_rev, 433 const svn_opt_revision_t *rev, 434 apr_pool_t *pool) 435{ 436 const char *s; 437 438 if (path) 439 { 440 const char *s1; 441 const char *s2; 442 443 SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool)); 444 SVN_ERR(opt_revision_to_string(&s2, path, rev, pool)); 445 446 s = apr_pstrcat(pool, s1, ":", s2, ":", path, SVN_VA_NULL); 447 } 448 else 449 s = NULL; 450 451 *str = s; 452 453 return SVN_NO_ERROR; 454} 455 456/* Allocate an entry from POOL and read it from [*BUF, END). The 457 buffer may be modified in place while parsing. Return the new 458 entry in *NEW_ENTRY. Advance *BUF to point at the end of the entry 459 record. 460 The entries file format should be provided in ENTRIES_FORMAT. */ 461static svn_error_t * 462read_entry(svn_wc_entry_t **new_entry, 463 char **buf, const char *end, 464 int entries_format, 465 apr_pool_t *pool) 466{ 467 svn_wc_entry_t *entry = alloc_entry(pool); 468 const char *name; 469 470#define MAYBE_DONE if (**buf == '\f') goto done 471 472 /* Find the name and set up the entry under that name. */ 473 SVN_ERR(read_path(&name, buf, end, pool)); 474 entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR; 475 476 /* Set up kind. */ 477 { 478 const char *kindstr; 479 SVN_ERR(read_val(&kindstr, buf, end)); 480 if (kindstr) 481 { 482 if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0) 483 entry->kind = svn_node_file; 484 else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0) 485 entry->kind = svn_node_dir; 486 else 487 return svn_error_createf 488 (SVN_ERR_NODE_UNKNOWN_KIND, NULL, 489 _("Entry '%s' has invalid node kind"), 490 (name ? name : SVN_WC_ENTRY_THIS_DIR)); 491 } 492 else 493 entry->kind = svn_node_none; 494 } 495 MAYBE_DONE; 496 497 /* Attempt to set revision (resolve_to_defaults may do it later, too) */ 498 SVN_ERR(read_revnum(&entry->revision, buf, end, pool)); 499 MAYBE_DONE; 500 501 /* Attempt to set up url path (again, see resolve_to_defaults). */ 502 SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool)); 503 MAYBE_DONE; 504 505 /* Set up repository root. Make sure it is a prefix of url. */ 506 SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool)); 507 if (entry->repos && entry->url 508 && ! svn_uri__is_ancestor(entry->repos, entry->url)) 509 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 510 _("Entry for '%s' has invalid repository " 511 "root"), 512 name ? name : SVN_WC_ENTRY_THIS_DIR); 513 MAYBE_DONE; 514 515 /* Look for a schedule attribute on this entry. */ 516 { 517 const char *schedulestr; 518 SVN_ERR(read_val(&schedulestr, buf, end)); 519 entry->schedule = svn_wc_schedule_normal; 520 if (schedulestr) 521 { 522 if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0) 523 entry->schedule = svn_wc_schedule_add; 524 else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0) 525 entry->schedule = svn_wc_schedule_delete; 526 else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0) 527 entry->schedule = svn_wc_schedule_replace; 528 else 529 return svn_error_createf( 530 SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, 531 _("Entry '%s' has invalid 'schedule' value"), 532 name ? name : SVN_WC_ENTRY_THIS_DIR); 533 } 534 } 535 MAYBE_DONE; 536 537 /* Attempt to set up text timestamp. */ 538 SVN_ERR(read_time(&entry->text_time, buf, end, pool)); 539 MAYBE_DONE; 540 541 /* Checksum. */ 542 SVN_ERR(read_str(&entry->checksum, buf, end, pool)); 543 MAYBE_DONE; 544 545 /* Setup last-committed values. */ 546 SVN_ERR(read_time(&entry->cmt_date, buf, end, pool)); 547 MAYBE_DONE; 548 549 SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool)); 550 MAYBE_DONE; 551 552 SVN_ERR(read_str(&entry->cmt_author, buf, end, pool)); 553 MAYBE_DONE; 554 555 /* has-props, has-prop-mods, cachable-props, present-props are all 556 deprecated. Read any values that may be in the 'entries' file, but 557 discard them, and just put default values into the entry. */ 558 { 559 const char *unused_value; 560 561 /* has-props flag. */ 562 SVN_ERR(read_val(&unused_value, buf, end)); 563 entry->has_props = FALSE; 564 MAYBE_DONE; 565 566 /* has-prop-mods flag. */ 567 SVN_ERR(read_val(&unused_value, buf, end)); 568 entry->has_prop_mods = FALSE; 569 MAYBE_DONE; 570 571 /* Use the empty string for cachable_props, indicating that we no 572 longer attempt to cache any properties. An empty string for 573 present_props means that no cachable props are present. */ 574 575 /* cachable-props string. */ 576 SVN_ERR(read_val(&unused_value, buf, end)); 577 entry->cachable_props = ""; 578 MAYBE_DONE; 579 580 /* present-props string. */ 581 SVN_ERR(read_val(&unused_value, buf, end)); 582 entry->present_props = ""; 583 MAYBE_DONE; 584 } 585 586 /* Is this entry in a state of mental torment (conflict)? */ 587 { 588 SVN_ERR(read_path(&entry->prejfile, buf, end, pool)); 589 MAYBE_DONE; 590 SVN_ERR(read_path(&entry->conflict_old, buf, end, pool)); 591 MAYBE_DONE; 592 SVN_ERR(read_path(&entry->conflict_new, buf, end, pool)); 593 MAYBE_DONE; 594 SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool)); 595 MAYBE_DONE; 596 } 597 598 /* Is this entry copied? */ 599 SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end)); 600 MAYBE_DONE; 601 602 SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool)); 603 MAYBE_DONE; 604 SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool)); 605 MAYBE_DONE; 606 607 /* Is this entry deleted? */ 608 SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end)); 609 MAYBE_DONE; 610 611 /* Is this entry absent? */ 612 SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end)); 613 MAYBE_DONE; 614 615 /* Is this entry incomplete? */ 616 SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end)); 617 MAYBE_DONE; 618 619 /* UUID. */ 620 SVN_ERR(read_str(&entry->uuid, buf, end, pool)); 621 MAYBE_DONE; 622 623 /* Lock token. */ 624 SVN_ERR(read_str(&entry->lock_token, buf, end, pool)); 625 MAYBE_DONE; 626 627 /* Lock owner. */ 628 SVN_ERR(read_str(&entry->lock_owner, buf, end, pool)); 629 MAYBE_DONE; 630 631 /* Lock comment. */ 632 SVN_ERR(read_str(&entry->lock_comment, buf, end, pool)); 633 MAYBE_DONE; 634 635 /* Lock creation date. */ 636 SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool)); 637 MAYBE_DONE; 638 639 /* Changelist. */ 640 SVN_ERR(read_str(&entry->changelist, buf, end, pool)); 641 MAYBE_DONE; 642 643 /* Keep entry in working copy after deletion? */ 644 SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end)); 645 MAYBE_DONE; 646 647 /* Translated size */ 648 { 649 const char *val; 650 651 /* read_val() returns NULL on an empty (e.g. default) entry line, 652 and entry has already been initialized accordingly already */ 653 SVN_ERR(read_val(&val, buf, end)); 654 if (val) 655 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0); 656 } 657 MAYBE_DONE; 658 659 /* Depth. */ 660 { 661 const char *result; 662 SVN_ERR(read_val(&result, buf, end)); 663 if (result) 664 { 665 svn_boolean_t invalid; 666 svn_boolean_t is_this_dir; 667 668 entry->depth = svn_depth_from_word(result); 669 670 /* Verify the depth value: 671 THIS_DIR should not have an excluded value and SUB_DIR should only 672 have excluded value. Remember that infinity value is not stored and 673 should not show up here. Otherwise, something bad may have 674 happened. However, infinity value itself will always be okay. */ 675 is_this_dir = !name; 676 /* '!=': XOR */ 677 invalid = is_this_dir != (entry->depth != svn_depth_exclude); 678 if (entry->depth != svn_depth_infinity && invalid) 679 return svn_error_createf( 680 SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, 681 _("Entry '%s' has invalid 'depth' value"), 682 name ? name : SVN_WC_ENTRY_THIS_DIR); 683 } 684 else 685 entry->depth = svn_depth_infinity; 686 687 } 688 MAYBE_DONE; 689 690 /* Tree conflict data. */ 691 SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool)); 692 MAYBE_DONE; 693 694 /* File external URL and revision. */ 695 { 696 const char *str; 697 SVN_ERR(read_str(&str, buf, end, pool)); 698 SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path, 699 &entry->file_external_peg_rev, 700 &entry->file_external_rev, 701 str, 702 pool)); 703 } 704 MAYBE_DONE; 705 706 done: 707 *new_entry = entry; 708 return SVN_NO_ERROR; 709} 710 711 712/* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its 713 boolean value, else set *ENTRY_FLAG false. ENTRY_NAME is the name 714 of the WC-entry. */ 715static svn_error_t * 716do_bool_attr(svn_boolean_t *entry_flag, 717 apr_hash_t *atts, const char *attr_name, 718 const char *entry_name) 719{ 720 const char *str = svn_hash_gets(atts, attr_name); 721 722 *entry_flag = FALSE; 723 if (str) 724 { 725 if (strcmp(str, "true") == 0) 726 *entry_flag = TRUE; 727 else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0) 728 *entry_flag = FALSE; 729 else 730 return svn_error_createf 731 (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, 732 _("Entry '%s' has invalid '%s' value"), 733 (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name); 734 } 735 return SVN_NO_ERROR; 736} 737 738 739/* */ 740static const char * 741extract_string(apr_hash_t *atts, 742 const char *att_name, 743 apr_pool_t *result_pool) 744{ 745 const char *value = svn_hash_gets(atts, att_name); 746 747 if (value == NULL) 748 return NULL; 749 750 return apr_pstrdup(result_pool, value); 751} 752 753 754/* Like extract_string(), but normalizes empty strings to NULL. */ 755static const char * 756extract_string_normalize(apr_hash_t *atts, 757 const char *att_name, 758 apr_pool_t *result_pool) 759{ 760 const char *value = svn_hash_gets(atts, att_name); 761 762 if (value == NULL) 763 return NULL; 764 765 if (*value == '\0') 766 return NULL; 767 768 return apr_pstrdup(result_pool, value); 769} 770 771 772/* NOTE: this is used for upgrading old XML-based entries file. Be wary of 773 removing items. 774 775 ### many attributes are no longer used within the old-style log files. 776 ### These attrs need to be recognized for old entries, however. For these 777 ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS 778 ### for that particular field. MODIFY_FLAGS is *only* used by the 779 ### log-based entry modification system, and will go way once we 780 ### completely move away from loggy. 781 782 Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose 783 keys and values are both char *. Allocate the entry and copy 784 attributes into POOL as needed. */ 785static svn_error_t * 786atts_to_entry(svn_wc_entry_t **new_entry, 787 apr_hash_t *atts, 788 apr_pool_t *pool) 789{ 790 svn_wc_entry_t *entry = alloc_entry(pool); 791 const char *name; 792 793 /* Find the name and set up the entry under that name. */ 794 name = svn_hash_gets(atts, ENTRIES_ATTR_NAME); 795 entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR; 796 797 /* Attempt to set revision (resolve_to_defaults may do it later, too) 798 799 ### not used by loggy; no need to set MODIFY_FLAGS */ 800 { 801 const char *revision_str 802 = svn_hash_gets(atts, ENTRIES_ATTR_REVISION); 803 804 if (revision_str) 805 entry->revision = SVN_STR_TO_REV(revision_str); 806 else 807 entry->revision = SVN_INVALID_REVNUM; 808 } 809 810 /* Attempt to set up url path (again, see resolve_to_defaults). 811 812 ### not used by loggy; no need to set MODIFY_FLAGS */ 813 entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool); 814 if (entry->url) 815 entry->url = svn_uri_canonicalize(entry->url, pool); 816 817 /* Set up repository root. Make sure it is a prefix of url. 818 819 ### not used by loggy; no need to set MODIFY_FLAGS */ 820 entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool); 821 if (entry->repos) 822 entry->repos = svn_uri_canonicalize(entry->repos, pool); 823 824 if (entry->url && entry->repos 825 && !svn_uri__is_ancestor(entry->repos, entry->url)) 826 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 827 _("Entry for '%s' has invalid repository " 828 "root"), 829 name ? name : SVN_WC_ENTRY_THIS_DIR); 830 831 /* Set up kind. */ 832 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 833 { 834 const char *kindstr 835 = svn_hash_gets(atts, ENTRIES_ATTR_KIND); 836 837 entry->kind = svn_node_none; 838 if (kindstr) 839 { 840 if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0) 841 entry->kind = svn_node_file; 842 else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0) 843 entry->kind = svn_node_dir; 844 else 845 return svn_error_createf 846 (SVN_ERR_NODE_UNKNOWN_KIND, NULL, 847 _("Entry '%s' has invalid node kind"), 848 (name ? name : SVN_WC_ENTRY_THIS_DIR)); 849 } 850 } 851 852 /* Look for a schedule attribute on this entry. */ 853 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 854 { 855 const char *schedulestr 856 = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE); 857 858 entry->schedule = svn_wc_schedule_normal; 859 if (schedulestr) 860 { 861 if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0) 862 entry->schedule = svn_wc_schedule_add; 863 else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0) 864 entry->schedule = svn_wc_schedule_delete; 865 else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0) 866 entry->schedule = svn_wc_schedule_replace; 867 else if (strcmp(schedulestr, "") == 0) 868 entry->schedule = svn_wc_schedule_normal; 869 else 870 return svn_error_createf( 871 SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, 872 _("Entry '%s' has invalid 'schedule' value"), 873 (name ? name : SVN_WC_ENTRY_THIS_DIR)); 874 } 875 } 876 877 /* Is this entry in a state of mental torment (conflict)? */ 878 entry->prejfile = extract_string_normalize(atts, 879 ENTRIES_ATTR_PREJFILE, 880 pool); 881 entry->conflict_old = extract_string_normalize(atts, 882 ENTRIES_ATTR_CONFLICT_OLD, 883 pool); 884 entry->conflict_new = extract_string_normalize(atts, 885 ENTRIES_ATTR_CONFLICT_NEW, 886 pool); 887 entry->conflict_wrk = extract_string_normalize(atts, 888 ENTRIES_ATTR_CONFLICT_WRK, 889 pool); 890 891 /* Is this entry copied? */ 892 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 893 SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name)); 894 895 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 896 entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool); 897 898 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 899 { 900 const char *revstr; 901 902 revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV); 903 if (revstr) 904 entry->copyfrom_rev = SVN_STR_TO_REV(revstr); 905 } 906 907 /* Is this entry deleted? 908 909 ### not used by loggy; no need to set MODIFY_FLAGS */ 910 SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name)); 911 912 /* Is this entry absent? 913 914 ### not used by loggy; no need to set MODIFY_FLAGS */ 915 SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name)); 916 917 /* Is this entry incomplete? 918 919 ### not used by loggy; no need to set MODIFY_FLAGS */ 920 SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE, 921 name)); 922 923 /* Attempt to set up timestamps. */ 924 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 925 { 926 const char *text_timestr; 927 928 text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME); 929 if (text_timestr) 930 SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool)); 931 932 /* Note: we do not persist prop_time, so there is no need to attempt 933 to parse a new prop_time value from the log. Certainly, on any 934 recent working copy, there will not be a log record to alter 935 the prop_time value. */ 936 } 937 938 /* Checksum. */ 939 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 940 entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool); 941 942 /* UUID. 943 944 ### not used by loggy; no need to set MODIFY_FLAGS */ 945 entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool); 946 947 /* Setup last-committed values. */ 948 { 949 const char *cmt_datestr, *cmt_revstr; 950 951 cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE); 952 if (cmt_datestr) 953 { 954 SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool)); 955 } 956 else 957 entry->cmt_date = 0; 958 959 cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV); 960 if (cmt_revstr) 961 { 962 entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr); 963 } 964 else 965 entry->cmt_rev = SVN_INVALID_REVNUM; 966 967 entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool); 968 } 969 970 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 971 entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool); 972 entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool); 973 entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool); 974 { 975 const char *cdate_str = 976 svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE); 977 if (cdate_str) 978 { 979 SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date, 980 cdate_str, pool)); 981 } 982 } 983 /* ----- end of lock handling. */ 984 985 /* Note: if there are attributes for the (deprecated) has_props, 986 has_prop_mods, cachable_props, or present_props, then we're just 987 going to ignore them. */ 988 989 /* Translated size */ 990 /* ### not used by loggy; no need to set MODIFY_FLAGS */ 991 { 992 const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE); 993 if (val) 994 { 995 /* Cast to off_t; it's safe: we put in an off_t to start with... */ 996 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0); 997 } 998 } 999 1000 *new_entry = entry; 1001 return SVN_NO_ERROR; 1002} 1003 1004/* Used when reading an entries file in XML format. */ 1005struct entries_accumulator 1006{ 1007 /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */ 1008 apr_hash_t *entries; 1009 1010 /* The parser that's parsing it, for signal_expat_bailout(). */ 1011 svn_xml_parser_t *parser; 1012 1013 /* Don't leave home without one. */ 1014 apr_pool_t *pool; 1015 1016 /* Cleared before handling each entry. */ 1017 apr_pool_t *scratch_pool; 1018}; 1019 1020 1021 1022/* Called whenever we find an <open> tag of some kind. */ 1023static void 1024handle_start_tag(void *userData, const char *tagname, const char **atts) 1025{ 1026 struct entries_accumulator *accum = userData; 1027 apr_hash_t *attributes; 1028 svn_wc_entry_t *entry; 1029 svn_error_t *err; 1030 1031 /* We only care about the `entry' tag; all other tags, such as `xml' 1032 and `wc-entries', are ignored. */ 1033 if (strcmp(tagname, ENTRIES_TAG_ENTRY)) 1034 return; 1035 1036 svn_pool_clear(accum->scratch_pool); 1037 /* Make an entry from the attributes. */ 1038 attributes = svn_xml_make_att_hash(atts, accum->scratch_pool); 1039 err = atts_to_entry(&entry, attributes, accum->pool); 1040 if (err) 1041 { 1042 svn_xml_signal_bailout(err, accum->parser); 1043 return; 1044 } 1045 1046 /* Find the name and set up the entry under that name. This 1047 should *NOT* be NULL, since svn_wc__atts_to_entry() should 1048 have made it into SVN_WC_ENTRY_THIS_DIR. */ 1049 svn_hash_sets(accum->entries, entry->name, entry); 1050} 1051 1052/* Parse BUF of size SIZE as an entries file in XML format, storing the parsed 1053 entries in ENTRIES. Use SCRATCH_POOL for temporary allocations and 1054 RESULT_POOL for the returned entries. */ 1055static svn_error_t * 1056parse_entries_xml(const char *dir_abspath, 1057 apr_hash_t *entries, 1058 const char *buf, 1059 apr_size_t size, 1060 apr_pool_t *result_pool, 1061 apr_pool_t *scratch_pool) 1062{ 1063 svn_xml_parser_t *svn_parser; 1064 struct entries_accumulator accum; 1065 1066 /* Set up userData for the XML parser. */ 1067 accum.entries = entries; 1068 accum.pool = result_pool; 1069 accum.scratch_pool = svn_pool_create(scratch_pool); 1070 1071 /* Create the XML parser */ 1072 svn_parser = svn_xml_make_parser(&accum, 1073 handle_start_tag, 1074 NULL, 1075 NULL, 1076 scratch_pool); 1077 1078 /* Store parser in its own userdata, so callbacks can call 1079 svn_xml_signal_bailout() */ 1080 accum.parser = svn_parser; 1081 1082 /* Parse. */ 1083 SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE), 1084 apr_psprintf(scratch_pool, 1085 _("XML parser failed in '%s'"), 1086 svn_dirent_local_style(dir_abspath, scratch_pool))); 1087 1088 svn_pool_destroy(accum.scratch_pool); 1089 1090 /* Clean up the XML parser */ 1091 svn_xml_free_parser(svn_parser); 1092 1093 return SVN_NO_ERROR; 1094} 1095 1096 1097 1098/* Use entry SRC to fill in blank portions of entry DST. SRC itself 1099 may not have any blanks, of course. 1100 Typically, SRC is a parent directory's own entry, and DST is some 1101 child in that directory. */ 1102static void 1103take_from_entry(const svn_wc_entry_t *src, 1104 svn_wc_entry_t *dst, 1105 apr_pool_t *pool) 1106{ 1107 /* Inherits parent's revision if doesn't have a revision of one's 1108 own, unless this is a subdirectory. */ 1109 if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir)) 1110 dst->revision = src->revision; 1111 1112 /* Inherits parent's url if doesn't have a url of one's own. */ 1113 if (! dst->url) 1114 dst->url = svn_path_url_add_component2(src->url, dst->name, pool); 1115 1116 if (! dst->repos) 1117 dst->repos = src->repos; 1118 1119 if ((! dst->uuid) 1120 && (! ((dst->schedule == svn_wc_schedule_add) 1121 || (dst->schedule == svn_wc_schedule_replace)))) 1122 { 1123 dst->uuid = src->uuid; 1124 } 1125} 1126 1127/* Resolve any missing information in ENTRIES by deducing from the 1128 directory's own entry (which must already be present in ENTRIES). */ 1129static svn_error_t * 1130resolve_to_defaults(apr_hash_t *entries, 1131 apr_pool_t *pool) 1132{ 1133 apr_hash_index_t *hi; 1134 svn_wc_entry_t *default_entry 1135 = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); 1136 1137 /* First check the dir's own entry for consistency. */ 1138 if (! default_entry) 1139 return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, 1140 NULL, 1141 _("Missing default entry")); 1142 1143 if (default_entry->revision == SVN_INVALID_REVNUM) 1144 return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION, 1145 NULL, 1146 _("Default entry has no revision number")); 1147 1148 if (! default_entry->url) 1149 return svn_error_create(SVN_ERR_ENTRY_MISSING_URL, 1150 NULL, 1151 _("Default entry is missing URL")); 1152 1153 1154 /* Then use it to fill in missing information in other entries. */ 1155 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1156 { 1157 svn_wc_entry_t *this_entry = apr_hash_this_val(hi); 1158 1159 if (this_entry == default_entry) 1160 /* THIS_DIR already has all the information it can possibly 1161 have. */ 1162 continue; 1163 1164 if (this_entry->kind == svn_node_dir) 1165 /* Entries that are directories have everything but their 1166 name, kind, and state stored in the THIS_DIR entry of the 1167 directory itself. However, we are disallowing the perusing 1168 of any entries outside of the current entries file. If a 1169 caller wants more info about a directory, it should look in 1170 the entries file in the directory. */ 1171 continue; 1172 1173 if (this_entry->kind == svn_node_file) 1174 /* For file nodes that do not explicitly have their ancestry 1175 stated, this can be derived from the default entry of the 1176 directory in which those files reside. */ 1177 take_from_entry(default_entry, this_entry, pool); 1178 } 1179 1180 return SVN_NO_ERROR; 1181} 1182 1183 1184 1185/* Read and parse an old-style 'entries' file in the administrative area 1186 of PATH, filling in ENTRIES with the contents. The results will be 1187 allocated in RESULT_POOL, and temporary allocations will be made in 1188 SCRATCH_POOL. */ 1189svn_error_t * 1190svn_wc__read_entries_old(apr_hash_t **entries, 1191 const char *dir_abspath, 1192 apr_pool_t *result_pool, 1193 apr_pool_t *scratch_pool) 1194{ 1195 char *curp; 1196 const char *endp; 1197 svn_wc_entry_t *entry; 1198 svn_stream_t *stream; 1199 svn_string_t *buf; 1200 1201 *entries = apr_hash_make(result_pool); 1202 1203 /* Open the entries file. */ 1204 SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES, 1205 scratch_pool, scratch_pool)); 1206 SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool)); 1207 1208 /* We own the returned data; it is modifiable, so cast away... */ 1209 curp = (char *)buf->data; 1210 endp = buf->data + buf->len; 1211 1212 /* If the first byte of the file is not a digit, then it is probably in XML 1213 format. */ 1214 if (curp != endp && !svn_ctype_isdigit(*curp)) 1215 SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len, 1216 result_pool, scratch_pool)); 1217 else 1218 { 1219 int entryno, entries_format; 1220 const char *val; 1221 1222 /* Read the format line from the entries file. In case we're in the 1223 middle of upgrading a working copy, this line will contain the 1224 original format pre-upgrade. */ 1225 SVN_ERR(read_val(&val, &curp, endp)); 1226 if (val) 1227 entries_format = (int)apr_strtoi64(val, NULL, 0); 1228 else 1229 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 1230 _("Invalid version line in entries file " 1231 "of '%s'"), 1232 svn_dirent_local_style(dir_abspath, 1233 scratch_pool)); 1234 entryno = 1; 1235 1236 while (curp != endp) 1237 { 1238 svn_error_t *err = read_entry(&entry, &curp, endp, 1239 entries_format, result_pool); 1240 if (! err) 1241 { 1242 /* We allow extra fields at the end of the line, for 1243 extensibility. */ 1244 curp = memchr(curp, '\f', endp - curp); 1245 if (! curp) 1246 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 1247 _("Missing entry terminator")); 1248 if (! err && (curp == endp || *(++curp) != '\n')) 1249 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 1250 _("Invalid entry terminator")); 1251 } 1252 if (err) 1253 return svn_error_createf(err->apr_err, err, 1254 _("Error at entry %d in entries file for " 1255 "'%s':"), 1256 entryno, 1257 svn_dirent_local_style(dir_abspath, 1258 scratch_pool)); 1259 1260 ++curp; 1261 ++entryno; 1262 1263 svn_hash_sets(*entries, entry->name, entry); 1264 } 1265 } 1266 1267 /* Fill in any implied fields. */ 1268 return svn_error_trace(resolve_to_defaults(*entries, result_pool)); 1269} 1270 1271 1272/* For non-directory PATHs full entry information is obtained by reading 1273 * the entries for the parent directory of PATH and then extracting PATH's 1274 * entry. If PATH is a directory then only abrieviated information is 1275 * available in the parent directory, more complete information is 1276 * available by reading the entries for PATH itself. 1277 * 1278 * Note: There is one bit of information about directories that is only 1279 * available in the parent directory, that is the "deleted" state. If PATH 1280 * is a versioned directory then the "deleted" state information will not 1281 * be returned in ENTRY. This means some bits of the code (e.g. revert) 1282 * need to obtain it by directly extracting the directory entry from the 1283 * parent directory's entries. I wonder if this function should handle 1284 * that? 1285 */ 1286svn_error_t * 1287svn_wc_entry(const svn_wc_entry_t **entry, 1288 const char *path, 1289 svn_wc_adm_access_t *adm_access, 1290 svn_boolean_t show_hidden, 1291 apr_pool_t *pool) 1292{ 1293 svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); 1294 const char *local_abspath; 1295 svn_wc_adm_access_t *dir_access; 1296 const char *entry_name; 1297 apr_hash_t *entries; 1298 1299 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); 1300 1301 /* Does the provided path refer to a directory with an associated 1302 access baton? */ 1303 dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool); 1304 if (dir_access == NULL) 1305 { 1306 /* Damn. Okay. Assume the path is to a child, and let's look for 1307 a baton associated with its parent. */ 1308 1309 const char *dir_abspath; 1310 1311 svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool); 1312 1313 dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool); 1314 } 1315 else 1316 { 1317 /* Woo! Got one. Look for "this dir" in the entries hash. */ 1318 entry_name = ""; 1319 } 1320 1321 if (dir_access == NULL) 1322 { 1323 /* Early exit. */ 1324 *entry = NULL; 1325 return SVN_NO_ERROR; 1326 } 1327 1328 /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and 1329 fetch all entries here (optimization) since we know how to filter 1330 out a "hidden" node. */ 1331 SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool)); 1332 *entry = svn_hash_gets(entries, entry_name); 1333 1334 if (!show_hidden && *entry != NULL) 1335 { 1336 svn_boolean_t hidden; 1337 1338 SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry)); 1339 if (hidden) 1340 *entry = NULL; 1341 } 1342 1343 return SVN_NO_ERROR; 1344} 1345