Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / entries.c
blobbee551a9b6876d3300bc3a0fb5409ff260442410
1 /*
2 * entries.c : manipulating the administrative `entries' file.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
20 #include <string.h>
21 #include <assert.h>
23 #include <apr_strings.h>
25 #include "svn_xml.h"
26 #include "svn_error.h"
27 #include "svn_types.h"
28 #include "svn_time.h"
29 #include "svn_pools.h"
30 #include "svn_path.h"
31 #include "svn_ctype.h"
33 #include "wc.h"
34 #include "adm_files.h"
35 #include "adm_ops.h"
36 #include "entries.h"
37 #include "lock.h"
39 #include "svn_private_config.h"
40 #include "private/svn_wc_private.h"
43 /** Overview **/
45 /* The administrative `entries' file tracks information about files
46 and subdirs within a particular directory.
48 See the section on the `entries' file in libsvn_wc/README, for
49 concrete information about the XML format.
53 /*--------------------------------------------------------------- */
56 /*** reading and writing the entries file ***/
59 static svn_wc_entry_t *
60 alloc_entry(apr_pool_t *pool)
62 svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
63 entry->revision = SVN_INVALID_REVNUM;
64 entry->copyfrom_rev = SVN_INVALID_REVNUM;
65 entry->cmt_rev = SVN_INVALID_REVNUM;
66 entry->kind = svn_node_none;
67 entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
68 entry->depth = svn_depth_infinity;
69 return entry;
73 /* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its
74 * boolean value and add MODIFY_FLAG into *MODIFY_FLAGS, else set *ENTRY_FLAG
75 * false. ENTRY_NAME is the name of the WC-entry. */
76 static svn_error_t *
77 do_bool_attr(svn_boolean_t *entry_flag,
78 apr_uint64_t *modify_flags, apr_uint64_t modify_flag,
79 apr_hash_t *atts, const char *attr_name,
80 const char *entry_name)
82 const char *str = apr_hash_get(atts, attr_name, APR_HASH_KEY_STRING);
84 *entry_flag = FALSE;
85 if (str)
87 if (strcmp(str, "true") == 0)
88 *entry_flag = TRUE;
89 else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
90 *entry_flag = FALSE;
91 else
92 return svn_error_createf
93 (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
94 _("Entry '%s' has invalid '%s' value"),
95 (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name);
97 *modify_flags |= modify_flag;
99 return SVN_NO_ERROR;
102 /* Read an escaped byte on the form 'xHH' from [*BUF, END), placing
103 the byte in *RESULT. Advance *BUF to point after the escape
104 sequence. */
105 static svn_error_t *
106 read_escaped(char *result, char **buf, const char *end)
108 apr_uint64_t val;
109 char digits[3];
111 if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1])
112 || ! svn_ctype_isxdigit((*buf)[2]))
113 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
114 _("Invalid escape sequence"));
115 (*buf)++;
116 digits[0] = *((*buf)++);
117 digits[1] = *((*buf)++);
118 digits[2] = 0;
119 if ((val = apr_strtoi64(digits, NULL, 16)) == 0)
120 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
121 _("Invalid escaped character"));
122 *result = (char) val;
123 return SVN_NO_ERROR;
126 /* Read a field, possibly with escaped bytes, from [*BUF, END),
127 stopping at the terminator. Place the read string in *RESULT, or set
128 *RESULT to NULL if it is the empty string. Allocate the returned string
129 in POOL. Advance *BUF to point after the terminator. */
130 static svn_error_t *
131 read_str(const char **result,
132 char **buf, const char *end,
133 apr_pool_t *pool)
135 svn_stringbuf_t *s = NULL;
136 const char *start;
137 if (*buf == end)
138 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
139 _("Unexpected end of entry"));
140 if (**buf == '\n')
142 *result = NULL;
143 (*buf)++;
144 return SVN_NO_ERROR;
147 start = *buf;
148 while (*buf != end && **buf != '\n')
150 if (**buf == '\\')
152 char c;
153 if (! s)
154 s = svn_stringbuf_ncreate(start, *buf - start, pool);
155 else
156 svn_stringbuf_appendbytes(s, start, *buf - start);
157 (*buf)++;
158 SVN_ERR(read_escaped(&c, buf, end));
159 svn_stringbuf_appendbytes(s, &c, 1);
160 start = *buf;
162 else
163 (*buf)++;
166 if (*buf == end)
167 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
168 _("Unexpected end of entry"));
170 if (s)
172 svn_stringbuf_appendbytes(s, start, *buf - start);
173 *result = s->data;
175 else
176 *result = apr_pstrndup(pool, start, *buf - start);
177 (*buf)++;
178 return SVN_NO_ERROR;
181 /* This is wrapper around read_str() (which see for details); it
182 simply asks svn_path_is_canonical() of the string it reads,
183 returning an error if the test fails. */
184 static svn_error_t *
185 read_path(const char **result,
186 char **buf, const char *end,
187 apr_pool_t *pool)
189 SVN_ERR(read_str(result, buf, end, pool));
190 if (*result && **result && (! svn_path_is_canonical(*result, pool)))
191 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
192 _("Entry contains non-canonical path '%s'"),
193 *result);
194 return SVN_NO_ERROR;
197 /* Read a field from [*BUF, END), terminated by a newline character.
198 The field may not contain escape sequences. The field is not
199 copyed and the buffer is modified in place, by replacing the
200 terminator with a NUL byte. Make *BUF point after the original
201 terminator. */
202 static svn_error_t *
203 read_val(const char **result,
204 char **buf, const char *end)
206 const char *start = *buf;
208 if (*buf == end)
209 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
210 _("Unexpected end of entry"));
211 if (**buf == '\n')
213 (*buf)++;
214 *result = NULL;
215 return SVN_NO_ERROR;
218 while (*buf != end && **buf != '\n')
219 (*buf)++;
220 if (*buf == end)
221 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
222 _("Unexpected end of entry"));
223 **buf = '\0';
224 *result = start;
225 (*buf)++;
226 return SVN_NO_ERROR;
229 /* Read a boolean field from [*BUF, END), placing the result in
230 *RESULT. If there is no boolean value (just a terminator), it
231 defaults to false. Else, the value must match FIELD_NAME, in which
232 case *RESULT will be set to true. Advance *BUF to point after the
233 terminator. */
234 static svn_error_t *
235 read_bool(svn_boolean_t *result, const char *field_name,
236 char **buf, const char *end)
238 const char *val;
239 SVN_ERR(read_val(&val, buf, end));
240 if (val)
242 if (strcmp(val, field_name) != 0)
243 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
244 _("Invalid value for field '%s'"),
245 field_name);
246 *result = TRUE;
248 else
249 *result = FALSE;
250 return SVN_NO_ERROR;
253 /* Read a revision number from [*BUF, END) stopping at the
254 terminator. Set *RESULT to the revision number, or
255 SVN_INVALID_REVNUM if there is none. Use POOL for temporary
256 allocations. Make *BUF point after the terminator. */
257 static svn_error_t *
258 read_revnum(svn_revnum_t *result,
259 char **buf,
260 const char *end,
261 apr_pool_t *pool)
263 const char *val;
265 SVN_ERR(read_val(&val, buf, end));
267 if (val)
268 *result = SVN_STR_TO_REV(val);
269 else
270 *result = SVN_INVALID_REVNUM;
272 return SVN_NO_ERROR;
275 /* Read a timestamp from [*BUF, END) stopping at the terminator.
276 Set *RESULT to the resulting timestamp, or 0 if there is none. Use
277 POOL for temporary allocations. Make *BUF point after the
278 terminator. */
279 static svn_error_t *
280 read_time(apr_time_t *result,
281 char **buf, const char *end,
282 apr_pool_t *pool)
284 const char *val;
286 SVN_ERR(read_val(&val, buf, end));
287 if (val)
288 SVN_ERR(svn_time_from_cstring(result, val, pool));
289 else
290 *result = 0;
292 return SVN_NO_ERROR;
295 /* Allocate an entry from POOL and read it from [*BUF, END). The
296 buffer may be modified in place while parsing. Return the new
297 entry in *NEW_ENTRY. Advance *BUF to point at the end of the entry
298 record. */
299 static svn_error_t *
300 read_entry(svn_wc_entry_t **new_entry,
301 char **buf, const char *end,
302 apr_pool_t *pool)
304 svn_wc_entry_t *entry = alloc_entry(pool);
305 const char *name;
307 #define MAYBE_DONE if (**buf == '\f') goto done
309 /* Find the name and set up the entry under that name. */
310 SVN_ERR(read_path(&name, buf, end, pool));
311 entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR;
313 /* Set up kind. */
315 const char *kindstr;
316 SVN_ERR(read_val(&kindstr, buf, end));
317 if (kindstr)
319 if (! strcmp(kindstr, SVN_WC__ENTRIES_ATTR_FILE_STR))
320 entry->kind = svn_node_file;
321 else if (! strcmp(kindstr, SVN_WC__ENTRIES_ATTR_DIR_STR))
322 entry->kind = svn_node_dir;
323 else
324 return svn_error_createf
325 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
326 _("Entry '%s' has invalid node kind"),
327 (name ? name : SVN_WC_ENTRY_THIS_DIR));
329 else
330 entry->kind = svn_node_none;
332 MAYBE_DONE;
334 /* Attempt to set revision (resolve_to_defaults may do it later, too) */
335 SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
336 MAYBE_DONE;
338 /* Attempt to set up url path (again, see resolve_to_defaults). */
339 SVN_ERR(read_path(&entry->url, buf, end, pool));
340 MAYBE_DONE;
342 /* Set up repository root. Make sure it is a prefix of url. */
343 SVN_ERR(read_path(&entry->repos, buf, end, pool));
344 if (entry->repos && entry->url
345 && ! svn_path_is_ancestor(entry->repos, entry->url))
346 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
347 _("Entry for '%s' has invalid repository "
348 "root"),
349 name ? name : SVN_WC_ENTRY_THIS_DIR);
350 MAYBE_DONE;
352 /* Look for a schedule attribute on this entry. */
354 const char *schedulestr;
355 SVN_ERR(read_val(&schedulestr, buf, end));
356 entry->schedule = svn_wc_schedule_normal;
357 if (schedulestr)
359 if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_ADD))
360 entry->schedule = svn_wc_schedule_add;
361 else if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_DELETE))
362 entry->schedule = svn_wc_schedule_delete;
363 else if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_REPLACE))
364 entry->schedule = svn_wc_schedule_replace;
365 else
366 return svn_error_createf
367 (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
368 _("Entry '%s' has invalid '%s' value"),
369 (name ? name : SVN_WC_ENTRY_THIS_DIR),
370 SVN_WC__ENTRY_ATTR_SCHEDULE);
373 MAYBE_DONE;
375 /* Attempt to set up text timestamp. */
376 SVN_ERR(read_time(&entry->text_time, buf, end, pool));
377 MAYBE_DONE;
379 /* Checksum. */
380 SVN_ERR(read_str(&entry->checksum, buf, end, pool));
381 MAYBE_DONE;
383 /* Setup last-committed values. */
384 SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
385 MAYBE_DONE;
387 SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
388 MAYBE_DONE;
390 SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
391 MAYBE_DONE;
393 /* has-props flag. */
394 SVN_ERR(read_bool(&entry->has_props, SVN_WC__ENTRY_ATTR_HAS_PROPS,
395 buf, end));
396 MAYBE_DONE;
398 /* has-prop-mods flag. */
399 SVN_ERR(read_bool(&entry->has_prop_mods, SVN_WC__ENTRY_ATTR_HAS_PROP_MODS,
400 buf, end));
401 MAYBE_DONE;
403 /* cachable-props string. */
404 SVN_ERR(read_val(&entry->cachable_props, buf, end));
405 if (entry->cachable_props)
406 entry->cachable_props = apr_pstrdup(pool, entry->cachable_props);
407 MAYBE_DONE;
409 /* present-props string. */
410 SVN_ERR(read_val(&entry->present_props, buf, end));
411 if (entry->present_props)
412 entry->present_props = apr_pstrdup(pool, entry->present_props);
413 MAYBE_DONE;
415 /* Is this entry in a state of mental torment (conflict)? */
417 SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
418 MAYBE_DONE;
419 SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
420 MAYBE_DONE;
421 SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
422 MAYBE_DONE;
423 SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
424 MAYBE_DONE;
427 /* Is this entry copied? */
428 SVN_ERR(read_bool(&entry->copied, SVN_WC__ENTRY_ATTR_COPIED, buf, end));
429 MAYBE_DONE;
431 SVN_ERR(read_path(&entry->copyfrom_url, buf, end, pool));
432 MAYBE_DONE;
433 SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
434 MAYBE_DONE;
436 /* Is this entry deleted? */
437 SVN_ERR(read_bool(&entry->deleted, SVN_WC__ENTRY_ATTR_DELETED, buf, end));
438 MAYBE_DONE;
440 /* Is this entry absent? */
441 SVN_ERR(read_bool(&entry->absent, SVN_WC__ENTRY_ATTR_ABSENT, buf, end));
442 MAYBE_DONE;
444 /* Is this entry incomplete? */
445 SVN_ERR(read_bool(&entry->incomplete, SVN_WC__ENTRY_ATTR_INCOMPLETE,
446 buf, end));
447 MAYBE_DONE;
449 /* UUID. */
450 SVN_ERR(read_str(&entry->uuid, buf, end, pool));
451 MAYBE_DONE;
453 /* Lock token. */
454 SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
455 MAYBE_DONE;
457 /* Lock owner. */
458 SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
459 MAYBE_DONE;
461 /* Lock comment. */
462 SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
463 MAYBE_DONE;
465 /* Lock creation date. */
466 SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
467 MAYBE_DONE;
469 /* Changelist. */
470 SVN_ERR(read_str(&entry->changelist, buf, end, pool));
471 MAYBE_DONE;
473 /* Keep entry in working copy after deletion? */
474 SVN_ERR(read_bool(&entry->keep_local, SVN_WC__ENTRY_ATTR_KEEP_LOCAL,
475 buf, end));
476 MAYBE_DONE;
478 /* Translated size */
480 const char *val;
482 /* read_val() returns NULL on an empty (e.g. default) entry line,
483 and entry has already been initialized accordingly already */
484 SVN_ERR(read_val(&val, buf, end));
485 if (val)
486 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
488 MAYBE_DONE;
490 /* Depth. */
492 const char *result;
493 SVN_ERR(read_val(&result, buf, end));
494 if (result)
495 entry->depth = svn_depth_from_word(result);
496 else
497 entry->depth = svn_depth_infinity;
499 MAYBE_DONE;
501 done:
502 *new_entry = entry;
503 return SVN_NO_ERROR;
507 svn_error_t *
508 svn_wc__atts_to_entry(svn_wc_entry_t **new_entry,
509 apr_uint64_t *modify_flags,
510 apr_hash_t *atts,
511 apr_pool_t *pool)
513 svn_wc_entry_t *entry = alloc_entry(pool);
514 const char *name;
516 *modify_flags = 0;
518 /* Find the name and set up the entry under that name. */
519 name = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_NAME, APR_HASH_KEY_STRING);
520 entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR;
522 /* Attempt to set revision (resolve_to_defaults may do it later, too) */
524 const char *revision_str
525 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING);
527 if (revision_str)
529 entry->revision = SVN_STR_TO_REV(revision_str);
530 *modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION;
532 else
533 entry->revision = SVN_INVALID_REVNUM;
536 /* Attempt to set up url path (again, see resolve_to_defaults). */
538 entry->url
539 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING);
541 if (entry->url)
543 *modify_flags |= SVN_WC__ENTRY_MODIFY_URL;
544 entry->url = apr_pstrdup(pool, entry->url);
548 /* Set up repository root. Make sure it is a prefix of url. */
550 entry->repos = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_REPOS,
551 APR_HASH_KEY_STRING);
552 if (entry->repos)
554 if (entry->url && ! svn_path_is_ancestor(entry->repos, entry->url))
555 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
556 _("Entry for '%s' has invalid repository "
557 "root"),
558 name ? name : SVN_WC_ENTRY_THIS_DIR);
559 *modify_flags |= SVN_WC__ENTRY_MODIFY_REPOS;
560 entry->repos = apr_pstrdup(pool, entry->repos);
564 /* Set up kind. */
566 const char *kindstr
567 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_KIND, APR_HASH_KEY_STRING);
569 entry->kind = svn_node_none;
570 if (kindstr)
572 if (! strcmp(kindstr, SVN_WC__ENTRIES_ATTR_FILE_STR))
573 entry->kind = svn_node_file;
574 else if (! strcmp(kindstr, SVN_WC__ENTRIES_ATTR_DIR_STR))
575 entry->kind = svn_node_dir;
576 else
577 return svn_error_createf
578 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
579 _("Entry '%s' has invalid node kind"),
580 (name ? name : SVN_WC_ENTRY_THIS_DIR));
581 *modify_flags |= SVN_WC__ENTRY_MODIFY_KIND;
585 /* Look for a schedule attribute on this entry. */
587 const char *schedulestr
588 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_SCHEDULE, APR_HASH_KEY_STRING);
590 entry->schedule = svn_wc_schedule_normal;
591 if (schedulestr)
593 if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_ADD))
594 entry->schedule = svn_wc_schedule_add;
595 else if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_DELETE))
596 entry->schedule = svn_wc_schedule_delete;
597 else if (! strcmp(schedulestr, SVN_WC__ENTRY_VALUE_REPLACE))
598 entry->schedule = svn_wc_schedule_replace;
599 else if (! strcmp(schedulestr, ""))
600 entry->schedule = svn_wc_schedule_normal;
601 else
602 return svn_error_createf
603 (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
604 _("Entry '%s' has invalid '%s' value"),
605 (name ? name : SVN_WC_ENTRY_THIS_DIR),
606 SVN_WC__ENTRY_ATTR_SCHEDULE);
608 *modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
612 /* Is this entry in a state of mental torment (conflict)? */
614 if ((entry->prejfile
615 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_PREJFILE,
616 APR_HASH_KEY_STRING)))
618 *modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE;
619 /* Normalize "" (used by the log runner) to NULL */
620 entry->prejfile = *(entry->prejfile)
621 ? apr_pstrdup(pool, entry->prejfile) : NULL;
624 if ((entry->conflict_old
625 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CONFLICT_OLD,
626 APR_HASH_KEY_STRING)))
628 *modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD;
629 /* Normalize "" (used by the log runner) to NULL */
630 entry->conflict_old =
631 *(entry->conflict_old)
632 ? apr_pstrdup(pool, entry->conflict_old) : NULL;
635 if ((entry->conflict_new
636 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CONFLICT_NEW,
637 APR_HASH_KEY_STRING)))
639 *modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW;
640 /* Normalize "" (used by the log runner) to NULL */
641 entry->conflict_new =
642 *(entry->conflict_new)
643 ? apr_pstrdup(pool, entry->conflict_new) : NULL;
646 if ((entry->conflict_wrk
647 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CONFLICT_WRK,
648 APR_HASH_KEY_STRING)))
650 *modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK;
651 /* Normalize "" (used by the log runner) to NULL */
652 entry->conflict_wrk =
653 *(entry->conflict_wrk)
654 ? apr_pstrdup(pool, entry->conflict_wrk) : NULL;
658 /* Is this entry copied? */
659 SVN_ERR(do_bool_attr(&entry->copied,
660 modify_flags, SVN_WC__ENTRY_MODIFY_COPIED,
661 atts, SVN_WC__ENTRY_ATTR_COPIED, name));
663 const char *revstr;
665 entry->copyfrom_url = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_COPYFROM_URL,
666 APR_HASH_KEY_STRING);
667 if (entry->copyfrom_url)
669 *modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_URL;
670 entry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
673 revstr = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_COPYFROM_REV,
674 APR_HASH_KEY_STRING);
675 if (revstr)
677 entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
678 *modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_REV;
682 /* Is this entry deleted? */
683 SVN_ERR(do_bool_attr(&entry->deleted,
684 modify_flags, SVN_WC__ENTRY_MODIFY_DELETED,
685 atts, SVN_WC__ENTRY_ATTR_DELETED, name));
687 /* Is this entry absent? */
688 SVN_ERR(do_bool_attr(&entry->absent,
689 modify_flags, SVN_WC__ENTRY_MODIFY_ABSENT,
690 atts, SVN_WC__ENTRY_ATTR_ABSENT, name));
692 /* Is this entry incomplete? */
693 SVN_ERR(do_bool_attr(&entry->incomplete,
694 modify_flags, SVN_WC__ENTRY_MODIFY_INCOMPLETE,
695 atts, SVN_WC__ENTRY_ATTR_INCOMPLETE, name));
697 /* Should this item be kept in the working copy after deletion? */
698 SVN_ERR(do_bool_attr(&entry->keep_local,
699 modify_flags, SVN_WC__ENTRY_MODIFY_KEEP_LOCAL,
700 atts, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, name));
702 /* Attempt to set up timestamps. */
704 const char *text_timestr, *prop_timestr;
706 text_timestr = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_TEXT_TIME,
707 APR_HASH_KEY_STRING);
708 if (text_timestr)
710 if (! strcmp(text_timestr, SVN_WC__TIMESTAMP_WC))
712 /* Special case: a magic string that means 'get this value
713 from the working copy' -- we ignore it here, trusting
714 that the caller of this function know what to do about
715 it. */
717 else
718 SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr,
719 pool));
721 *modify_flags |= SVN_WC__ENTRY_MODIFY_TEXT_TIME;
724 prop_timestr = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_PROP_TIME,
725 APR_HASH_KEY_STRING);
726 if (prop_timestr)
728 if (! strcmp(prop_timestr, SVN_WC__TIMESTAMP_WC))
730 /* Special case: a magic string that means 'get this value
731 from the working copy' -- we ignore it here, trusting
732 that the caller of this function know what to do about
733 it. */
735 else
736 SVN_ERR(svn_time_from_cstring(&entry->prop_time, prop_timestr,
737 pool));
739 *modify_flags |= SVN_WC__ENTRY_MODIFY_PROP_TIME;
743 /* Checksum. */
745 entry->checksum = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CHECKSUM,
746 APR_HASH_KEY_STRING);
747 if (entry->checksum)
749 *modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM;
750 entry->checksum = apr_pstrdup(pool, entry->checksum);
754 /* UUID. */
756 entry->uuid = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_UUID,
757 APR_HASH_KEY_STRING);
758 if (entry->uuid)
760 *modify_flags |= SVN_WC__ENTRY_MODIFY_UUID;
761 entry->uuid = apr_pstrdup(pool, entry->uuid);
765 /* Setup last-committed values. */
767 const char *cmt_datestr, *cmt_revstr;
769 cmt_datestr = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CMT_DATE,
770 APR_HASH_KEY_STRING);
771 if (cmt_datestr)
773 SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
774 *modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_DATE;
776 else
777 entry->cmt_date = 0;
779 cmt_revstr = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CMT_REV,
780 APR_HASH_KEY_STRING);
781 if (cmt_revstr)
783 entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
784 *modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV;
786 else
787 entry->cmt_rev = SVN_INVALID_REVNUM;
789 entry->cmt_author = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CMT_AUTHOR,
790 APR_HASH_KEY_STRING);
791 if (entry->cmt_author)
793 *modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_AUTHOR;
794 entry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
798 /* Lock token. */
799 entry->lock_token = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_LOCK_TOKEN,
800 APR_HASH_KEY_STRING);
801 if (entry->lock_token)
803 *modify_flags |= SVN_WC__ENTRY_MODIFY_LOCK_TOKEN;
804 entry->lock_token = apr_pstrdup(pool, entry->lock_token);
807 /* lock owner. */
808 entry->lock_owner = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_LOCK_OWNER,
809 APR_HASH_KEY_STRING);
810 if (entry->lock_owner)
812 *modify_flags |= SVN_WC__ENTRY_MODIFY_LOCK_OWNER;
813 entry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
816 /* lock comment. */
817 entry->lock_comment = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_LOCK_COMMENT,
818 APR_HASH_KEY_STRING);
819 if (entry->lock_comment)
821 *modify_flags |= SVN_WC__ENTRY_MODIFY_LOCK_COMMENT;
822 entry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
825 /* lock creation date. */
827 const char *cdate_str =
828 apr_hash_get(atts, SVN_WC__ENTRY_ATTR_LOCK_CREATION_DATE,
829 APR_HASH_KEY_STRING);
830 if (cdate_str)
832 SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
833 cdate_str, pool));
834 *modify_flags |= SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE;
838 /* changelist */
839 entry->changelist = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_CHANGELIST,
840 APR_HASH_KEY_STRING);
841 if (entry->changelist)
843 *modify_flags |= SVN_WC__ENTRY_MODIFY_CHANGELIST;
844 entry->changelist = apr_pstrdup(pool, entry->changelist);
847 /* has-props flag. */
848 SVN_ERR(do_bool_attr(&entry->has_props,
849 modify_flags, SVN_WC__ENTRY_MODIFY_HAS_PROPS,
850 atts, SVN_WC__ENTRY_ATTR_HAS_PROPS, name));
852 /* has-prop-mods flag. */
854 const char *has_prop_mods_str
855 = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_HAS_PROP_MODS,
856 APR_HASH_KEY_STRING);
858 if (has_prop_mods_str)
860 if (strcmp(has_prop_mods_str, "true") == 0)
861 entry->has_prop_mods = TRUE;
862 else if (strcmp(has_prop_mods_str, "false") != 0)
863 return svn_error_createf
864 (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
865 _("Entry '%s' has invalid '%s' value"),
866 (name ? name : SVN_WC_ENTRY_THIS_DIR),
867 SVN_WC__ENTRY_ATTR_HAS_PROP_MODS);
869 *modify_flags |= SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS;
873 /* cachable-props string. */
874 entry->cachable_props = apr_hash_get(atts,
875 SVN_WC__ENTRY_ATTR_CACHABLE_PROPS,
876 APR_HASH_KEY_STRING);
877 if (entry->cachable_props)
879 *modify_flags |= SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS;
880 entry->cachable_props = apr_pstrdup(pool, entry->cachable_props);
883 /* present-props string. */
884 entry->present_props = apr_hash_get(atts,
885 SVN_WC__ENTRY_ATTR_PRESENT_PROPS,
886 APR_HASH_KEY_STRING);
887 if (entry->present_props)
889 *modify_flags |= SVN_WC__ENTRY_MODIFY_PRESENT_PROPS;
890 entry->present_props = apr_pstrdup(pool, entry->present_props);
893 /* Translated size */
895 const char *val
896 = apr_hash_get(atts,
897 SVN_WC__ENTRY_ATTR_WORKING_SIZE,
898 APR_HASH_KEY_STRING);
899 if (val)
901 if (! strcmp(val, SVN_WC__WORKING_SIZE_WC))
903 /* Special case (same as the timestamps); ignore here
904 these will be handled elsewhere */
906 else
907 /* Cast to off_t; it's safe: we put in an off_t to start with... */
908 entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
910 *modify_flags |= SVN_WC__ENTRY_MODIFY_WORKING_SIZE;
914 *new_entry = entry;
915 return SVN_NO_ERROR;
918 /* Used when reading an entries file in XML format. */
919 struct entries_accumulator
921 /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
922 apr_hash_t *entries;
924 /* The parser that's parsing it, for signal_expat_bailout(). */
925 svn_xml_parser_t *parser;
927 /* Should we include 'deleted' entries in the hash? */
928 svn_boolean_t show_hidden;
930 /* Don't leave home without one. */
931 apr_pool_t *pool;
933 /* Cleared before handling each entry. */
934 apr_pool_t *scratch_pool;
938 /* Called whenever we find an <open> tag of some kind. */
939 static void
940 handle_start_tag(void *userData, const char *tagname, const char **atts)
942 struct entries_accumulator *accum = userData;
943 apr_hash_t *attributes;
944 svn_wc_entry_t *entry;
945 svn_error_t *err;
946 apr_uint64_t modify_flags = 0;
948 /* We only care about the `entry' tag; all other tags, such as `xml'
949 and `wc-entries', are ignored. */
950 if (strcmp(tagname, SVN_WC__ENTRIES_ENTRY))
951 return;
953 svn_pool_clear(accum->scratch_pool);
954 /* Make an entry from the attributes. */
955 attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
956 err = svn_wc__atts_to_entry(&entry, &modify_flags, attributes, accum->pool);
957 if (err)
959 svn_xml_signal_bailout(err, accum->parser);
960 return;
963 /* Find the name and set up the entry under that name. This
964 should *NOT* be NULL, since svn_wc__atts_to_entry() should
965 have made it into SVN_WC_ENTRY_THIS_DIR. (Note that technically,
966 an entry can't be both absent and scheduled for addition, but we
967 don't need a sanity check for that here.) */
968 if ((entry->deleted || entry->absent)
969 && (entry->schedule != svn_wc_schedule_add)
970 && (entry->schedule != svn_wc_schedule_replace)
971 && (! accum->show_hidden))
973 else
974 apr_hash_set(accum->entries, entry->name, APR_HASH_KEY_STRING, entry);
977 /* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
978 entries in ENTRIES. Use pool for temporary allocations and the pool of
979 ADM_ACCESS for the returned entries. */
980 static svn_error_t *
981 parse_entries_xml(svn_wc_adm_access_t *adm_access,
982 apr_hash_t *entries,
983 svn_boolean_t show_hidden,
984 const char *buf,
985 apr_size_t size,
986 apr_pool_t *pool)
988 svn_xml_parser_t *svn_parser;
989 struct entries_accumulator accum;
991 /* Set up userData for the XML parser. */
992 accum.entries = entries;
993 accum.show_hidden = show_hidden;
994 accum.pool = svn_wc_adm_access_pool(adm_access);
995 accum.scratch_pool = svn_pool_create(pool);
997 /* Create the XML parser */
998 svn_parser = svn_xml_make_parser(&accum,
999 handle_start_tag,
1000 NULL,
1001 NULL,
1002 pool);
1004 /* Store parser in its own userdata, so callbacks can call
1005 svn_xml_signal_bailout() */
1006 accum.parser = svn_parser;
1008 /* Parse. */
1009 SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
1010 apr_psprintf(pool,
1011 _("XML parser failed in '%s'"),
1012 svn_path_local_style
1013 (svn_wc_adm_access_path(adm_access), pool)));
1015 svn_pool_destroy(accum.scratch_pool);
1017 /* Clean up the XML parser */
1018 svn_xml_free_parser(svn_parser);
1020 return SVN_NO_ERROR;
1025 /* Use entry SRC to fill in blank portions of entry DST. SRC itself
1026 may not have any blanks, of course.
1027 Typically, SRC is a parent directory's own entry, and DST is some
1028 child in that directory. */
1029 static void
1030 take_from_entry(svn_wc_entry_t *src, svn_wc_entry_t *dst, apr_pool_t *pool)
1032 /* Inherits parent's revision if doesn't have a revision of one's
1033 own, unless this is a subdirectory. */
1034 if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
1035 dst->revision = src->revision;
1037 /* Inherits parent's url if doesn't have a url of one's own. */
1038 if (! dst->url)
1039 dst->url = svn_path_url_add_component(src->url, dst->name, pool);
1041 if (! dst->repos)
1042 dst->repos = src->repos;
1044 if ((! dst->uuid)
1045 && (! ((dst->schedule == svn_wc_schedule_add)
1046 || (dst->schedule == svn_wc_schedule_replace))))
1048 dst->uuid = src->uuid;
1051 if (! dst->cachable_props)
1052 dst->cachable_props = src->cachable_props;
1056 /* Resolve any missing information in ENTRIES by deducing from the
1057 directory's own entry (which must already be present in ENTRIES). */
1058 static svn_error_t *
1059 resolve_to_defaults(apr_hash_t *entries,
1060 apr_pool_t *pool)
1062 apr_hash_index_t *hi;
1063 svn_wc_entry_t *default_entry
1064 = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
1066 /* First check the dir's own entry for consistency. */
1067 if (! default_entry)
1068 return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
1069 NULL,
1070 _("Missing default entry"));
1072 if (default_entry->revision == SVN_INVALID_REVNUM)
1073 return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
1074 NULL,
1075 _("Default entry has no revision number"));
1077 if (! default_entry->url)
1078 return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
1079 NULL,
1080 _("Default entry is missing URL"));
1083 /* Then use it to fill in missing information in other entries. */
1084 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1086 void *val;
1087 svn_wc_entry_t *this_entry;
1089 apr_hash_this(hi, NULL, NULL, &val);
1090 this_entry = val;
1092 if (this_entry == default_entry)
1093 /* THIS_DIR already has all the information it can possibly
1094 have. */
1095 continue;
1097 if (this_entry->kind == svn_node_dir)
1098 /* Entries that are directories have everything but their
1099 name, kind, and state stored in the THIS_DIR entry of the
1100 directory itself. However, we are disallowing the perusing
1101 of any entries outside of the current entries file. If a
1102 caller wants more info about a directory, it should look in
1103 the entries file in the directory. */
1104 continue;
1106 if (this_entry->kind == svn_node_file)
1107 /* For file nodes that do not explicitly have their ancestry
1108 stated, this can be derived from the default entry of the
1109 directory in which those files reside. */
1110 take_from_entry(default_entry, this_entry, pool);
1113 return SVN_NO_ERROR;
1118 /* Fill the entries cache in ADM_ACCESS. Either the full hash cache will be
1119 populated, if SHOW_HIDDEN is TRUE, or the truncated hash cache will be
1120 populated if SHOW_HIDDEN is FALSE. POOL is used for local memory
1121 allocation, the access baton pool is used for the cache. */
1122 static svn_error_t *
1123 read_entries(svn_wc_adm_access_t *adm_access,
1124 svn_boolean_t show_hidden,
1125 apr_pool_t *pool)
1127 const char *path = svn_wc_adm_access_path(adm_access);
1128 apr_file_t *infile = NULL;
1129 svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
1130 apr_hash_t *entries = apr_hash_make(svn_wc_adm_access_pool(adm_access));
1131 char *curp, *endp;
1132 svn_wc_entry_t *entry;
1133 int entryno;
1135 /* Open the entries file. */
1136 SVN_ERR(svn_wc__open_adm_file(&infile, path,
1137 SVN_WC__ADM_ENTRIES, APR_READ, pool));
1139 SVN_ERR(svn_stringbuf_from_aprfile(&buf, infile, pool));
1141 curp = buf->data;
1142 endp = buf->data + buf->len;
1144 /* If the first byte of the file is not a digit, then it is probably in XML
1145 format. */
1146 if (curp != endp && !svn_ctype_isdigit(*curp))
1147 SVN_ERR(parse_entries_xml(adm_access, entries, show_hidden,
1148 buf->data, buf->len, pool));
1149 else
1151 /* Skip format line. */
1152 /* ### Could read it here and report it to caller if it wants it. */
1153 curp = memchr(curp, '\n', buf->len);
1154 if (! curp)
1155 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1156 _("Invalid version line in entries file "
1157 "of '%s'"),
1158 svn_path_local_style(path, pool));
1159 ++curp;
1160 entryno = 1;
1162 while (curp != endp)
1164 svn_error_t *err = read_entry(&entry, &curp, endp,
1165 svn_wc_adm_access_pool(adm_access));
1166 if (! err)
1168 /* We allow extra fields at the end of the line, for
1169 extensibility. */
1170 curp = memchr(curp, '\f', endp - curp);
1171 if (! curp)
1172 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1173 _("Missing entry terminator"));
1174 if (! err && (curp == endp || *(++curp) != '\n'))
1175 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1176 _("Invalid entry terminator"));
1178 if (err)
1179 return svn_error_createf(err->apr_err, err,
1180 _("Error at entry %d in entries file for "
1181 "'%s':"),
1182 entryno, svn_path_local_style(path, pool));
1184 ++curp;
1185 ++entryno;
1186 if ((entry->deleted || entry->absent)
1187 && (entry->schedule != svn_wc_schedule_add)
1188 && (entry->schedule != svn_wc_schedule_replace)
1189 && (! show_hidden))
1191 else
1192 apr_hash_set(entries, entry->name, APR_HASH_KEY_STRING, entry);
1196 /* Close the entries file. */
1197 SVN_ERR(svn_wc__close_adm_file(infile, svn_wc_adm_access_path(adm_access),
1198 SVN_WC__ADM_ENTRIES, 0, pool));
1200 /* Fill in any implied fields. */
1201 SVN_ERR(resolve_to_defaults(entries, svn_wc_adm_access_pool(adm_access)));
1203 svn_wc__adm_access_set_entries(adm_access, show_hidden, entries);
1205 return SVN_NO_ERROR;
1208 /* For non-directory PATHs full entry information is obtained by reading
1209 * the entries for the parent directory of PATH and then extracting PATH's
1210 * entry. If PATH is a directory then only abrieviated information is
1211 * available in the parent directory, more complete information is
1212 * available by reading the entries for PATH itself.
1214 * Note: There is one bit of information about directories that is only
1215 * available in the parent directory, that is the "deleted" state. If PATH
1216 * is a versioned directory then the "deleted" state information will not
1217 * be returned in ENTRY. This means some bits of the code (e.g. revert)
1218 * need to obtain it by directly extracting the directory entry from the
1219 * parent directory's entries. I wonder if this function should handle
1220 * that?
1222 svn_error_t *
1223 svn_wc_entry(const svn_wc_entry_t **entry,
1224 const char *path,
1225 svn_wc_adm_access_t *adm_access,
1226 svn_boolean_t show_hidden,
1227 apr_pool_t *pool)
1229 const char *entry_name;
1230 svn_wc_adm_access_t *dir_access;
1232 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access, path, pool));
1233 if (! dir_access)
1235 const char *dir_path, *base_name;
1236 svn_path_split(path, &dir_path, &base_name, pool);
1237 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access, dir_path,
1238 pool));
1239 entry_name = base_name;
1241 else
1242 entry_name = SVN_WC_ENTRY_THIS_DIR;
1244 if (dir_access)
1246 apr_hash_t *entries;
1247 SVN_ERR(svn_wc_entries_read(&entries, dir_access, show_hidden, pool));
1248 *entry = apr_hash_get(entries, entry_name, APR_HASH_KEY_STRING);
1250 else
1251 *entry = NULL;
1253 return SVN_NO_ERROR;
1257 svn_error_t *
1258 svn_wc__entry_versioned_internal(const svn_wc_entry_t **entry,
1259 const char *path,
1260 svn_wc_adm_access_t *adm_access,
1261 svn_boolean_t show_hidden,
1262 const char *caller_filename,
1263 int caller_lineno,
1264 apr_pool_t *pool)
1266 SVN_ERR(svn_wc_entry(entry, path, adm_access, show_hidden, pool));
1268 if (! *entry)
1270 svn_error_t *err
1271 = svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
1272 _("'%s' is not under version control"),
1273 svn_path_local_style(path, pool));
1275 err->file = caller_filename;
1276 err->line = caller_lineno;
1277 return err;
1280 return SVN_NO_ERROR;
1284 #if 0
1285 /* This is #if 0'd out until I decide where to use it. --cmpilato */
1287 /* Run a simple validity check on the ENTRIES (the list of entries
1288 associated with the directory PATH). */
1289 static svn_error_t *
1290 check_entries(apr_hash_t *entries,
1291 const char *path,
1292 apr_pool_t *pool)
1294 svn_wc_entry_t *default_entry;
1295 apr_hash_index_t *hi;
1297 default_entry = apr_hash_get(entries,
1298 SVN_WC_ENTRY_THIS_DIR,
1299 APR_HASH_KEY_STRING);
1300 if (! default_entry)
1301 return svn_error_createf
1302 (SVN_ERR_WC_CORRUPT, NULL,
1303 _("Corrupt working copy: '%s' has no default entry"),
1304 svn_path_local_style(path, pool));
1306 /* Validate DEFAULT_ENTRY's current schedule. */
1307 switch (default_entry->schedule)
1309 case svn_wc_schedule_normal:
1310 case svn_wc_schedule_add:
1311 case svn_wc_schedule_delete:
1312 case svn_wc_schedule_replace:
1313 /* These are all valid states */
1314 break;
1316 default:
1317 /* This is an invalid state */
1318 return svn_error_createf
1319 (SVN_ERR_WC_CORRUPT, NULL,
1320 _("Corrupt working copy: directory '%s' has an invalid schedule"),
1321 svn_path_local_style(path, pool));
1324 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1326 const void *key;
1327 const char *name;
1328 void *val;
1329 svn_wc_entry_t *this_entry;
1331 /* Get the entry */
1332 apr_hash_this(hi, &key, NULL, &val);
1333 this_entry = val;
1334 name = key;
1336 /* We've already checked the "this dir" entry */
1337 if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR ))
1338 continue;
1340 /* Validate THIS_ENTRY's current schedule. */
1341 switch (this_entry->schedule)
1343 case svn_wc_schedule_normal:
1344 case svn_wc_schedule_add:
1345 case svn_wc_schedule_delete:
1346 case svn_wc_schedule_replace:
1347 /* These are all valid states */
1348 break;
1350 default:
1351 /* This is an invalid state */
1352 return svn_error_createf
1353 (SVN_ERR_WC_CORRUPT, NULL,
1354 _("Corrupt working copy: "
1355 "'%s' in directory '%s' has an invalid schedule"),
1356 name, svn_path_local_style(path, pool));
1359 if ((default_entry->schedule == svn_wc_schedule_add)
1360 && (this_entry->schedule != svn_wc_schedule_add))
1361 return svn_error_createf
1362 (SVN_ERR_WC_CORRUPT, NULL,
1363 _("Corrupt working copy: '%s' in directory '%s' (which is "
1364 "scheduled for addition) is not itself scheduled for addition"),
1365 name, svn_path_local_style(path, pool));
1367 if ((default_entry->schedule == svn_wc_schedule_delete)
1368 && (this_entry->schedule != svn_wc_schedule_delete))
1369 return svn_error_createf
1370 (SVN_ERR_WC_CORRUPT, NULL,
1371 _("Corrupt working copy: '%s' in directory '%s' (which is "
1372 "scheduled for deletion) is not itself scheduled for deletion"),
1373 name, svn_path_local_style(path, pool));
1375 if ((default_entry->schedule == svn_wc_schedule_replace)
1376 && (this_entry->schedule == svn_wc_schedule_normal))
1377 return svn_error_createf
1378 (SVN_ERR_WC_CORRUPT, NULL,
1379 _("Corrupt working copy: '%s' in directory '%s' (which is "
1380 "scheduled for replacement) has an invalid schedule"),
1381 name, svn_path_local_style(path, pool));
1384 return SVN_NO_ERROR;
1386 #endif /* 0 */
1389 svn_error_t *
1390 svn_wc_entries_read(apr_hash_t **entries,
1391 svn_wc_adm_access_t *adm_access,
1392 svn_boolean_t show_hidden,
1393 apr_pool_t *pool)
1395 apr_hash_t *new_entries;
1397 new_entries = svn_wc__adm_access_entries(adm_access, show_hidden, pool);
1398 if (! new_entries)
1400 /* Ask for the deleted entries because most operations request them
1401 at some stage, getting them now avoids a second file parse. */
1402 SVN_ERR(read_entries(adm_access, TRUE, pool));
1404 new_entries = svn_wc__adm_access_entries(adm_access, show_hidden, pool);
1407 *entries = new_entries;
1408 return SVN_NO_ERROR;
1411 /* If STR is non-null, append STR to BUF, terminating it with a
1412 newline, escaping bytes that needs escaping, using POOL for
1413 temporary allocations. Else if STR is null, just append the
1414 terminating newline. */
1415 static void
1416 write_str(svn_stringbuf_t *buf, const char *str, apr_pool_t *pool)
1418 const char *start = str;
1419 if (str)
1421 while (*str)
1423 /* Escape control characters and | and \. */
1424 if (svn_ctype_iscntrl(*str) || *str == '\\')
1426 svn_stringbuf_appendbytes(buf, start, str - start);
1427 svn_stringbuf_appendcstr(buf,
1428 apr_psprintf(pool, "\\x%02x", *str));
1429 start = str + 1;
1431 ++str;
1433 svn_stringbuf_appendbytes(buf, start, str - start);
1435 svn_stringbuf_appendbytes(buf, "\n", 1);
1438 /* Append the string VAL of length LEN to BUF, without escaping any
1439 bytes, followed by a terminator. If VAL is NULL, ignore LEN and
1440 append just the terminator. */
1441 static void
1442 write_val(svn_stringbuf_t *buf, const char *val, apr_size_t len)
1444 if (val)
1445 svn_stringbuf_appendbytes(buf, val, len);
1446 svn_stringbuf_appendbytes(buf, "\n", 1);
1449 /* If VAL is true, append FIELD_NAME followed by a terminator to BUF.
1450 Else, just append the terminator. */
1451 static void
1452 write_bool(svn_stringbuf_t *buf, const char *field_name, svn_boolean_t val)
1454 write_val(buf, val ? field_name : NULL, val ? strlen(field_name) : 0);
1457 /* If REVNUM is valid, append the representation of REVNUM to BUF
1458 followed by a terminator, using POOL for temporary allocations.
1459 Otherwise, just append the terminator. */
1460 static void
1461 write_revnum(svn_stringbuf_t *buf, svn_revnum_t revnum, apr_pool_t *pool)
1463 if (SVN_IS_VALID_REVNUM(revnum))
1464 svn_stringbuf_appendcstr(buf, apr_ltoa(pool, revnum));
1465 svn_stringbuf_appendbytes(buf, "\n", 1);
1468 /* Append the timestamp VAL to BUF (or the empty string if VAL is 0),
1469 followed by a terminator. Use POOL for temporary allocations. */
1470 static void
1471 write_time(svn_stringbuf_t *buf, apr_time_t val, apr_pool_t *pool)
1473 if (val)
1474 svn_stringbuf_appendcstr(buf, svn_time_to_cstring(val, pool));
1475 svn_stringbuf_appendbytes(buf, "\n", 1);
1478 /* Append a single entry ENTRY to the string OUTPUT, using the
1479 entry for "this dir" THIS_DIR for comparison/optimization.
1480 Allocations are done in POOL. */
1481 static void
1482 write_entry(svn_stringbuf_t *buf,
1483 svn_wc_entry_t *entry,
1484 const char *name,
1485 svn_wc_entry_t *this_dir,
1486 apr_pool_t *pool)
1488 const char *valuestr;
1489 svn_revnum_t valuerev;
1490 svn_boolean_t is_this_dir = strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0;
1491 svn_boolean_t is_subdir = ! is_this_dir && (entry->kind == svn_node_dir);
1493 assert(name);
1495 /* Name. */
1496 write_str(buf, name, pool);
1498 /* Kind. */
1499 switch (entry->kind)
1501 case svn_node_dir:
1502 write_val(buf, SVN_WC__ENTRIES_ATTR_DIR_STR,
1503 sizeof(SVN_WC__ENTRIES_ATTR_DIR_STR) - 1);
1504 break;
1506 case svn_node_none:
1507 write_val(buf, NULL, 0);
1508 break;
1510 case svn_node_file:
1511 case svn_node_unknown:
1512 default:
1513 write_val(buf, SVN_WC__ENTRIES_ATTR_FILE_STR,
1514 sizeof(SVN_WC__ENTRIES_ATTR_FILE_STR) - 1);
1515 break;
1518 /* Revision. */
1519 if (is_this_dir || (! is_subdir && entry->revision != this_dir->revision))
1520 valuerev = entry->revision;
1521 else
1522 valuerev = SVN_INVALID_REVNUM;
1523 write_revnum(buf, valuerev, pool);
1525 /* URL. */
1526 if (is_this_dir ||
1527 (! is_subdir && strcmp(svn_path_url_add_component(this_dir->url, name,
1528 pool),
1529 entry->url) != 0))
1530 valuestr = entry->url;
1531 else
1532 valuestr = NULL;
1533 write_str(buf, valuestr, pool);
1535 /* Repository root. */
1536 if (! is_subdir
1537 && (is_this_dir
1538 || (this_dir->repos == NULL
1539 || (entry->repos
1540 && strcmp(this_dir->repos, entry->repos) != 0))))
1541 valuestr = entry->repos;
1542 else
1543 valuestr = NULL;
1544 write_str(buf, valuestr, pool);
1546 /* Schedule. */
1547 switch (entry->schedule)
1549 case svn_wc_schedule_add:
1550 write_val(buf, SVN_WC__ENTRY_VALUE_ADD,
1551 sizeof(SVN_WC__ENTRY_VALUE_ADD) - 1);
1552 break;
1554 case svn_wc_schedule_delete:
1555 write_val(buf, SVN_WC__ENTRY_VALUE_DELETE,
1556 sizeof(SVN_WC__ENTRY_VALUE_DELETE) - 1);
1557 break;
1559 case svn_wc_schedule_replace:
1560 write_val(buf, SVN_WC__ENTRY_VALUE_REPLACE,
1561 sizeof(SVN_WC__ENTRY_VALUE_REPLACE) - 1);
1562 break;
1564 case svn_wc_schedule_normal:
1565 default:
1566 write_val(buf, NULL, 0);
1567 break;
1570 /* Text time. */
1571 write_time(buf, entry->text_time, pool);
1573 /* Checksum. */
1574 write_val(buf, entry->checksum,
1575 entry->checksum ? strlen(entry->checksum) : 0);
1577 /* Last-commit stuff */
1578 write_time(buf, entry->cmt_date, pool);
1579 write_revnum(buf, entry->cmt_rev, pool);
1580 write_str(buf, entry->cmt_author, pool);
1582 /* has-props flag. */
1583 write_bool(buf, SVN_WC__ENTRY_ATTR_HAS_PROPS, entry->has_props);
1585 /* has-prop-mods flag. */
1586 write_bool(buf, SVN_WC__ENTRY_ATTR_HAS_PROP_MODS, entry->has_prop_mods);
1588 /* cachable-props string. */
1589 if (is_this_dir
1590 || ! this_dir->cachable_props || ! entry->cachable_props
1591 || strcmp(this_dir->cachable_props, entry->cachable_props) != 0)
1592 valuestr = entry->cachable_props;
1593 else
1594 valuestr = NULL;
1595 write_val(buf, valuestr, valuestr ? strlen(valuestr) : 0);
1597 /* present-props string. */
1598 write_val(buf, entry->present_props,
1599 entry->present_props ? strlen(entry->present_props) : 0);
1601 /* Conflict. */
1602 write_str(buf, entry->prejfile, pool);
1603 write_str(buf, entry->conflict_old, pool);
1604 write_str(buf, entry->conflict_new, pool);
1605 write_str(buf, entry->conflict_wrk, pool);
1607 write_bool(buf, SVN_WC__ENTRY_ATTR_COPIED, entry->copied);
1609 /* Copy-related Stuff */
1610 write_str(buf, entry->copyfrom_url, pool);
1611 write_revnum(buf, entry->copyfrom_rev, pool);
1613 /* Deleted state */
1614 write_bool(buf, SVN_WC__ENTRY_ATTR_DELETED, entry->deleted);
1616 /* Absent state */
1617 write_bool(buf, SVN_WC__ENTRY_ATTR_ABSENT, entry->absent);
1619 /* Incomplete state */
1620 write_bool(buf, SVN_WC__ENTRY_ATTR_INCOMPLETE, entry->incomplete);
1622 /* UUID. */
1623 if (is_this_dir || ! this_dir->uuid || ! entry->uuid
1624 || strcmp(this_dir->uuid, entry->uuid) != 0)
1625 valuestr = entry->uuid;
1626 else
1627 valuestr = NULL;
1628 write_val(buf, valuestr, valuestr ? strlen(valuestr) : 0);
1630 /* Lock token. */
1631 write_str(buf, entry->lock_token, pool);
1633 /* Lock owner. */
1634 write_str(buf, entry->lock_owner, pool);
1636 /* Lock comment. */
1637 write_str(buf, entry->lock_comment, pool);
1639 /* Lock creation date. */
1640 write_time(buf, entry->lock_creation_date, pool);
1642 /* Changelist. */
1643 write_str(buf, entry->changelist, pool);
1645 /* Keep in working copy flag. */
1646 write_bool(buf, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, entry->keep_local);
1648 /* Translated size */
1650 const char *val
1651 = (entry->working_size != SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN)
1652 ? apr_off_t_toa(pool, entry->working_size) : "";
1653 write_val(buf, val, strlen(val));
1656 /* Depth. */
1657 if (is_subdir || entry->depth == svn_depth_infinity)
1659 write_val(buf, NULL, 0);
1661 else
1663 const char *val = svn_depth_to_word(entry->depth);
1664 write_val(buf, val, strlen(val));
1667 /* Remove redundant separators at the end of the entry. */
1668 while (buf->len > 1 && buf->data[buf->len - 2] == '\n')
1669 buf->len--;
1671 svn_stringbuf_appendbytes(buf, "\f\n", 2);
1674 /* Append a single entry ENTRY as an XML element to the string OUTPUT,
1675 using the entry for "this dir" THIS_DIR for
1676 comparison/optimization. Allocations are done in POOL. */
1677 static void
1678 write_entry_xml(svn_stringbuf_t **output,
1679 svn_wc_entry_t *entry,
1680 const char *name,
1681 svn_wc_entry_t *this_dir,
1682 apr_pool_t *pool)
1684 apr_hash_t *atts = apr_hash_make(pool);
1685 const char *valuestr;
1687 /*** Create a hash that represents an entry. ***/
1689 assert(name);
1691 /* Name */
1692 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_NAME, APR_HASH_KEY_STRING,
1693 entry->name);
1695 /* Revision */
1696 if (SVN_IS_VALID_REVNUM(entry->revision))
1697 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING,
1698 apr_psprintf(pool, "%ld", entry->revision));
1700 /* URL */
1701 if (entry->url)
1702 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING,
1703 entry->url);
1705 /* Repository root */
1706 if (entry->repos)
1707 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REPOS, APR_HASH_KEY_STRING,
1708 entry->repos);
1710 /* Kind */
1711 switch (entry->kind)
1713 case svn_node_dir:
1714 valuestr = SVN_WC__ENTRIES_ATTR_DIR_STR;
1715 break;
1717 case svn_node_none:
1718 valuestr = NULL;
1719 break;
1721 case svn_node_file:
1722 case svn_node_unknown:
1723 default:
1724 valuestr = SVN_WC__ENTRIES_ATTR_FILE_STR;
1725 break;
1727 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_KIND, APR_HASH_KEY_STRING, valuestr);
1729 /* Schedule */
1730 switch (entry->schedule)
1732 case svn_wc_schedule_add:
1733 valuestr = SVN_WC__ENTRY_VALUE_ADD;
1734 break;
1736 case svn_wc_schedule_delete:
1737 valuestr = SVN_WC__ENTRY_VALUE_DELETE;
1738 break;
1740 case svn_wc_schedule_replace:
1741 valuestr = SVN_WC__ENTRY_VALUE_REPLACE;
1742 break;
1744 case svn_wc_schedule_normal:
1745 default:
1746 valuestr = NULL;
1747 break;
1749 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_SCHEDULE, APR_HASH_KEY_STRING,
1750 valuestr);
1752 /* Conflicts */
1753 if (entry->conflict_old)
1754 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CONFLICT_OLD, APR_HASH_KEY_STRING,
1755 entry->conflict_old);
1757 if (entry->conflict_new)
1758 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CONFLICT_NEW, APR_HASH_KEY_STRING,
1759 entry->conflict_new);
1761 if (entry->conflict_wrk)
1762 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CONFLICT_WRK, APR_HASH_KEY_STRING,
1763 entry->conflict_wrk);
1765 if (entry->prejfile)
1766 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_PREJFILE, APR_HASH_KEY_STRING,
1767 entry->prejfile);
1769 /* Copy-related Stuff */
1770 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_COPIED, APR_HASH_KEY_STRING,
1771 (entry->copied ? "true" : NULL));
1773 if (SVN_IS_VALID_REVNUM(entry->copyfrom_rev))
1774 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_COPYFROM_REV, APR_HASH_KEY_STRING,
1775 apr_psprintf(pool, "%ld",
1776 entry->copyfrom_rev));
1778 if (entry->copyfrom_url)
1779 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_COPYFROM_URL, APR_HASH_KEY_STRING,
1780 entry->copyfrom_url);
1782 /* Deleted state */
1783 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_DELETED, APR_HASH_KEY_STRING,
1784 (entry->deleted ? "true" : NULL));
1786 /* Absent state */
1787 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_ABSENT, APR_HASH_KEY_STRING,
1788 (entry->absent ? "true" : NULL));
1790 /* Incomplete state */
1791 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_INCOMPLETE, APR_HASH_KEY_STRING,
1792 (entry->incomplete ? "true" : NULL));
1794 /* Timestamps */
1795 if (entry->text_time)
1797 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_TEXT_TIME, APR_HASH_KEY_STRING,
1798 svn_time_to_cstring(entry->text_time, pool));
1800 if (entry->prop_time)
1802 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_PROP_TIME, APR_HASH_KEY_STRING,
1803 svn_time_to_cstring(entry->prop_time, pool));
1806 /* Checksum */
1807 if (entry->checksum)
1808 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CHECKSUM, APR_HASH_KEY_STRING,
1809 entry->checksum);
1811 /* Last-commit stuff */
1812 if (SVN_IS_VALID_REVNUM(entry->cmt_rev))
1813 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CMT_REV, APR_HASH_KEY_STRING,
1814 apr_psprintf(pool, "%ld", entry->cmt_rev));
1816 if (entry->cmt_author)
1817 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CMT_AUTHOR, APR_HASH_KEY_STRING,
1818 entry->cmt_author);
1820 if (entry->uuid)
1821 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_UUID, APR_HASH_KEY_STRING,
1822 entry->uuid);
1824 if (entry->cmt_date)
1826 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CMT_DATE, APR_HASH_KEY_STRING,
1827 svn_time_to_cstring(entry->cmt_date, pool));
1830 /* Lock token */
1831 if (entry->lock_token)
1832 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_LOCK_TOKEN, APR_HASH_KEY_STRING,
1833 entry->lock_token);
1835 /* Lock owner */
1836 if (entry->lock_owner)
1837 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_LOCK_OWNER, APR_HASH_KEY_STRING,
1838 entry->lock_owner);
1840 /* Lock comment */
1841 if (entry->lock_comment)
1842 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_LOCK_COMMENT, APR_HASH_KEY_STRING,
1843 entry->lock_comment);
1845 /* Lock creation date */
1846 if (entry->lock_creation_date)
1847 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_LOCK_CREATION_DATE,
1848 APR_HASH_KEY_STRING,
1849 svn_time_to_cstring(entry->lock_creation_date, pool));
1851 /* Has-props flag. */
1852 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_HAS_PROPS, APR_HASH_KEY_STRING,
1853 (entry->has_props ? "true" : NULL));
1855 /* Prop-mods. */
1856 if (entry->has_prop_mods)
1857 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_HAS_PROP_MODS,
1858 APR_HASH_KEY_STRING, "true");
1860 /* Cachable props. */
1861 if (entry->cachable_props && *entry->cachable_props)
1862 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CACHABLE_PROPS,
1863 APR_HASH_KEY_STRING, entry->cachable_props);
1865 /* Present props. */
1866 if (entry->present_props
1867 && *entry->present_props)
1868 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_PRESENT_PROPS,
1869 APR_HASH_KEY_STRING, entry->present_props);
1871 /*** Now, remove stuff that can be derived through inheritance rules. ***/
1873 /* We only want to write out 'revision' and 'url' for the
1874 following things:
1875 1. the current directory's "this dir" entry.
1876 2. non-directory entries:
1877 a. which are marked for addition (and consequently should
1878 have an invalid revnum)
1879 b. whose revision or url is valid and different than
1880 that of the "this dir" entry.
1882 if (strcmp(name, SVN_WC_ENTRY_THIS_DIR))
1884 /* This is NOT the "this dir" entry */
1885 if (! strcmp(name, "."))
1887 /* By golly, if this isn't recognized as the "this dir"
1888 entry, and it looks like '.', we're just asking for an
1889 infinite recursion to happen. Abort! */
1890 abort();
1893 if (entry->kind == svn_node_dir)
1895 /* We don't write url, revision, repository root or uuid for subdir
1896 entries. */
1897 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING,
1898 NULL);
1899 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING,
1900 NULL);
1901 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REPOS, APR_HASH_KEY_STRING,
1902 NULL);
1903 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_UUID, APR_HASH_KEY_STRING,
1904 NULL);
1906 else
1908 /* If this is not the "this dir" entry, and the revision is
1909 the same as that of the "this dir" entry, don't write out
1910 the revision. */
1911 if (entry->revision == this_dir->revision)
1912 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REVISION,
1913 APR_HASH_KEY_STRING, NULL);
1915 /* If this is not the "this dir" entry, and the uuid is
1916 the same as that of the "this dir" entry, don't write out
1917 the uuid. */
1918 if (entry->uuid && this_dir->uuid)
1920 if (strcmp(entry->uuid, this_dir->uuid) == 0)
1921 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_UUID,
1922 APR_HASH_KEY_STRING, NULL);
1925 /* If this is not the "this dir" entry, and the url is
1926 trivially calculable from that of the "this dir" entry,
1927 don't write out the url */
1928 if (entry->url)
1930 if (strcmp(entry->url,
1931 svn_path_url_add_component(this_dir->url,
1932 name, pool)) == 0)
1933 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_URL,
1934 APR_HASH_KEY_STRING, NULL);
1937 /* Avoid writing repository root if that's the same as this_dir. */
1938 if (entry->repos && this_dir->repos
1939 && strcmp(entry->repos, this_dir->repos) == 0)
1940 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_REPOS, APR_HASH_KEY_STRING,
1941 NULL);
1943 /* Cachable props are also inherited. */
1944 if (entry->cachable_props && this_dir->cachable_props
1945 && strcmp(entry->cachable_props, this_dir->cachable_props) == 0)
1946 apr_hash_set(atts, SVN_WC__ENTRY_ATTR_CACHABLE_PROPS,
1947 APR_HASH_KEY_STRING, NULL);
1951 /* Append the entry onto the accumulating string. */
1952 svn_xml_make_open_tag_hash(output,
1953 pool,
1954 svn_xml_self_closing,
1955 SVN_WC__ENTRIES_ENTRY,
1956 atts);
1959 /* Construct an entries file from the ENTRIES hash in XML format in a
1960 newly allocated stringbuf and return it in *OUTPUT. Allocate the
1961 result in POOL. THIS_DIR is the this_dir entry in ENTRIES. */
1962 static void
1963 write_entries_xml(svn_stringbuf_t **output,
1964 apr_hash_t *entries,
1965 svn_wc_entry_t *this_dir,
1966 apr_pool_t *pool)
1968 apr_hash_index_t *hi;
1969 apr_pool_t *subpool = svn_pool_create(pool);
1971 svn_xml_make_header(output, pool);
1972 svn_xml_make_open_tag(output, pool, svn_xml_normal,
1973 SVN_WC__ENTRIES_TOPLEVEL,
1974 "xmlns",
1975 SVN_XML_NAMESPACE,
1976 NULL);
1978 /* Write out "this dir" */
1979 write_entry_xml(output, this_dir, SVN_WC_ENTRY_THIS_DIR, this_dir, pool);
1981 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1983 const void *key;
1984 void *val;
1985 svn_wc_entry_t *this_entry;
1987 svn_pool_clear(subpool);
1989 /* Get the entry and make sure its attributes are up-to-date. */
1990 apr_hash_this(hi, &key, NULL, &val);
1991 this_entry = val;
1993 /* Don't rewrite the "this dir" entry! */
1994 if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR ))
1995 continue;
1997 /* Append the entry to output */
1998 write_entry_xml(output, this_entry, key, this_dir, subpool);
2001 svn_xml_make_close_tag(output, pool, SVN_WC__ENTRIES_TOPLEVEL);
2003 svn_pool_destroy(subpool);
2006 svn_error_t *
2007 svn_wc__entries_write(apr_hash_t *entries,
2008 svn_wc_adm_access_t *adm_access,
2009 apr_pool_t *pool)
2011 svn_error_t *err = SVN_NO_ERROR;
2012 svn_stringbuf_t *bigstr = NULL;
2013 apr_file_t *outfile = NULL;
2014 apr_hash_index_t *hi;
2015 svn_wc_entry_t *this_dir;
2017 SVN_ERR(svn_wc__adm_write_check(adm_access));
2019 /* Get a copy of the "this dir" entry for comparison purposes. */
2020 this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
2021 APR_HASH_KEY_STRING);
2023 /* If there is no "this dir" entry, something is wrong. */
2024 if (! this_dir)
2025 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
2026 _("No default entry in directory '%s'"),
2027 svn_path_local_style
2028 (svn_wc_adm_access_path(adm_access), pool));
2030 /* Open entries file for writing. It's important we don't use APR_EXCL
2031 * here. Consider what happens if a log file is interrupted, it may
2032 * leave a .svn/tmp/entries file behind. Then when cleanup reruns the
2033 * log file, and it attempts to modify the entries file, APR_EXCL would
2034 * cause an error that prevents cleanup running. We don't use log file
2035 * tags such as SVN_WC__LOG_MV to move entries files so any existing file
2036 * is not "valuable".
2038 SVN_ERR(svn_wc__open_adm_file(&outfile,
2039 svn_wc_adm_access_path(adm_access),
2040 SVN_WC__ADM_ENTRIES,
2041 (APR_WRITE | APR_CREATE),
2042 pool));
2044 if (svn_wc__adm_wc_format(adm_access) > SVN_WC__XML_ENTRIES_VERSION)
2046 apr_pool_t *subpool = svn_pool_create(pool);
2047 bigstr = svn_stringbuf_createf(pool, "%d\n",
2048 svn_wc__adm_wc_format(adm_access));
2049 /* Write out "this dir" */
2050 write_entry(bigstr, this_dir, SVN_WC_ENTRY_THIS_DIR, this_dir, pool);
2052 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2054 const void *key;
2055 void *val;
2056 svn_wc_entry_t *this_entry;
2058 svn_pool_clear(subpool);
2060 /* Get the entry and make sure its attributes are up-to-date. */
2061 apr_hash_this(hi, &key, NULL, &val);
2062 this_entry = val;
2064 /* Don't rewrite the "this dir" entry! */
2065 if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR ))
2066 continue;
2068 /* Append the entry to BIGSTR */
2069 write_entry(bigstr, this_entry, key, this_dir, subpool);
2072 svn_pool_destroy(subpool);
2074 else
2075 /* This is needed during cleanup of a not yet upgraded WC. */
2076 write_entries_xml(&bigstr, entries, this_dir, pool);
2078 SVN_ERR_W(svn_io_file_write_full(outfile, bigstr->data,
2079 bigstr->len, NULL, pool),
2080 apr_psprintf(pool,
2081 _("Error writing to '%s'"),
2082 svn_path_local_style
2083 (svn_wc_adm_access_path(adm_access), pool)));
2085 err = svn_wc__close_adm_file(outfile,
2086 svn_wc_adm_access_path(adm_access),
2087 SVN_WC__ADM_ENTRIES, 1, pool);
2089 svn_wc__adm_access_set_entries(adm_access, TRUE, entries);
2090 svn_wc__adm_access_set_entries(adm_access, FALSE, NULL);
2092 return err;
2096 /* Update an entry NAME in ENTRIES, according to the combination of
2097 entry data found in ENTRY and masked by MODIFY_FLAGS. If the entry
2098 already exists, the requested changes will be folded (merged) into
2099 the entry's existing state. If the entry doesn't exist, the entry
2100 will be created with exactly those properties described by the set
2101 of changes. Also cleanups meaningless fields combinations.
2103 POOL may be used to allocate memory referenced by ENTRIES.
2105 static void
2106 fold_entry(apr_hash_t *entries,
2107 const char *name,
2108 apr_uint64_t modify_flags,
2109 svn_wc_entry_t *entry,
2110 apr_pool_t *pool)
2112 svn_wc_entry_t *cur_entry
2113 = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
2115 assert(name != NULL);
2117 if (! cur_entry)
2118 cur_entry = alloc_entry(pool);
2120 /* Name (just a safeguard here, really) */
2121 if (! cur_entry->name)
2122 cur_entry->name = apr_pstrdup(pool, name);
2124 /* Revision */
2125 if (modify_flags & SVN_WC__ENTRY_MODIFY_REVISION)
2126 cur_entry->revision = entry->revision;
2128 /* Ancestral URL in repository */
2129 if (modify_flags & SVN_WC__ENTRY_MODIFY_URL)
2130 cur_entry->url = entry->url ? apr_pstrdup(pool, entry->url) : NULL;
2132 /* Repository root */
2133 if (modify_flags & SVN_WC__ENTRY_MODIFY_REPOS)
2134 cur_entry->repos = entry->repos ? apr_pstrdup(pool, entry->repos) : NULL;
2136 /* Kind */
2137 if (modify_flags & SVN_WC__ENTRY_MODIFY_KIND)
2138 cur_entry->kind = entry->kind;
2140 /* Schedule */
2141 if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE)
2142 cur_entry->schedule = entry->schedule;
2144 /* Checksum */
2145 if (modify_flags & SVN_WC__ENTRY_MODIFY_CHECKSUM)
2146 cur_entry->checksum = entry->checksum
2147 ? apr_pstrdup(pool, entry->checksum)
2148 : NULL;
2150 /* Copy-related stuff */
2151 if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED)
2152 cur_entry->copied = entry->copied;
2154 if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_URL)
2155 cur_entry->copyfrom_url = entry->copyfrom_url
2156 ? apr_pstrdup(pool, entry->copyfrom_url)
2157 : NULL;
2159 if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_REV)
2160 cur_entry->copyfrom_rev = entry->copyfrom_rev;
2162 /* Deleted state */
2163 if (modify_flags & SVN_WC__ENTRY_MODIFY_DELETED)
2164 cur_entry->deleted = entry->deleted;
2166 /* Absent state */
2167 if (modify_flags & SVN_WC__ENTRY_MODIFY_ABSENT)
2168 cur_entry->absent = entry->absent;
2170 /* Incomplete state */
2171 if (modify_flags & SVN_WC__ENTRY_MODIFY_INCOMPLETE)
2172 cur_entry->incomplete = entry->incomplete;
2174 /* Text/prop modification times */
2175 if (modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME)
2176 cur_entry->text_time = entry->text_time;
2178 if (modify_flags & SVN_WC__ENTRY_MODIFY_PROP_TIME)
2179 cur_entry->prop_time = entry->prop_time;
2181 /* Conflict stuff */
2182 if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_OLD)
2183 cur_entry->conflict_old = entry->conflict_old
2184 ? apr_pstrdup(pool, entry->conflict_old)
2185 : NULL;
2187 if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_NEW)
2188 cur_entry->conflict_new = entry->conflict_new
2189 ? apr_pstrdup(pool, entry->conflict_new)
2190 : NULL;
2192 if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_WRK)
2193 cur_entry->conflict_wrk = entry->conflict_wrk
2194 ? apr_pstrdup(pool, entry->conflict_wrk)
2195 : NULL;
2197 if (modify_flags & SVN_WC__ENTRY_MODIFY_PREJFILE)
2198 cur_entry->prejfile = entry->prejfile
2199 ? apr_pstrdup(pool, entry->prejfile)
2200 : NULL;
2202 /* Last-commit stuff */
2203 if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_REV)
2204 cur_entry->cmt_rev = entry->cmt_rev;
2206 if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_DATE)
2207 cur_entry->cmt_date = entry->cmt_date;
2209 if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_AUTHOR)
2210 cur_entry->cmt_author = entry->cmt_author
2211 ? apr_pstrdup(pool, entry->cmt_author)
2212 : NULL;
2214 if (modify_flags & SVN_WC__ENTRY_MODIFY_UUID)
2215 cur_entry->uuid = entry->uuid
2216 ? apr_pstrdup(pool, entry->uuid)
2217 : NULL;
2219 /* Lock token */
2220 if (modify_flags & SVN_WC__ENTRY_MODIFY_LOCK_TOKEN)
2221 cur_entry->lock_token = (entry->lock_token
2222 ? apr_pstrdup(pool, entry->lock_token)
2223 : NULL);
2225 /* Lock owner */
2226 if (modify_flags & SVN_WC__ENTRY_MODIFY_LOCK_OWNER)
2227 cur_entry->lock_owner = (entry->lock_owner
2228 ? apr_pstrdup(pool, entry->lock_owner)
2229 : NULL);
2231 /* Lock comment */
2232 if (modify_flags & SVN_WC__ENTRY_MODIFY_LOCK_COMMENT)
2233 cur_entry->lock_comment = (entry->lock_comment
2234 ? apr_pstrdup(pool, entry->lock_comment)
2235 : NULL);
2237 /* Lock creation date */
2238 if (modify_flags & SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE)
2239 cur_entry->lock_creation_date = entry->lock_creation_date;
2241 /* Changelist */
2242 if (modify_flags & SVN_WC__ENTRY_MODIFY_CHANGELIST)
2243 cur_entry->changelist = (entry->changelist
2244 ? apr_pstrdup(pool, entry->changelist)
2245 : NULL);
2247 /* has-props flag */
2248 if (modify_flags & SVN_WC__ENTRY_MODIFY_HAS_PROPS)
2249 cur_entry->has_props = entry->has_props;
2251 /* prop-mods flag */
2252 if (modify_flags & SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS)
2253 cur_entry->has_prop_mods = entry->has_prop_mods;
2255 /* Cachable props. */
2256 if (modify_flags & SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS)
2257 cur_entry->cachable_props = (entry->cachable_props
2258 ? apr_pstrdup(pool, entry->cachable_props)
2259 : NULL);
2261 /* Property existence */
2262 if (modify_flags & SVN_WC__ENTRY_MODIFY_PRESENT_PROPS)
2263 cur_entry->present_props = (entry->present_props
2264 ? apr_pstrdup(pool, entry->present_props)
2265 : NULL);
2267 if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL)
2268 cur_entry->keep_local = entry->keep_local;
2270 /* Note that we don't bother to fold entry->depth, because it is
2271 only meaningful on the this-dir entry anyway. */
2273 /* Absorb defaults from the parent dir, if any, unless this is a
2274 subdir entry. */
2275 if (cur_entry->kind != svn_node_dir)
2277 svn_wc_entry_t *default_entry
2278 = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
2279 if (default_entry)
2280 take_from_entry(default_entry, cur_entry, pool);
2283 /* Cleanup meaningless fields */
2285 /* ### svn_wc_schedule_delete is the minimal value. We need it because it's
2286 impossible to NULLify copyfrom_url with log-instructions.
2288 Note that I tried to find the smallest collection not to clear these
2289 fields for, but this condition still fails the test suite:
2291 !(entry->schedule == svn_wc_schedule_add
2292 || entry->schedule == svn_wc_schedule_replace
2293 || (entry->schedule == svn_wc_schedule_normal && entry->copied)))
2296 if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE
2297 && entry->schedule == svn_wc_schedule_delete)
2299 cur_entry->copied = FALSE;
2300 cur_entry->copyfrom_rev = SVN_INVALID_REVNUM;
2301 cur_entry->copyfrom_url = NULL;
2304 if (modify_flags & SVN_WC__ENTRY_MODIFY_WORKING_SIZE)
2305 cur_entry->working_size = entry->working_size;
2307 /* keep_local makes sense only when we are going to delete directory. */
2308 if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE
2309 && entry->schedule != svn_wc_schedule_delete)
2311 cur_entry->keep_local = FALSE;
2314 /* Make sure the entry exists in the entries hash. Possibly it
2315 already did, in which case this could have been skipped, but what
2316 the heck. */
2317 apr_hash_set(entries, cur_entry->name, APR_HASH_KEY_STRING, cur_entry);
2321 void
2322 svn_wc__entry_remove(apr_hash_t *entries, const char *name)
2324 apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL);
2328 /* Our general purpose intelligence module for handling scheduling
2329 changes to a single entry.
2331 Given an entryname NAME in ENTRIES, examine the caller's requested
2332 change in *SCHEDULE and the current state of the entry. Possibly
2333 modify *SCHEDULE and *MODIFY_FLAGS so that when merged, it will
2334 reflect the caller's original intent.
2336 POOL is used for local allocations only, calling this function does not
2337 use POOL to allocate any memory referenced by ENTRIES.
2339 static svn_error_t *
2340 fold_scheduling(apr_hash_t *entries,
2341 const char *name,
2342 apr_uint64_t *modify_flags,
2343 svn_wc_schedule_t *schedule,
2344 apr_pool_t *pool)
2346 svn_wc_entry_t *entry, *this_dir_entry;
2348 /* If we're not supposed to be bothering with this anyway...return. */
2349 if (! (*modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE))
2350 return SVN_NO_ERROR;
2352 /* Get the current entry */
2353 entry = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
2355 /* If we're not merging in changes, only the _add, _delete, _replace
2356 and _normal schedules are allowed. */
2357 if (*modify_flags & SVN_WC__ENTRY_MODIFY_FORCE)
2359 switch (*schedule)
2361 case svn_wc_schedule_add:
2362 case svn_wc_schedule_delete:
2363 case svn_wc_schedule_replace:
2364 case svn_wc_schedule_normal:
2365 /* Since we aren't merging in a change, not only are these
2366 schedules legal, but they are final. */
2367 return SVN_NO_ERROR;
2369 default:
2370 return svn_error_create(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, NULL);
2374 /* The only operation valid on an item not already in revision
2375 control is addition. */
2376 if (! entry)
2378 if (*schedule == svn_wc_schedule_add)
2379 return SVN_NO_ERROR;
2380 else
2381 return
2382 svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2383 _("'%s' is not under version control"),
2384 name);
2387 /* Get the default entry */
2388 this_dir_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
2389 APR_HASH_KEY_STRING);
2391 /* At this point, we know the following things:
2393 1. There is already an entry for this item in the entries file
2394 whose existence is either _normal or _added (or about to
2395 become such), which for our purposes mean the same thing.
2397 2. We have been asked to merge in a state change, not to
2398 explicitly set the state. */
2400 /* Here are some cases that are parent-directory sensitive.
2401 Basically, we make sure that we are not allowing versioned
2402 resources to just sorta dangle below directories marked for
2403 deletion. */
2404 if ((entry != this_dir_entry)
2405 && (this_dir_entry->schedule == svn_wc_schedule_delete))
2407 if (*schedule == svn_wc_schedule_add)
2408 return
2409 svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2410 _("Can't add '%s' to deleted directory; "
2411 "try undeleting its parent directory first"),
2412 name);
2413 if (*schedule == svn_wc_schedule_replace)
2414 return
2415 svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2416 _("Can't replace '%s' in deleted directory; "
2417 "try undeleting its parent directory first"),
2418 name);
2421 if (entry->absent && (*schedule == svn_wc_schedule_add))
2423 return svn_error_createf
2424 (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2425 _("'%s' is marked as absent, so it cannot be scheduled for addition"),
2426 name);
2429 switch (entry->schedule)
2431 case svn_wc_schedule_normal:
2432 switch (*schedule)
2434 case svn_wc_schedule_normal:
2435 /* Normal is a trivial no-op case. Reset the
2436 schedule modification bit and move along. */
2437 *modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
2438 return SVN_NO_ERROR;
2441 case svn_wc_schedule_delete:
2442 case svn_wc_schedule_replace:
2443 /* These are all good. */
2444 return SVN_NO_ERROR;
2447 case svn_wc_schedule_add:
2448 /* You can't add something that's already been added to
2449 revision control... unless it's got a 'deleted' state */
2450 if (! entry->deleted)
2451 return
2452 svn_error_createf
2453 (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2454 _("Entry '%s' is already under version control"), name);
2456 break;
2458 case svn_wc_schedule_add:
2459 switch (*schedule)
2461 case svn_wc_schedule_normal:
2462 case svn_wc_schedule_add:
2463 case svn_wc_schedule_replace:
2464 /* These are all no-op cases. Normal is obvious, as is add.
2465 Replace on an entry marked for addition breaks down to
2466 (add + (delete + add)), which resolves to just (add), and
2467 since this entry is already marked with (add), this too
2468 is a no-op. */
2469 *modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
2470 return SVN_NO_ERROR;
2473 case svn_wc_schedule_delete:
2474 /* Not-yet-versioned item being deleted. If the original
2475 entry was not marked as "deleted", then remove the entry.
2476 Else, return the entry to a 'normal' state, preserving
2477 the "deleted" flag. Check that we are not trying to
2478 remove the SVN_WC_ENTRY_THIS_DIR entry as that would
2479 leave the entries file in an invalid state. */
2480 assert(entry != this_dir_entry);
2481 if (! entry->deleted)
2482 apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL);
2483 else
2484 *schedule = svn_wc_schedule_normal;
2485 return SVN_NO_ERROR;
2487 break;
2489 case svn_wc_schedule_delete:
2490 switch (*schedule)
2492 case svn_wc_schedule_normal:
2493 /* Reverting a delete results in normal */
2494 return SVN_NO_ERROR;
2496 case svn_wc_schedule_delete:
2497 /* These are no-op cases. */
2498 *modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
2499 return SVN_NO_ERROR;
2502 case svn_wc_schedule_add:
2503 /* Re-adding an entry marked for deletion? This is really a
2504 replace operation. */
2505 *schedule = svn_wc_schedule_replace;
2506 return SVN_NO_ERROR;
2509 case svn_wc_schedule_replace:
2510 /* Replacing an item marked for deletion breaks down to
2511 (delete + (delete + add)), which might deserve a warning,
2512 but whatever. */
2513 return SVN_NO_ERROR;
2516 break;
2518 case svn_wc_schedule_replace:
2519 switch (*schedule)
2521 case svn_wc_schedule_normal:
2522 /* Reverting replacements results normal. */
2523 return SVN_NO_ERROR;
2525 case svn_wc_schedule_add:
2526 /* Adding a to-be-replaced entry breaks down to ((delete +
2527 add) + add) which might deserve a warning, but we'll just
2528 no-op it. */
2529 case svn_wc_schedule_replace:
2530 /* Replacing a to-be-replaced entry breaks down to ((delete
2531 + add) + (delete + add)), which is insane! Make up your
2532 friggin' mind, dude! :-) Well, we'll no-op this one,
2533 too. */
2534 *modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
2535 return SVN_NO_ERROR;
2538 case svn_wc_schedule_delete:
2539 /* Deleting a to-be-replaced entry breaks down to ((delete +
2540 add) + delete) which resolves to a flat deletion. */
2541 *schedule = svn_wc_schedule_delete;
2542 return SVN_NO_ERROR;
2545 break;
2547 default:
2548 return
2549 svn_error_createf
2550 (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
2551 _("Entry '%s' has illegal schedule"), name);
2553 return SVN_NO_ERROR;
2558 svn_error_t *
2559 svn_wc__entry_modify(svn_wc_adm_access_t *adm_access,
2560 const char *name,
2561 svn_wc_entry_t *entry,
2562 apr_uint64_t modify_flags,
2563 svn_boolean_t do_sync,
2564 apr_pool_t *pool)
2566 apr_hash_t *entries, *entries_nohidden;
2567 svn_boolean_t entry_was_deleted_p = FALSE;
2569 /* ENTRY is rather necessary! */
2570 assert(entry);
2572 /* Load ADM_ACCESS's whole entries file. */
2573 SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
2574 SVN_ERR(svn_wc_entries_read(&entries_nohidden, adm_access, FALSE, pool));
2576 /* Ensure that NAME is valid. */
2577 if (name == NULL)
2578 name = SVN_WC_ENTRY_THIS_DIR;
2580 if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE)
2582 svn_wc_entry_t *entry_before, *entry_after;
2583 apr_uint64_t orig_modify_flags = modify_flags;
2584 svn_wc_schedule_t orig_schedule = entry->schedule;
2586 /* Keep a copy of the unmodified entry on hand. */
2587 entry_before = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
2589 /* If scheduling changes were made, we have a special routine to
2590 manage those modifications. */
2591 SVN_ERR(fold_scheduling(entries, name, &modify_flags,
2592 &entry->schedule, pool));
2594 if (entries != entries_nohidden)
2596 SVN_ERR(fold_scheduling(entries_nohidden, name, &orig_modify_flags,
2597 &orig_schedule, pool));
2599 /* Make certain that both folding operations had the same
2600 result. */
2601 assert(orig_modify_flags == modify_flags);
2602 assert(orig_schedule == entry->schedule);
2605 /* Special case: fold_state_changes() may have actually REMOVED
2606 the entry in question! If so, don't try to fold_entry, as
2607 this will just recreate the entry again. */
2608 entry_after = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
2610 /* Note if this entry was deleted above so we don't accidentally
2611 re-add it in the following steps. */
2612 if (entry_before && (! entry_after))
2613 entry_was_deleted_p = TRUE;
2616 /* If the entry wasn't just removed from the entries hash, fold the
2617 changes into the entry. */
2618 if (! entry_was_deleted_p)
2620 fold_entry(entries, name, modify_flags, entry,
2621 svn_wc_adm_access_pool(adm_access));
2622 if (entries != entries_nohidden)
2623 fold_entry(entries_nohidden, name, modify_flags, entry,
2624 svn_wc_adm_access_pool(adm_access));
2627 /* Sync changes to disk. */
2628 if (do_sync)
2629 SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
2631 return SVN_NO_ERROR;
2635 svn_wc_entry_t *
2636 svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
2638 svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));
2640 /* Perform a trivial copy ... */
2641 *dupentry = *entry;
2643 /* ...and then re-copy stuff that needs to be duped into our pool. */
2644 if (entry->name)
2645 dupentry->name = apr_pstrdup(pool, entry->name);
2646 if (entry->url)
2647 dupentry->url = apr_pstrdup(pool, entry->url);
2648 if (entry->repos)
2649 dupentry->repos = apr_pstrdup(pool, entry->repos);
2650 if (entry->uuid)
2651 dupentry->uuid = apr_pstrdup(pool, entry->uuid);
2652 if (entry->copyfrom_url)
2653 dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
2654 if (entry->conflict_old)
2655 dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
2656 if (entry->conflict_new)
2657 dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
2658 if (entry->conflict_wrk)
2659 dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
2660 if (entry->prejfile)
2661 dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
2662 if (entry->checksum)
2663 dupentry->checksum = apr_pstrdup(pool, entry->checksum);
2664 if (entry->cmt_author)
2665 dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
2666 if (entry->lock_token)
2667 dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
2668 if (entry->lock_owner)
2669 dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
2670 if (entry->lock_comment)
2671 dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
2672 if (entry->changelist)
2673 dupentry->changelist = apr_pstrdup(pool, entry->changelist);
2674 if (entry->cachable_props)
2675 dupentry->cachable_props = apr_pstrdup(pool, entry->cachable_props);
2676 if (entry->present_props)
2677 dupentry->present_props = apr_pstrdup(pool, entry->present_props);
2678 return dupentry;
2682 svn_error_t *
2683 svn_wc__tweak_entry(apr_hash_t *entries,
2684 const char *name,
2685 const char *new_url,
2686 const char *repos,
2687 svn_revnum_t new_rev,
2688 svn_boolean_t allow_removal,
2689 svn_boolean_t *write_required,
2690 apr_pool_t *pool)
2692 svn_wc_entry_t *entry;
2694 entry = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
2695 if (! entry)
2696 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
2697 _("No such entry: '%s'"), name);
2699 if (new_url != NULL
2700 && (! entry->url || strcmp(new_url, entry->url)))
2702 *write_required = TRUE;
2703 entry->url = apr_pstrdup(pool, new_url);
2706 if (repos != NULL
2707 && (! entry->repos || strcmp(repos, entry->repos))
2708 && entry->url
2709 && svn_path_is_ancestor(repos, entry->url))
2711 svn_boolean_t set_repos = TRUE;
2713 /* Setting the repository root on THIS_DIR will make files in this
2714 directory inherit that property. So to not make the WC corrupt,
2715 we have to make sure that the repos root is valid for such entries as
2716 well. Note that this shouldn't happen in normal circumstances. */
2717 if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
2719 apr_hash_index_t *hi;
2720 for (hi = apr_hash_first(pool, entries); hi;
2721 hi = apr_hash_next(hi))
2723 void *value;
2724 const svn_wc_entry_t *child_entry;
2726 apr_hash_this(hi, NULL, NULL, &value);
2727 child_entry = value;
2729 if (! child_entry->repos && child_entry->url
2730 && ! svn_path_is_ancestor(repos, child_entry->url))
2732 set_repos = FALSE;
2733 break;
2738 if (set_repos)
2740 *write_required = TRUE;
2741 entry->repos = apr_pstrdup(pool, repos);
2745 if ((SVN_IS_VALID_REVNUM(new_rev))
2746 && (entry->schedule != svn_wc_schedule_add)
2747 && (entry->schedule != svn_wc_schedule_replace)
2748 && (entry->copied != TRUE)
2749 && (entry->revision != new_rev))
2751 *write_required = TRUE;
2752 entry->revision = new_rev;
2755 /* As long as this function is only called as a helper to
2756 svn_wc__do_update_cleanup, then it's okay to remove any entry
2757 under certain circumstances:
2759 If the entry is still marked 'deleted', then the server did not
2760 re-add it. So it's really gone in this revision, thus we remove
2761 the entry.
2763 If the entry is still marked 'absent' and yet is not the same
2764 revision as new_rev, then the server did not re-add it, nor
2765 re-absent it, so we can remove the entry.
2767 ### This function cannot always determine whether removal is
2768 ### appropriate, hence the ALLOW_REMOVAL flag. It's all a bit of a
2769 ### mess. */
2770 if (allow_removal
2771 && (entry->deleted || (entry->absent && entry->revision != new_rev)))
2773 *write_required = TRUE;
2774 apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL);
2777 return SVN_NO_ERROR;
2783 /*** Initialization of the entries file. ***/
2785 svn_error_t *
2786 svn_wc__entries_init(const char *path,
2787 const char *uuid,
2788 const char *url,
2789 const char *repos,
2790 svn_revnum_t initial_rev,
2791 svn_depth_t depth,
2792 apr_pool_t *pool)
2794 apr_file_t *f = NULL;
2795 svn_stringbuf_t *accum = svn_stringbuf_createf(pool, "%d\n",
2796 SVN_WC__VERSION);
2797 svn_wc_entry_t *entry = alloc_entry(pool);
2799 /* Sanity checks. */
2800 assert(! repos || svn_path_is_ancestor(repos, url));
2801 assert(depth == svn_depth_empty
2802 || depth == svn_depth_files
2803 || depth == svn_depth_immediates
2804 || depth == svn_depth_infinity);
2806 /* Create the entries file, which must not exist prior to this. */
2807 SVN_ERR(svn_wc__open_adm_file(&f, path, SVN_WC__ADM_ENTRIES,
2808 (APR_WRITE | APR_CREATE | APR_EXCL), pool));
2810 /* Add an entry for the dir itself. The directory has no name. It
2811 might have a UUID, but otherwise only the revision and default
2812 ancestry are present as XML attributes, and possibly an
2813 'incomplete' flag if the revnum is > 0. */
2815 entry->kind = svn_node_dir;
2816 entry->url = url;
2817 entry->revision = initial_rev;
2818 entry->uuid = uuid;
2819 entry->repos = repos;
2820 entry->depth = depth;
2821 if (initial_rev > 0)
2822 entry->incomplete = TRUE;
2823 /* Add cachable-props here so that it can be inherited by other entries.
2825 entry->cachable_props = SVN_WC__CACHABLE_PROPS;
2827 write_entry(accum, entry, SVN_WC_ENTRY_THIS_DIR, entry, pool);
2829 SVN_ERR_W(svn_io_file_write_full(f, accum->data, accum->len, NULL, pool),
2830 apr_psprintf(pool,
2831 _("Error writing entries file for '%s'"),
2832 svn_path_local_style(path, pool)));
2834 /* Now we have a `entries' file with exactly one entry, an entry
2835 for this dir. Close the file and sync it up. */
2836 SVN_ERR(svn_wc__close_adm_file(f, path, SVN_WC__ADM_ENTRIES, 1, pool));
2838 return SVN_NO_ERROR;
2842 /*--------------------------------------------------------------- */
2844 /*** Generic Entry Walker */
2847 /* A recursive entry-walker, helper for svn_wc_walk_entries2 */
2848 static svn_error_t *
2849 walker_helper(const char *dirpath,
2850 svn_wc_adm_access_t *adm_access,
2851 const svn_wc_entry_callbacks2_t *walk_callbacks,
2852 void *walk_baton,
2853 svn_depth_t depth,
2854 svn_boolean_t show_hidden,
2855 svn_cancel_func_t cancel_func,
2856 void *cancel_baton,
2857 apr_pool_t *pool)
2859 apr_pool_t *subpool = svn_pool_create(pool);
2860 apr_hash_t *entries;
2861 apr_hash_index_t *hi;
2862 svn_wc_entry_t *dot_entry;
2864 SVN_ERR(walk_callbacks->handle_error
2865 (dirpath, svn_wc_entries_read(&entries, adm_access, show_hidden,
2866 pool), walk_baton, pool));
2868 /* As promised, always return the '.' entry first. */
2869 dot_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
2870 APR_HASH_KEY_STRING);
2871 if (! dot_entry)
2872 return walk_callbacks->handle_error
2873 (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
2874 _("Directory '%s' has no THIS_DIR entry"),
2875 svn_path_local_style(dirpath, pool)),
2876 walk_baton, pool);
2878 SVN_ERR(walk_callbacks->handle_error
2879 (dirpath,
2880 walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, pool),
2881 walk_baton, pool));
2883 if (depth == svn_depth_empty)
2884 return SVN_NO_ERROR;
2886 /* Loop over each of the other entries. */
2887 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2889 const void *key;
2890 void *val;
2891 const svn_wc_entry_t *current_entry;
2892 const char *entrypath;
2894 svn_pool_clear(subpool);
2896 /* See if someone wants to cancel this operation. */
2897 if (cancel_func)
2898 SVN_ERR(cancel_func(cancel_baton));
2900 apr_hash_this(hi, &key, NULL, &val);
2901 current_entry = val;
2903 if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
2904 continue;
2906 entrypath = svn_path_join(dirpath, key, subpool);
2908 if (current_entry->kind == svn_node_file
2909 || depth >= svn_depth_immediates)
2911 SVN_ERR(walk_callbacks->handle_error
2912 (entrypath,
2913 walk_callbacks->found_entry(entrypath, current_entry,
2914 walk_baton, subpool),
2915 walk_baton, pool));
2918 if (current_entry->kind == svn_node_dir
2919 && depth >= svn_depth_immediates)
2921 svn_wc_adm_access_t *entry_access;
2922 svn_depth_t depth_below_here = depth;
2924 if (depth == svn_depth_immediates)
2925 depth_below_here = svn_depth_empty;
2927 SVN_ERR(walk_callbacks->handle_error
2928 (entrypath,
2929 svn_wc_adm_retrieve(&entry_access, adm_access, entrypath,
2930 subpool),
2931 walk_baton, pool));
2933 if (entry_access)
2934 SVN_ERR(walker_helper(entrypath, entry_access,
2935 walk_callbacks, walk_baton,
2936 depth_below_here, show_hidden,
2937 cancel_func, cancel_baton,
2938 subpool));
2942 svn_pool_destroy(subpool);
2943 return SVN_NO_ERROR;
2947 svn_error_t *
2948 svn_wc_walk_entries(const char *path,
2949 svn_wc_adm_access_t *adm_access,
2950 const svn_wc_entry_callbacks_t *walk_callbacks,
2951 void *walk_baton,
2952 svn_boolean_t show_hidden,
2953 apr_pool_t *pool)
2955 return svn_wc_walk_entries2(path, adm_access, walk_callbacks,
2956 walk_baton, show_hidden, NULL, NULL,
2957 pool);
2960 svn_error_t *
2961 svn_wc__walker_default_error_handler(const char *path,
2962 svn_error_t *err,
2963 void *walk_baton,
2964 apr_pool_t *pool)
2966 return err;
2969 svn_error_t *
2970 svn_wc_walk_entries2(const char *path,
2971 svn_wc_adm_access_t *adm_access,
2972 const svn_wc_entry_callbacks_t *walk_callbacks,
2973 void *walk_baton,
2974 svn_boolean_t show_hidden,
2975 svn_cancel_func_t cancel_func,
2976 void *cancel_baton,
2977 apr_pool_t *pool)
2979 svn_wc_entry_callbacks2_t walk_cb2 = { walk_callbacks->found_entry,
2980 svn_wc__walker_default_error_handler };
2981 return svn_wc_walk_entries3(path, adm_access,
2982 &walk_cb2, walk_baton, svn_depth_infinity,
2983 show_hidden, cancel_func, cancel_baton, pool);
2986 /* The public API. */
2987 svn_error_t *
2988 svn_wc_walk_entries3(const char *path,
2989 svn_wc_adm_access_t *adm_access,
2990 const svn_wc_entry_callbacks2_t *walk_callbacks,
2991 void *walk_baton,
2992 svn_depth_t depth,
2993 svn_boolean_t show_hidden,
2994 svn_cancel_func_t cancel_func,
2995 void *cancel_baton,
2996 apr_pool_t *pool)
2998 const svn_wc_entry_t *entry;
3000 SVN_ERR(svn_wc_entry(&entry, path, adm_access, show_hidden, pool));
3002 if (! entry)
3003 return walk_callbacks->handle_error
3004 (path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
3005 _("'%s' is not under version control"),
3006 svn_path_local_style(path, pool)),
3007 walk_baton, pool);
3009 if (entry->kind == svn_node_file)
3010 return walk_callbacks->handle_error
3011 (path, walk_callbacks->found_entry(path, entry, walk_baton, pool),
3012 walk_baton, pool);
3014 else if (entry->kind == svn_node_dir)
3015 return walker_helper(path, adm_access, walk_callbacks, walk_baton,
3016 depth, show_hidden, cancel_func, cancel_baton, pool);
3018 else
3019 return walk_callbacks->handle_error
3020 (path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
3021 _("'%s' has an unrecognized node kind"),
3022 svn_path_local_style(path, pool)),
3023 walk_baton, pool);
3027 svn_error_t *
3028 svn_wc_mark_missing_deleted(const char *path,
3029 svn_wc_adm_access_t *parent,
3030 apr_pool_t *pool)
3032 svn_node_kind_t pkind;
3034 SVN_ERR(svn_io_check_path(path, &pkind, pool));
3036 if (pkind == svn_node_none)
3038 const char *parent_path, *bname;
3039 svn_wc_adm_access_t *adm_access;
3040 svn_wc_entry_t newent;
3042 newent.deleted = TRUE;
3043 newent.schedule = svn_wc_schedule_normal;
3045 svn_path_split(path, &parent_path, &bname, pool);
3047 SVN_ERR(svn_wc_adm_retrieve(&adm_access, parent, parent_path, pool));
3048 SVN_ERR(svn_wc__entry_modify(adm_access, bname, &newent,
3049 (SVN_WC__ENTRY_MODIFY_DELETED
3050 | SVN_WC__ENTRY_MODIFY_SCHEDULE
3051 | SVN_WC__ENTRY_MODIFY_FORCE),
3052 TRUE, /* sync right away */ pool));
3054 return SVN_NO_ERROR;
3056 else
3057 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
3058 _("Unexpectedly found '%s': "
3059 "path is marked 'missing'"),
3060 svn_path_local_style(path, pool));