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