2 * old-and-busted.c: routines for reading pre-1.7 working copies.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
28 #include "svn_dirent_uri.h"
31 #include "svn_ctype.h"
32 #include "svn_pools.h"
35 #include "adm_files.h"
39 #include "private/svn_wc_private.h"
40 #include "svn_private_config.h"
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"
52 /* Tag names used in our old XML entries file. */
53 #define ENTRIES_TAG_ENTRY "entry"
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"
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"
93 static svn_wc_entry_t *
94 alloc_entry(apr_pool_t *pool)
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;
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
115 read_escaped(char *result, char **buf, const char *end)
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"));
125 digits[0] = *((*buf)++);
126 digits[1] = *((*buf)++);
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;
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. */
140 read_str(const char **result,
141 char **buf, const char *end,
144 svn_stringbuf_t *s = NULL;
147 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
148 _("Unexpected end of entry"));
157 while (*buf != end && **buf != '\n')
163 s = svn_stringbuf_ncreate(start, *buf - start, pool);
165 svn_stringbuf_appendbytes(s, start, *buf - start);
167 SVN_ERR(read_escaped(&c, buf, end));
168 svn_stringbuf_appendbyte(s, c);
176 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
177 _("Unexpected end of entry"));
181 svn_stringbuf_appendbytes(s, start, *buf - start);
185 *result = apr_pstrndup(pool, start, *buf - start);
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
196 read_path(const char **result,
197 char **buf, const char *end,
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'"),
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. */
213 read_url(const char **result,
214 char **buf, const char *end,
218 SVN_ERR(read_str(result, buf, end, pool));
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);
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
234 read_val(const char **result,
235 char **buf, const char *end)
237 const char *start = *buf;
240 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
241 _("Unexpected end of entry"));
249 while (*buf != end && **buf != '\n')
252 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
253 _("Unexpected end of entry"));
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
266 read_bool(svn_boolean_t *result, const char *field_name,
267 char **buf, const char *end)
270 SVN_ERR(read_val(&val, buf, end));
273 if (strcmp(val, field_name) != 0)
274 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
275 _("Invalid value for field '%s'"),
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. */
289 read_revnum(svn_revnum_t *result,
296 SVN_ERR(read_val(&val, buf, end));
299 *result = SVN_STR_TO_REV(val);
301 *result = SVN_INVALID_REVNUM;
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
311 read_time(apr_time_t *result,
312 char **buf, const char *end,
317 SVN_ERR(read_val(&val, buf, end));
319 SVN_ERR(svn_time_from_cstring(result, val, pool));
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.
332 string_to_opt_revision(svn_opt_revision_t *opt_rev,
336 const char *s = *str;
338 SVN_ERR_ASSERT(opt_rev);
340 while (*s && *s != ':')
343 /* Should not find a \0. */
345 return svn_error_createf
346 (SVN_ERR_INCORRECT_PARAMS, NULL,
347 _("Found an unexpected \\0 in the file external '%s'"), *str);
349 if (0 == strncmp(*str, "HEAD:", 5))
351 opt_rev->kind = svn_opt_revision_head;
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;
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.
375 opt_revision_to_string(const char **str,
377 const svn_opt_revision_t *rev,
382 case svn_opt_revision_head:
383 *str = apr_pstrmemdup(pool, "HEAD", 4);
385 case svn_opt_revision_number:
386 *str = apr_ltoa(pool, rev->value.number);
389 return svn_error_createf
390 (SVN_ERR_INCORRECT_PARAMS, NULL,
391 _("Illegal file external revision kind %d for path '%s'"),
400 svn_wc__unserialize_file_external(const char **path_result,
401 svn_opt_revision_t *peg_rev_result,
402 svn_opt_revision_t *rev_result,
408 svn_opt_revision_t peg_rev;
409 svn_opt_revision_t op_rev;
412 SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool));
413 SVN_ERR(string_to_opt_revision(&op_rev, &s, pool));
415 *path_result = apr_pstrdup(pool, s);
416 *peg_rev_result = peg_rev;
417 *rev_result = op_rev;
422 peg_rev_result->kind = svn_opt_revision_unspecified;
423 rev_result->kind = svn_opt_revision_unspecified;
430 svn_wc__serialize_file_external(const char **str,
432 const svn_opt_revision_t *peg_rev,
433 const svn_opt_revision_t *rev,
443 SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool));
444 SVN_ERR(opt_revision_to_string(&s2, path, rev, pool));
446 s = apr_pstrcat(pool, s1, ":", s2, ":", path, (char *)NULL);
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
460 The entries file format should be provided in ENTRIES_FORMAT. */
462 read_entry(svn_wc_entry_t **new_entry,
463 char **buf, const char *end,
467 svn_wc_entry_t *entry = alloc_entry(pool);
470 #define MAYBE_DONE if (**buf == '\f') goto done
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;
479 SVN_ERR(read_val(&kindstr, buf, end));
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;
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));
493 entry->kind = svn_node_none;
497 /* Attempt to set revision (resolve_to_defaults may do it later, too) */
498 SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
501 /* Attempt to set up url path (again, see resolve_to_defaults). */
502 SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool));
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 "
512 name ? name : SVN_WC_ENTRY_THIS_DIR);
515 /* Look for a schedule attribute on this entry. */
517 const char *schedulestr;
518 SVN_ERR(read_val(&schedulestr, buf, end));
519 entry->schedule = svn_wc_schedule_normal;
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;
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);
537 /* Attempt to set up text timestamp. */
538 SVN_ERR(read_time(&entry->text_time, buf, end, pool));
542 SVN_ERR(read_str(&entry->checksum, buf, end, pool));
545 /* Setup last-committed values. */
546 SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
549 SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
552 SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
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. */
559 const char *unused_value;
561 /* has-props flag. */
562 SVN_ERR(read_val(&unused_value, buf, end));
563 entry->has_props = FALSE;
566 /* has-prop-mods flag. */
567 SVN_ERR(read_val(&unused_value, buf, end));
568 entry->has_prop_mods = FALSE;
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. */
575 /* cachable-props string. */
576 SVN_ERR(read_val(&unused_value, buf, end));
577 entry->cachable_props = "";
580 /* present-props string. */
581 SVN_ERR(read_val(&unused_value, buf, end));
582 entry->present_props = "";
586 /* Is this entry in a state of mental torment (conflict)? */
588 SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
590 SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
592 SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
594 SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
598 /* Is this entry copied? */
599 SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end));
602 SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool));
604 SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
607 /* Is this entry deleted? */
608 SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end));
611 /* Is this entry absent? */
612 SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end));
615 /* Is this entry incomplete? */
616 SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end));
620 SVN_ERR(read_str(&entry->uuid, buf, end, pool));
624 SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
628 SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
632 SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
635 /* Lock creation date. */
636 SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
640 SVN_ERR(read_str(&entry->changelist, buf, end, pool));
643 /* Keep entry in working copy after deletion? */
644 SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end));
647 /* Translated size */
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));
655 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
662 SVN_ERR(read_val(&result, buf, end));
665 svn_boolean_t invalid;
666 svn_boolean_t is_this_dir;
668 entry->depth = svn_depth_from_word(result);
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. */
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);
685 entry->depth = svn_depth_infinity;
690 /* Tree conflict data. */
691 SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool));
694 /* File external URL and revision. */
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,
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
716 do_bool_attr(svn_boolean_t *entry_flag,
717 apr_hash_t *atts, const char *attr_name,
718 const char *entry_name)
720 const char *str = svn_hash_gets(atts, attr_name);
725 if (strcmp(str, "true") == 0)
727 else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
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);
741 extract_string(apr_hash_t *atts,
742 const char *att_name,
743 apr_pool_t *result_pool)
745 const char *value = svn_hash_gets(atts, att_name);
750 return apr_pstrdup(result_pool, value);
754 /* Like extract_string(), but normalizes empty strings to NULL. */
756 extract_string_normalize(apr_hash_t *atts,
757 const char *att_name,
758 apr_pool_t *result_pool)
760 const char *value = svn_hash_gets(atts, att_name);
768 return apr_pstrdup(result_pool, value);
772 /* NOTE: this is used for upgrading old XML-based entries file. Be wary of
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.
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. */
786 atts_to_entry(svn_wc_entry_t **new_entry,
790 svn_wc_entry_t *entry = alloc_entry(pool);
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;
797 /* Attempt to set revision (resolve_to_defaults may do it later, too)
799 ### not used by loggy; no need to set MODIFY_FLAGS */
801 const char *revision_str
802 = svn_hash_gets(atts, ENTRIES_ATTR_REVISION);
805 entry->revision = SVN_STR_TO_REV(revision_str);
807 entry->revision = SVN_INVALID_REVNUM;
810 /* Attempt to set up url path (again, see resolve_to_defaults).
812 ### not used by loggy; no need to set MODIFY_FLAGS */
813 entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool);
815 /* Set up repository root. Make sure it is a prefix of url.
817 ### not used by loggy; no need to set MODIFY_FLAGS */
818 entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool);
820 if (entry->url && entry->repos
821 && !svn_uri__is_ancestor(entry->repos, entry->url))
822 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
823 _("Entry for '%s' has invalid repository "
825 name ? name : SVN_WC_ENTRY_THIS_DIR);
828 /* ### not used by loggy; no need to set MODIFY_FLAGS */
831 = svn_hash_gets(atts, ENTRIES_ATTR_KIND);
833 entry->kind = svn_node_none;
836 if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
837 entry->kind = svn_node_file;
838 else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
839 entry->kind = svn_node_dir;
841 return svn_error_createf
842 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
843 _("Entry '%s' has invalid node kind"),
844 (name ? name : SVN_WC_ENTRY_THIS_DIR));
848 /* Look for a schedule attribute on this entry. */
849 /* ### not used by loggy; no need to set MODIFY_FLAGS */
851 const char *schedulestr
852 = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE);
854 entry->schedule = svn_wc_schedule_normal;
857 if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
858 entry->schedule = svn_wc_schedule_add;
859 else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
860 entry->schedule = svn_wc_schedule_delete;
861 else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
862 entry->schedule = svn_wc_schedule_replace;
863 else if (strcmp(schedulestr, "") == 0)
864 entry->schedule = svn_wc_schedule_normal;
866 return svn_error_createf(
867 SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
868 _("Entry '%s' has invalid 'schedule' value"),
869 (name ? name : SVN_WC_ENTRY_THIS_DIR));
873 /* Is this entry in a state of mental torment (conflict)? */
874 entry->prejfile = extract_string_normalize(atts,
875 ENTRIES_ATTR_PREJFILE,
877 entry->conflict_old = extract_string_normalize(atts,
878 ENTRIES_ATTR_CONFLICT_OLD,
880 entry->conflict_new = extract_string_normalize(atts,
881 ENTRIES_ATTR_CONFLICT_NEW,
883 entry->conflict_wrk = extract_string_normalize(atts,
884 ENTRIES_ATTR_CONFLICT_WRK,
887 /* Is this entry copied? */
888 /* ### not used by loggy; no need to set MODIFY_FLAGS */
889 SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name));
891 /* ### not used by loggy; no need to set MODIFY_FLAGS */
892 entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool);
894 /* ### not used by loggy; no need to set MODIFY_FLAGS */
898 revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV);
900 entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
903 /* Is this entry deleted?
905 ### not used by loggy; no need to set MODIFY_FLAGS */
906 SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name));
908 /* Is this entry absent?
910 ### not used by loggy; no need to set MODIFY_FLAGS */
911 SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name));
913 /* Is this entry incomplete?
915 ### not used by loggy; no need to set MODIFY_FLAGS */
916 SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE,
919 /* Attempt to set up timestamps. */
920 /* ### not used by loggy; no need to set MODIFY_FLAGS */
922 const char *text_timestr;
924 text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME);
926 SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool));
928 /* Note: we do not persist prop_time, so there is no need to attempt
929 to parse a new prop_time value from the log. Certainly, on any
930 recent working copy, there will not be a log record to alter
931 the prop_time value. */
935 /* ### not used by loggy; no need to set MODIFY_FLAGS */
936 entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool);
940 ### not used by loggy; no need to set MODIFY_FLAGS */
941 entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool);
943 /* Setup last-committed values. */
945 const char *cmt_datestr, *cmt_revstr;
947 cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE);
950 SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
955 cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV);
958 entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
961 entry->cmt_rev = SVN_INVALID_REVNUM;
963 entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool);
966 /* ### not used by loggy; no need to set MODIFY_FLAGS */
967 entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool);
968 entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool);
969 entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool);
971 const char *cdate_str =
972 svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE);
975 SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
979 /* ----- end of lock handling. */
981 /* Note: if there are attributes for the (deprecated) has_props,
982 has_prop_mods, cachable_props, or present_props, then we're just
983 going to ignore them. */
985 /* Translated size */
986 /* ### not used by loggy; no need to set MODIFY_FLAGS */
988 const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE);
991 /* Cast to off_t; it's safe: we put in an off_t to start with... */
992 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
1000 /* Used when reading an entries file in XML format. */
1001 struct entries_accumulator
1003 /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
1004 apr_hash_t *entries;
1006 /* The parser that's parsing it, for signal_expat_bailout(). */
1007 svn_xml_parser_t *parser;
1009 /* Don't leave home without one. */
1012 /* Cleared before handling each entry. */
1013 apr_pool_t *scratch_pool;
1018 /* Called whenever we find an <open> tag of some kind. */
1020 handle_start_tag(void *userData, const char *tagname, const char **atts)
1022 struct entries_accumulator *accum = userData;
1023 apr_hash_t *attributes;
1024 svn_wc_entry_t *entry;
1027 /* We only care about the `entry' tag; all other tags, such as `xml'
1028 and `wc-entries', are ignored. */
1029 if (strcmp(tagname, ENTRIES_TAG_ENTRY))
1032 svn_pool_clear(accum->scratch_pool);
1033 /* Make an entry from the attributes. */
1034 attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
1035 err = atts_to_entry(&entry, attributes, accum->pool);
1038 svn_xml_signal_bailout(err, accum->parser);
1042 /* Find the name and set up the entry under that name. This
1043 should *NOT* be NULL, since svn_wc__atts_to_entry() should
1044 have made it into SVN_WC_ENTRY_THIS_DIR. */
1045 svn_hash_sets(accum->entries, entry->name, entry);
1048 /* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
1049 entries in ENTRIES. Use SCRATCH_POOL for temporary allocations and
1050 RESULT_POOL for the returned entries. */
1051 static svn_error_t *
1052 parse_entries_xml(const char *dir_abspath,
1053 apr_hash_t *entries,
1056 apr_pool_t *result_pool,
1057 apr_pool_t *scratch_pool)
1059 svn_xml_parser_t *svn_parser;
1060 struct entries_accumulator accum;
1062 /* Set up userData for the XML parser. */
1063 accum.entries = entries;
1064 accum.pool = result_pool;
1065 accum.scratch_pool = svn_pool_create(scratch_pool);
1067 /* Create the XML parser */
1068 svn_parser = svn_xml_make_parser(&accum,
1074 /* Store parser in its own userdata, so callbacks can call
1075 svn_xml_signal_bailout() */
1076 accum.parser = svn_parser;
1079 SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
1080 apr_psprintf(scratch_pool,
1081 _("XML parser failed in '%s'"),
1082 svn_dirent_local_style(dir_abspath, scratch_pool)));
1084 svn_pool_destroy(accum.scratch_pool);
1086 /* Clean up the XML parser */
1087 svn_xml_free_parser(svn_parser);
1089 return SVN_NO_ERROR;
1094 /* Use entry SRC to fill in blank portions of entry DST. SRC itself
1095 may not have any blanks, of course.
1096 Typically, SRC is a parent directory's own entry, and DST is some
1097 child in that directory. */
1099 take_from_entry(const svn_wc_entry_t *src,
1100 svn_wc_entry_t *dst,
1103 /* Inherits parent's revision if doesn't have a revision of one's
1104 own, unless this is a subdirectory. */
1105 if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
1106 dst->revision = src->revision;
1108 /* Inherits parent's url if doesn't have a url of one's own. */
1110 dst->url = svn_path_url_add_component2(src->url, dst->name, pool);
1113 dst->repos = src->repos;
1116 && (! ((dst->schedule == svn_wc_schedule_add)
1117 || (dst->schedule == svn_wc_schedule_replace))))
1119 dst->uuid = src->uuid;
1123 /* Resolve any missing information in ENTRIES by deducing from the
1124 directory's own entry (which must already be present in ENTRIES). */
1125 static svn_error_t *
1126 resolve_to_defaults(apr_hash_t *entries,
1129 apr_hash_index_t *hi;
1130 svn_wc_entry_t *default_entry
1131 = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
1133 /* First check the dir's own entry for consistency. */
1134 if (! default_entry)
1135 return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
1137 _("Missing default entry"));
1139 if (default_entry->revision == SVN_INVALID_REVNUM)
1140 return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
1142 _("Default entry has no revision number"));
1144 if (! default_entry->url)
1145 return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
1147 _("Default entry is missing URL"));
1150 /* Then use it to fill in missing information in other entries. */
1151 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1153 svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
1155 if (this_entry == default_entry)
1156 /* THIS_DIR already has all the information it can possibly
1160 if (this_entry->kind == svn_node_dir)
1161 /* Entries that are directories have everything but their
1162 name, kind, and state stored in the THIS_DIR entry of the
1163 directory itself. However, we are disallowing the perusing
1164 of any entries outside of the current entries file. If a
1165 caller wants more info about a directory, it should look in
1166 the entries file in the directory. */
1169 if (this_entry->kind == svn_node_file)
1170 /* For file nodes that do not explicitly have their ancestry
1171 stated, this can be derived from the default entry of the
1172 directory in which those files reside. */
1173 take_from_entry(default_entry, this_entry, pool);
1176 return SVN_NO_ERROR;
1181 /* Read and parse an old-style 'entries' file in the administrative area
1182 of PATH, filling in ENTRIES with the contents. The results will be
1183 allocated in RESULT_POOL, and temporary allocations will be made in
1186 svn_wc__read_entries_old(apr_hash_t **entries,
1187 const char *dir_abspath,
1188 apr_pool_t *result_pool,
1189 apr_pool_t *scratch_pool)
1193 svn_wc_entry_t *entry;
1194 svn_stream_t *stream;
1197 *entries = apr_hash_make(result_pool);
1199 /* Open the entries file. */
1200 SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES,
1201 scratch_pool, scratch_pool));
1202 SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool));
1204 /* We own the returned data; it is modifiable, so cast away... */
1205 curp = (char *)buf->data;
1206 endp = buf->data + buf->len;
1208 /* If the first byte of the file is not a digit, then it is probably in XML
1210 if (curp != endp && !svn_ctype_isdigit(*curp))
1211 SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len,
1212 result_pool, scratch_pool));
1215 int entryno, entries_format;
1218 /* Read the format line from the entries file. In case we're in the
1219 middle of upgrading a working copy, this line will contain the
1220 original format pre-upgrade. */
1221 SVN_ERR(read_val(&val, &curp, endp));
1223 entries_format = (int)apr_strtoi64(val, NULL, 0);
1225 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1226 _("Invalid version line in entries file "
1228 svn_dirent_local_style(dir_abspath,
1232 while (curp != endp)
1234 svn_error_t *err = read_entry(&entry, &curp, endp,
1235 entries_format, result_pool);
1238 /* We allow extra fields at the end of the line, for
1240 curp = memchr(curp, '\f', endp - curp);
1242 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1243 _("Missing entry terminator"));
1244 if (! err && (curp == endp || *(++curp) != '\n'))
1245 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1246 _("Invalid entry terminator"));
1249 return svn_error_createf(err->apr_err, err,
1250 _("Error at entry %d in entries file for "
1253 svn_dirent_local_style(dir_abspath,
1259 svn_hash_sets(*entries, entry->name, entry);
1263 /* Fill in any implied fields. */
1264 return svn_error_trace(resolve_to_defaults(*entries, result_pool));
1268 /* For non-directory PATHs full entry information is obtained by reading
1269 * the entries for the parent directory of PATH and then extracting PATH's
1270 * entry. If PATH is a directory then only abrieviated information is
1271 * available in the parent directory, more complete information is
1272 * available by reading the entries for PATH itself.
1274 * Note: There is one bit of information about directories that is only
1275 * available in the parent directory, that is the "deleted" state. If PATH
1276 * is a versioned directory then the "deleted" state information will not
1277 * be returned in ENTRY. This means some bits of the code (e.g. revert)
1278 * need to obtain it by directly extracting the directory entry from the
1279 * parent directory's entries. I wonder if this function should handle
1283 svn_wc_entry(const svn_wc_entry_t **entry,
1285 svn_wc_adm_access_t *adm_access,
1286 svn_boolean_t show_hidden,
1289 svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
1290 const char *local_abspath;
1291 svn_wc_adm_access_t *dir_access;
1292 const char *entry_name;
1293 apr_hash_t *entries;
1295 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
1297 /* Does the provided path refer to a directory with an associated
1299 dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool);
1300 if (dir_access == NULL)
1302 /* Damn. Okay. Assume the path is to a child, and let's look for
1303 a baton associated with its parent. */
1305 const char *dir_abspath;
1307 svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool);
1309 dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool);
1313 /* Woo! Got one. Look for "this dir" in the entries hash. */
1317 if (dir_access == NULL)
1321 return SVN_NO_ERROR;
1324 /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
1325 fetch all entries here (optimization) since we know how to filter
1326 out a "hidden" node. */
1327 SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
1328 *entry = svn_hash_gets(entries, entry_name);
1330 if (!show_hidden && *entry != NULL)
1332 svn_boolean_t hidden;
1334 SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry));
1339 return SVN_NO_ERROR;