status.c revision 299742
1/*
2 * status.c: construct a status structure from an entry structure
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 <assert.h>
27#include <string.h>
28
29#include <apr_pools.h>
30#include <apr_file_io.h>
31#include <apr_hash.h>
32
33#include "svn_pools.h"
34#include "svn_types.h"
35#include "svn_delta.h"
36#include "svn_string.h"
37#include "svn_error.h"
38#include "svn_dirent_uri.h"
39#include "svn_io.h"
40#include "svn_config.h"
41#include "svn_time.h"
42#include "svn_hash.h"
43#include "svn_sorts.h"
44
45#include "svn_private_config.h"
46
47#include "wc.h"
48#include "props.h"
49
50#include "private/svn_sorts_private.h"
51#include "private/svn_wc_private.h"
52#include "private/svn_fspath.h"
53#include "private/svn_editor.h"
54
55
56/* The file internal variant of svn_wc_status3_t, with slightly more
57   data.
58
59   Instead of directly creating svn_wc_status3_t instances, we really
60   create instances of this struct with slightly more data for processing
61   by the status walker and status editor.
62
63   svn_wc_status3_dup() allocates space for this struct, but doesn't
64   copy the actual data. The remaining fields are copied by hash_stash(),
65   which is where the status editor stashes information for producing
66   later. */
67typedef struct svn_wc__internal_status_t
68{
69  svn_wc_status3_t s; /* First member; same pointer*/
70
71  svn_boolean_t has_descendants;
72
73  /* Make sure to update hash_stash() when adding values here */
74} svn_wc__internal_status_t;
75
76
77/*** Baton used for walking the local status */
78struct walk_status_baton
79{
80  /* The DB handle for managing the working copy state. */
81  svn_wc__db_t *db;
82
83  /*** External handling ***/
84  /* Target of the status */
85  const char *target_abspath;
86
87  /* Should we ignore text modifications? */
88  svn_boolean_t ignore_text_mods;
89
90  /* Scan the working copy for local modifications and missing nodes. */
91  svn_boolean_t check_working_copy;
92
93  /* Externals info harvested during the status run. */
94  apr_hash_t *externals;
95
96  /*** Repository lock handling ***/
97  /* The repository root URL, if set. */
98  const char *repos_root;
99
100  /* Repository locks, if set. */
101  apr_hash_t *repos_locks;
102};
103
104/*** Editor batons ***/
105
106struct edit_baton
107{
108  /* For status, the "destination" of the edit.  */
109  const char *anchor_abspath;
110  const char *target_abspath;
111  const char *target_basename;
112
113  /* The DB handle for managing the working copy state.  */
114  svn_wc__db_t *db;
115
116  /* The overall depth of this edit (a dir baton may override this).
117   *
118   * If this is svn_depth_unknown, the depths found in the working
119   * copy will govern the edit; or if the edit depth indicates a
120   * descent deeper than the found depths are capable of, the found
121   * depths also govern, of course (there's no point descending into
122   * something that's not there).
123   */
124  svn_depth_t default_depth;
125
126  /* Do we want all statuses (instead of just the interesting ones) ? */
127  svn_boolean_t get_all;
128
129  /* Ignore the svn:ignores. */
130  svn_boolean_t no_ignore;
131
132  /* The comparison revision in the repository.  This is a reference
133     because this editor returns this rev to the driver directly, as
134     well as in each statushash entry. */
135  svn_revnum_t *target_revision;
136
137  /* Status function/baton. */
138  svn_wc_status_func4_t status_func;
139  void *status_baton;
140
141  /* Cancellation function/baton. */
142  svn_cancel_func_t cancel_func;
143  void *cancel_baton;
144
145  /* The configured set of default ignores. */
146  const apr_array_header_t *ignores;
147
148  /* Status item for the path represented by the anchor of the edit. */
149  svn_wc__internal_status_t *anchor_status;
150
151  /* Was open_root() called for this edit drive? */
152  svn_boolean_t root_opened;
153
154  /* The local status baton */
155  struct walk_status_baton wb;
156};
157
158
159struct dir_baton
160{
161  /* The path to this directory. */
162  const char *local_abspath;
163
164  /* Basename of this directory. */
165  const char *name;
166
167  /* The global edit baton. */
168  struct edit_baton *edit_baton;
169
170  /* Baton for this directory's parent, or NULL if this is the root
171     directory. */
172  struct dir_baton *parent_baton;
173
174  /* The ambient requested depth below this point in the edit.  This
175     can differ from the parent baton's depth (with the edit baton
176     considered the ultimate parent baton).  For example, if the
177     parent baton has svn_depth_immediates, then here we should have
178     svn_depth_empty, because there would be no further recursion, not
179     even to file children. */
180  svn_depth_t depth;
181
182  /* Is this directory filtered out due to depth?  (Note that if this
183     is TRUE, the depth field is undefined.) */
184  svn_boolean_t excluded;
185
186  /* 'svn status' shouldn't print status lines for things that are
187     added;  we're only interest in asking if objects that the user
188     *already* has are up-to-date or not.  Thus if this flag is set,
189     the next two will be ignored.  :-)  */
190  svn_boolean_t added;
191
192  /* Gets set iff there's a change to this directory's properties, to
193     guide us when syncing adm files later. */
194  svn_boolean_t prop_changed;
195
196  /* This means (in terms of 'svn status') that some child was deleted
197     or added to the directory */
198  svn_boolean_t text_changed;
199
200  /* Working copy status structures for children of this directory.
201     This hash maps const char * abspaths  to svn_wc_status3_t *
202     status items. */
203  apr_hash_t *statii;
204
205  /* The pool in which this baton itself is allocated. */
206  apr_pool_t *pool;
207
208  /* The repository root relative path to this item in the repository. */
209  const char *repos_relpath;
210
211  /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
212  svn_node_kind_t ood_kind;
213  svn_revnum_t ood_changed_rev;
214  apr_time_t ood_changed_date;
215  const char *ood_changed_author;
216};
217
218
219struct file_baton
220{
221/* Absolute local path to this file */
222  const char *local_abspath;
223
224  /* The global edit baton. */
225  struct edit_baton *edit_baton;
226
227  /* Baton for this file's parent directory. */
228  struct dir_baton *dir_baton;
229
230  /* Pool specific to this file_baton. */
231  apr_pool_t *pool;
232
233  /* Basename of this file */
234  const char *name;
235
236  /* 'svn status' shouldn't print status lines for things that are
237     added;  we're only interest in asking if objects that the user
238     *already* has are up-to-date or not.  Thus if this flag is set,
239     the next two will be ignored.  :-)  */
240  svn_boolean_t added;
241
242  /* This gets set if the file underwent a text change, which guides
243     the code that syncs up the adm dir and working copy. */
244  svn_boolean_t text_changed;
245
246  /* This gets set if the file underwent a prop change, which guides
247     the code that syncs up the adm dir and working copy. */
248  svn_boolean_t prop_changed;
249
250  /* The repository root relative path to this item in the repository. */
251  const char *repos_relpath;
252
253  /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
254  svn_node_kind_t ood_kind;
255  svn_revnum_t ood_changed_rev;
256  apr_time_t ood_changed_date;
257
258  const char *ood_changed_author;
259};
260
261
262/** Code **/
263
264
265
266/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
267   information in INFO if available, falling back on
268   PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
269   finally falling back on querying DB. */
270static svn_error_t *
271get_repos_root_url_relpath(const char **repos_relpath,
272                           const char **repos_root_url,
273                           const char **repos_uuid,
274                           const struct svn_wc__db_info_t *info,
275                           const char *parent_repos_relpath,
276                           const char *parent_repos_root_url,
277                           const char *parent_repos_uuid,
278                           svn_wc__db_t *db,
279                           const char *local_abspath,
280                           apr_pool_t *result_pool,
281                           apr_pool_t *scratch_pool)
282{
283  if (info->repos_relpath && info->repos_root_url)
284    {
285      *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
286      *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
287      *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
288    }
289  else if (parent_repos_relpath && parent_repos_root_url)
290    {
291      *repos_relpath = svn_relpath_join(parent_repos_relpath,
292                                        svn_dirent_basename(local_abspath,
293                                                            NULL),
294                                        result_pool);
295      *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
296      *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
297    }
298  else
299    {
300      SVN_ERR(svn_wc__db_read_repos_info(NULL,
301                                         repos_relpath, repos_root_url,
302                                         repos_uuid,
303                                         db, local_abspath,
304                                         result_pool, scratch_pool));
305    }
306
307  return SVN_NO_ERROR;
308}
309
310static svn_error_t *
311internal_status(svn_wc__internal_status_t **status,
312                svn_wc__db_t *db,
313                const char *local_abspath,
314                svn_boolean_t check_working_copy,
315                apr_pool_t *result_pool,
316                apr_pool_t *scratch_pool);
317
318/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
319   RESULT_POOL and use SCRATCH_POOL for temporary allocations.
320
321   PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
322   and repository relative path of the parent of LOCAL_ABSPATH or NULL if
323   LOCAL_ABSPATH doesn't have a versioned parent directory.
324
325   DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
326   NULL if the node does not exist on disk.
327
328   If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
329   *STATUS will be set to NULL.  If GET_ALL is non-zero, then *STATUS will be
330   allocated and returned no matter what.  If IGNORE_TEXT_MODS is TRUE then
331   don't check for text mods, assume there are none and set and *STATUS
332   returned to reflect that assumption. If CHECK_WORKING_COPY is FALSE,
333   do not adjust the result for missing working copy files.
334
335   The status struct's repos_lock field will be set to REPOS_LOCK.
336*/
337static svn_error_t *
338assemble_status(svn_wc__internal_status_t **status,
339                svn_wc__db_t *db,
340                const char *local_abspath,
341                const char *parent_repos_root_url,
342                const char *parent_repos_relpath,
343                const char *parent_repos_uuid,
344                const struct svn_wc__db_info_t *info,
345                const svn_io_dirent2_t *dirent,
346                svn_boolean_t get_all,
347                svn_boolean_t ignore_text_mods,
348                svn_boolean_t check_working_copy,
349                const svn_lock_t *repos_lock,
350                apr_pool_t *result_pool,
351                apr_pool_t *scratch_pool)
352{
353  svn_wc__internal_status_t *inner_stat;
354  svn_wc_status3_t *stat;
355  svn_boolean_t switched_p = FALSE;
356  svn_boolean_t copied = FALSE;
357  svn_boolean_t conflicted;
358  const char *moved_from_abspath = NULL;
359
360  /* Defaults for two main variables. */
361  enum svn_wc_status_kind node_status = svn_wc_status_normal;
362  enum svn_wc_status_kind text_status = svn_wc_status_normal;
363  enum svn_wc_status_kind prop_status = svn_wc_status_none;
364
365
366  if (!info->repos_relpath || !parent_repos_relpath)
367    switched_p = FALSE;
368  else
369    {
370      /* A node is switched if it doesn't have the implied repos_relpath */
371      const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
372                                                   info->repos_relpath);
373      switched_p = !name || (strcmp(name,
374                                    svn_dirent_basename(local_abspath, NULL))
375                             != 0);
376    }
377
378  if (info->status == svn_wc__db_status_incomplete || info->incomplete)
379    {
380      /* Highest precedence.  */
381      node_status = svn_wc_status_incomplete;
382    }
383  else if (info->status == svn_wc__db_status_deleted)
384    {
385      node_status = svn_wc_status_deleted;
386
387      if (!info->have_base || info->have_more_work || info->copied)
388        copied = TRUE;
389      else if (!info->have_more_work && info->have_base)
390        copied = FALSE;
391      else
392        {
393          const char *work_del_abspath;
394
395          /* Find out details of our deletion.  */
396          SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
397                                           &work_del_abspath, NULL,
398                                           db, local_abspath,
399                                           scratch_pool, scratch_pool));
400          if (work_del_abspath)
401            copied = TRUE; /* Working deletion */
402        }
403    }
404  else if (check_working_copy)
405    {
406      /* Examine whether our target is missing or obstructed. To detect
407       * obstructions, we have to look at the on-disk status in DIRENT. */
408      svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
409                                        ? svn_node_dir
410                                        : svn_node_file;
411
412      if (!dirent || dirent->kind != expected_kind)
413        {
414          /* A present or added node should be on disk, so it is
415             reported missing or obstructed.  */
416          if (!dirent || dirent->kind == svn_node_none)
417            node_status = svn_wc_status_missing;
418          else
419            node_status = svn_wc_status_obstructed;
420        }
421    }
422
423  /* Does the node have props? */
424  if (info->status != svn_wc__db_status_deleted)
425    {
426      if (info->props_mod)
427        prop_status = svn_wc_status_modified;
428      else if (info->had_props)
429        prop_status = svn_wc_status_normal;
430    }
431
432  /* If NODE_STATUS is still normal, after the above checks, then
433     we should proceed to refine the status.
434
435     If it was changed, then the subdir is incomplete or missing/obstructed.
436   */
437  if (info->kind != svn_node_dir
438      && node_status == svn_wc_status_normal)
439    {
440      svn_boolean_t text_modified_p = FALSE;
441
442      /* Implement predecence rules: */
443
444      /* 1. Set the two main variables to "discovered" values first (M, C).
445            Together, these two stati are of lowest precedence, and C has
446            precedence over M. */
447
448      /* If the entry is a file, check for textual modifications */
449      if ((info->kind == svn_node_file
450          || info->kind == svn_node_symlink)
451#ifdef HAVE_SYMLINK
452             && (info->special == (dirent && dirent->special))
453#endif /* HAVE_SYMLINK */
454          )
455        {
456          /* If the on-disk dirent exactly matches the expected state
457             skip all operations in svn_wc__internal_text_modified_p()
458             to avoid an extra filestat for every file, which can be
459             expensive on network drives as a filestat usually can't
460             be cached there */
461          if (!info->has_checksum)
462            text_modified_p = TRUE; /* Local addition -> Modified */
463          else if (ignore_text_mods
464                  ||(dirent
465                     && info->recorded_size != SVN_INVALID_FILESIZE
466                     && info->recorded_time != 0
467                     && info->recorded_size == dirent->filesize
468                     && info->recorded_time == dirent->mtime))
469            text_modified_p = FALSE;
470          else
471            {
472              svn_error_t *err;
473              err = svn_wc__internal_file_modified_p(&text_modified_p,
474                                                     db, local_abspath,
475                                                     FALSE, scratch_pool);
476
477              if (err)
478                {
479                  if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
480                    return svn_error_trace(err);
481
482                  /* An access denied is very common on Windows when another
483                     application has the file open.  Previously we ignored
484                     this error in svn_wc__text_modified_internal_p, where it
485                     should have really errored. */
486                  svn_error_clear(err);
487                  text_modified_p = TRUE;
488                }
489            }
490        }
491#ifdef HAVE_SYMLINK
492      else if (info->special != (dirent && dirent->special))
493        node_status = svn_wc_status_obstructed;
494#endif /* HAVE_SYMLINK */
495
496      if (text_modified_p)
497        text_status = svn_wc_status_modified;
498    }
499
500  conflicted = info->conflicted;
501  if (conflicted)
502    {
503      svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
504
505      /* ### Check if the conflict was resolved by removing the marker files.
506         ### This should really be moved to the users of this API */
507      SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
508                                            &tree_conflicted,
509                                            db, local_abspath, scratch_pool));
510
511      if (!text_conflicted && !prop_conflicted && !tree_conflicted)
512        conflicted = FALSE;
513    }
514
515  if (node_status == svn_wc_status_normal)
516    {
517      /* 2. Possibly overwrite the text_status variable with "scheduled"
518            states from the entry (A, D, R).  As a group, these states are
519            of medium precedence.  They also override any C or M that may
520            be in the prop_status field at this point, although they do not
521            override a C text status.*/
522      if (info->status == svn_wc__db_status_added)
523        {
524          copied = info->copied;
525          if (!info->op_root)
526            { /* Keep status normal */ }
527          else if (!info->have_base && !info->have_more_work)
528            {
529              /* Simple addition or copy, no replacement */
530              node_status = svn_wc_status_added;
531            }
532          else
533            {
534              svn_wc__db_status_t below_working;
535              svn_boolean_t have_base, have_work;
536
537              SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
538                                                    &below_working,
539                                                    db, local_abspath,
540                                                    scratch_pool));
541
542              /* If the node is not present or deleted (read: not present
543                 in working), then the node is not a replacement */
544              if (below_working != svn_wc__db_status_not_present
545                  && below_working != svn_wc__db_status_deleted)
546                {
547                  node_status = svn_wc_status_replaced;
548                }
549              else
550                node_status = svn_wc_status_added;
551            }
552
553          /* Get moved-from info (only for potential op-roots of a move). */
554          if (info->moved_here && info->op_root)
555            {
556              svn_error_t *err;
557              err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
558                                          db, local_abspath,
559                                          result_pool, scratch_pool);
560
561              if (err)
562                {
563                  if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
564                    return svn_error_trace(err);
565
566                  svn_error_clear(err);
567                  /* We are no longer moved... So most likely we are somehow
568                     changing the db for things like resolving conflicts. */
569
570                  moved_from_abspath = NULL;
571                }
572            }
573        }
574    }
575
576
577  if (node_status == svn_wc_status_normal)
578    node_status = text_status;
579
580  if (node_status == svn_wc_status_normal
581      && prop_status != svn_wc_status_none)
582    node_status = prop_status;
583
584  /* 5. Easy out:  unless we're fetching -every- node, don't bother
585     to allocate a struct for an uninteresting node.
586
587     This filter should match the filter in is_sendable_status() */
588  if (! get_all)
589    if (((node_status == svn_wc_status_none)
590         || (node_status == svn_wc_status_normal))
591
592        && (! switched_p)
593        && (! info->locked)
594        && (! info->lock)
595        && (! repos_lock)
596        && (! info->changelist)
597        && (! conflicted)
598        && (! info->moved_to))
599      {
600        *status = NULL;
601        return SVN_NO_ERROR;
602      }
603
604  /* 6. Build and return a status structure. */
605
606  inner_stat = apr_pcalloc(result_pool, sizeof(*inner_stat));
607  stat = &inner_stat->s;
608  inner_stat->has_descendants = info->has_descendants;
609
610  switch (info->kind)
611    {
612      case svn_node_dir:
613        stat->kind = svn_node_dir;
614        break;
615      case svn_node_file:
616      case svn_node_symlink:
617        stat->kind = svn_node_file;
618        break;
619      case svn_node_unknown:
620      default:
621        stat->kind = svn_node_unknown;
622    }
623  stat->depth = info->depth;
624
625  if (dirent)
626    {
627      stat->filesize = (dirent->kind == svn_node_file)
628                            ? dirent->filesize
629                            : SVN_INVALID_FILESIZE;
630      stat->actual_kind = dirent->special ? svn_node_symlink
631                                          : dirent->kind;
632    }
633  else
634    {
635      stat->filesize = SVN_INVALID_FILESIZE;
636      stat->actual_kind = ignore_text_mods ? svn_node_unknown
637                                           : svn_node_none;
638    }
639
640  stat->node_status = node_status;
641  stat->text_status = text_status;
642  stat->prop_status = prop_status;
643  stat->repos_node_status = svn_wc_status_none;   /* default */
644  stat->repos_text_status = svn_wc_status_none;   /* default */
645  stat->repos_prop_status = svn_wc_status_none;   /* default */
646  stat->switched = switched_p;
647  stat->copied = copied;
648  stat->repos_lock = repos_lock;
649  stat->revision = info->revnum;
650  stat->changed_rev = info->changed_rev;
651  if (info->changed_author)
652    stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
653  stat->changed_date = info->changed_date;
654
655  stat->ood_kind = svn_node_none;
656  stat->ood_changed_rev = SVN_INVALID_REVNUM;
657  stat->ood_changed_date = 0;
658  stat->ood_changed_author = NULL;
659
660  SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
661                                     &stat->repos_root_url,
662                                     &stat->repos_uuid, info,
663                                     parent_repos_relpath,
664                                     parent_repos_root_url,
665                                     parent_repos_uuid,
666                                     db, local_abspath,
667                                     result_pool, scratch_pool));
668
669  if (info->lock)
670    {
671      svn_lock_t *lck = svn_lock_create(result_pool);
672      lck->path = stat->repos_relpath;
673      lck->token = info->lock->token;
674      lck->owner = info->lock->owner;
675      lck->comment = info->lock->comment;
676      lck->creation_date = info->lock->date;
677      stat->lock = lck;
678    }
679  else
680    stat->lock = NULL;
681
682  stat->locked = info->locked;
683  stat->conflicted = conflicted;
684  stat->versioned = TRUE;
685  if (info->changelist)
686    stat->changelist = apr_pstrdup(result_pool, info->changelist);
687
688  stat->moved_from_abspath = moved_from_abspath;
689
690  /* ### TODO: Handle multiple moved_to values properly */
691  if (info->moved_to)
692    stat->moved_to_abspath = apr_pstrdup(result_pool,
693                                         info->moved_to->moved_to_abspath);
694
695  stat->file_external = info->file_external;
696
697  *status = inner_stat;
698
699  return SVN_NO_ERROR;
700}
701
702/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
703   available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
704   temporary allocations.
705
706   If IS_IGNORED is non-zero and this is a non-versioned entity, set
707   the node_status to svn_wc_status_none.  Otherwise set the
708   node_status to svn_wc_status_unversioned.
709 */
710static svn_error_t *
711assemble_unversioned(svn_wc__internal_status_t **status,
712                     svn_wc__db_t *db,
713                     const char *local_abspath,
714                     const svn_io_dirent2_t *dirent,
715                     svn_boolean_t tree_conflicted,
716                     svn_boolean_t is_ignored,
717                     apr_pool_t *result_pool,
718                     apr_pool_t *scratch_pool)
719{
720  svn_wc__internal_status_t *inner_status;
721  svn_wc_status3_t *stat;
722
723  /* return a fairly blank structure. */
724  inner_status = apr_pcalloc(result_pool, sizeof(*inner_status));
725  stat = &inner_status->s;
726
727  /*stat->versioned = FALSE;*/
728  stat->kind = svn_node_unknown; /* not versioned */
729  stat->depth = svn_depth_unknown;
730  if (dirent)
731    {
732      stat->actual_kind = dirent->special ? svn_node_symlink
733                                           : dirent->kind;
734      stat->filesize = (dirent->kind == svn_node_file)
735                            ? dirent->filesize
736                            : SVN_INVALID_FILESIZE;
737    }
738  else
739    {
740       stat->actual_kind = svn_node_none;
741       stat->filesize = SVN_INVALID_FILESIZE;
742    }
743
744  stat->node_status = svn_wc_status_none;
745  stat->text_status = svn_wc_status_none;
746  stat->prop_status = svn_wc_status_none;
747  stat->repos_node_status = svn_wc_status_none;
748  stat->repos_text_status = svn_wc_status_none;
749  stat->repos_prop_status = svn_wc_status_none;
750
751  /* If this path has no entry, but IS present on disk, it's
752     unversioned.  If this file is being explicitly ignored (due
753     to matching an ignore-pattern), the node_status is set to
754     svn_wc_status_ignored.  Otherwise the node_status is set to
755     svn_wc_status_unversioned. */
756  if (dirent && dirent->kind != svn_node_none)
757    {
758      if (is_ignored)
759        stat->node_status = svn_wc_status_ignored;
760      else
761        stat->node_status = svn_wc_status_unversioned;
762    }
763  else if (tree_conflicted)
764    {
765      /* If this path has no entry, is NOT present on disk, and IS a
766         tree conflict victim, report it as conflicted. */
767      stat->node_status = svn_wc_status_conflicted;
768    }
769
770  stat->revision = SVN_INVALID_REVNUM;
771  stat->changed_rev = SVN_INVALID_REVNUM;
772  stat->ood_changed_rev = SVN_INVALID_REVNUM;
773  stat->ood_kind = svn_node_none;
774
775  /* For the case of an incoming delete to a locally deleted path during
776     an update, we get a tree conflict. */
777  stat->conflicted = tree_conflicted;
778  stat->changelist = NULL;
779
780  *status = inner_status;
781  return SVN_NO_ERROR;
782}
783
784
785/* Given an ENTRY object representing PATH, build a status structure
786   and pass it off to the STATUS_FUNC/STATUS_BATON.  All other
787   arguments are the same as those passed to assemble_status().  */
788static svn_error_t *
789send_status_structure(const struct walk_status_baton *wb,
790                      const char *local_abspath,
791                      const char *parent_repos_root_url,
792                      const char *parent_repos_relpath,
793                      const char *parent_repos_uuid,
794                      const struct svn_wc__db_info_t *info,
795                      const svn_io_dirent2_t *dirent,
796                      svn_boolean_t get_all,
797                      svn_wc_status_func4_t status_func,
798                      void *status_baton,
799                      apr_pool_t *scratch_pool)
800{
801  svn_wc__internal_status_t *statstruct;
802  const svn_lock_t *repos_lock = NULL;
803
804  /* Check for a repository lock. */
805  if (wb->repos_locks)
806    {
807      const char *repos_relpath, *repos_root_url, *repos_uuid;
808
809      SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
810                                         &repos_uuid,
811                                         info, parent_repos_relpath,
812                                         parent_repos_root_url,
813                                         parent_repos_uuid,
814                                         wb->db, local_abspath,
815                                         scratch_pool, scratch_pool));
816      if (repos_relpath)
817        {
818          /* repos_lock still uses the deprecated filesystem absolute path
819             format */
820          repos_lock = svn_hash_gets(wb->repos_locks,
821                                     svn_fspath__join("/", repos_relpath,
822                                                      scratch_pool));
823        }
824    }
825
826  SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
827                          parent_repos_root_url, parent_repos_relpath,
828                          parent_repos_uuid,
829                          info, dirent, get_all,
830                          wb->ignore_text_mods, wb->check_working_copy,
831                          repos_lock, scratch_pool, scratch_pool));
832
833  if (statstruct && status_func)
834    return svn_error_trace((*status_func)(status_baton, local_abspath,
835                                          &statstruct->s,
836                                          scratch_pool));
837
838  return SVN_NO_ERROR;
839}
840
841
842/* Store in *PATTERNS a list of ignores collected from svn:ignore properties
843   on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
844   repository ancestors (as cached in the working copy), including the default
845   ignores passed in as IGNORES.
846
847   Upon return, *PATTERNS will contain zero or more (const char *)
848   patterns from the value of the SVN_PROP_IGNORE property set on
849   the working directory path.
850
851   IGNORES is a list of patterns to include; typically this will
852   be the default ignores as, for example, specified in a config file.
853
854   DB, LOCAL_ABSPATH is used to access the working copy.
855
856   Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
857
858   None of the arguments may be NULL.
859*/
860static svn_error_t *
861collect_ignore_patterns(apr_array_header_t **patterns,
862                        svn_wc__db_t *db,
863                        const char *local_abspath,
864                        const apr_array_header_t *ignores,
865                        apr_pool_t *result_pool,
866                        apr_pool_t *scratch_pool)
867{
868  int i;
869  apr_hash_t *props;
870  apr_array_header_t *inherited_props;
871  svn_error_t *err;
872
873  /* ### assert we are passed a directory? */
874
875  *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
876
877  /* Copy default ignores into the local PATTERNS array. */
878  for (i = 0; i < ignores->nelts; i++)
879    {
880      const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
881      APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
882                                                            ignore);
883    }
884
885  err = svn_wc__db_read_inherited_props(&inherited_props, &props,
886                                        db, local_abspath,
887                                        SVN_PROP_INHERITABLE_IGNORES,
888                                        scratch_pool, scratch_pool);
889
890  if (err)
891    {
892      if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
893        return svn_error_trace(err);
894
895      svn_error_clear(err);
896      return SVN_NO_ERROR;
897    }
898
899  if (props)
900    {
901      const svn_string_t *value;
902
903      value = svn_hash_gets(props, SVN_PROP_IGNORE);
904      if (value)
905        svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
906                                 result_pool);
907
908      value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
909      if (value)
910        svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
911                                 result_pool);
912    }
913
914  for (i = 0; i < inherited_props->nelts; i++)
915    {
916      svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
917        inherited_props, i, svn_prop_inherited_item_t *);
918      const svn_string_t *value;
919
920      value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
921
922      if (value)
923        svn_cstring_split_append(*patterns, value->data,
924                                 "\n\r", FALSE, result_pool);
925    }
926
927  return SVN_NO_ERROR;
928}
929
930
931/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
932   LOCAL_ABSPATH is the drop location for, or an intermediate directory
933   of the drop location for, an externals definition.  Use SCRATCH_POOL
934   for scratchwork.  */
935static svn_boolean_t
936is_external_path(apr_hash_t *externals,
937                 const char *local_abspath,
938                 apr_pool_t *scratch_pool)
939{
940  apr_hash_index_t *hi;
941
942  /* First try: does the path exist as a key in the hash? */
943  if (svn_hash_gets(externals, local_abspath))
944    return TRUE;
945
946  /* Failing that, we need to check if any external is a child of
947     LOCAL_ABSPATH.  */
948  for (hi = apr_hash_first(scratch_pool, externals);
949       hi;
950       hi = apr_hash_next(hi))
951    {
952      const char *external_abspath = apr_hash_this_key(hi);
953
954      if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
955        return TRUE;
956    }
957
958  return FALSE;
959}
960
961
962/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
963   for it through STATUS_FUNC/STATUS_BATON unless this path is being
964   ignored.  This function should never be called on a versioned entry.
965
966   LOCAL_ABSPATH is the path to the unversioned file whose status is being
967   requested.  PATH_KIND is the node kind of NAME as determined by the
968   caller.  PATH_SPECIAL is the special status of the path, also determined
969   by the caller.
970   PATTERNS points to a list of filename patterns which are marked as ignored.
971   None of these parameter may be NULL.
972
973   If NO_IGNORE is TRUE, the item will be added regardless of
974   whether it is ignored; otherwise we will only add the item if it
975   does not match any of the patterns in PATTERN or INHERITED_IGNORES.
976
977   Allocate everything in POOL.
978*/
979static svn_error_t *
980send_unversioned_item(const struct walk_status_baton *wb,
981                      const char *local_abspath,
982                      const svn_io_dirent2_t *dirent,
983                      svn_boolean_t tree_conflicted,
984                      const apr_array_header_t *patterns,
985                      svn_boolean_t no_ignore,
986                      svn_wc_status_func4_t status_func,
987                      void *status_baton,
988                      apr_pool_t *scratch_pool)
989{
990  svn_boolean_t is_ignored;
991  svn_boolean_t is_external;
992  svn_wc__internal_status_t *status;
993  const char *base_name = svn_dirent_basename(local_abspath, NULL);
994
995  is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
996  SVN_ERR(assemble_unversioned(&status,
997                               wb->db, local_abspath,
998                               dirent, tree_conflicted,
999                               is_ignored,
1000                               scratch_pool, scratch_pool));
1001
1002  is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
1003  if (is_external)
1004    status->s.node_status = svn_wc_status_external;
1005
1006  /* We can have a tree conflict on an unversioned path, i.e. an incoming
1007   * delete on a locally deleted path during an update. Don't ever ignore
1008   * those! */
1009  if (status->s.conflicted)
1010    is_ignored = FALSE;
1011
1012  /* If we aren't ignoring it, or if it's an externals path, pass this
1013     entry to the status func. */
1014  if (no_ignore
1015      || !is_ignored
1016      || is_external)
1017    return svn_error_trace((*status_func)(status_baton, local_abspath,
1018                                          &status->s, scratch_pool));
1019
1020  return SVN_NO_ERROR;
1021}
1022
1023static svn_error_t *
1024get_dir_status(const struct walk_status_baton *wb,
1025               const char *local_abspath,
1026               svn_boolean_t skip_this_dir,
1027               const char *parent_repos_root_url,
1028               const char *parent_repos_relpath,
1029               const char *parent_repos_uuid,
1030               const struct svn_wc__db_info_t *dir_info,
1031               const svn_io_dirent2_t *dirent,
1032               const apr_array_header_t *ignore_patterns,
1033               svn_depth_t depth,
1034               svn_boolean_t get_all,
1035               svn_boolean_t no_ignore,
1036               svn_wc_status_func4_t status_func,
1037               void *status_baton,
1038               svn_cancel_func_t cancel_func,
1039               void *cancel_baton,
1040               apr_pool_t *scratch_pool);
1041
1042/* Send out a status structure according to the information gathered on one
1043 * child node. (Basically this function is the guts of the loop in
1044 * get_dir_status() and of get_child_status().)
1045 *
1046 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
1047 * dirname of LOCAL_ABSPATH.
1048 *
1049 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
1050 * be an unversioned file or dir, or a versioned file.  For versioned
1051 * directories use get_dir_status() instead.
1052 *
1053 * INFO may be NULL for an unversioned node. If such node has a tree conflict,
1054 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
1055 * UNVERSIONED_TREE_CONFLICTED is ignored.
1056 *
1057 * DIRENT should reflect LOCAL_ABSPATH's dirent information.
1058 *
1059 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
1060 * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
1061 *
1062 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
1063 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
1064 * containing all ignore patterns, as returned by collect_ignore_patterns() on
1065 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
1066 * non-NULL, it is assumed it already holds those results.
1067 * This speeds up repeated calls with the same PARENT_ABSPATH.
1068 *
1069 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
1070 * allocations are made in SCRATCH_POOL.
1071 *
1072 * The remaining parameters correspond to get_dir_status(). */
1073static svn_error_t *
1074one_child_status(const struct walk_status_baton *wb,
1075                 const char *local_abspath,
1076                 const char *parent_abspath,
1077                 const struct svn_wc__db_info_t *info,
1078                 const svn_io_dirent2_t *dirent,
1079                 const char *dir_repos_root_url,
1080                 const char *dir_repos_relpath,
1081                 const char *dir_repos_uuid,
1082                 svn_boolean_t unversioned_tree_conflicted,
1083                 apr_array_header_t **collected_ignore_patterns,
1084                 const apr_array_header_t *ignore_patterns,
1085                 svn_depth_t depth,
1086                 svn_boolean_t get_all,
1087                 svn_boolean_t no_ignore,
1088                 svn_wc_status_func4_t status_func,
1089                 void *status_baton,
1090                 svn_cancel_func_t cancel_func,
1091                 void *cancel_baton,
1092                 apr_pool_t *result_pool,
1093                 apr_pool_t *scratch_pool)
1094{
1095  svn_boolean_t conflicted = info ? info->conflicted
1096                                  : unversioned_tree_conflicted;
1097
1098  if (info
1099      && info->status != svn_wc__db_status_not_present
1100      && info->status != svn_wc__db_status_excluded
1101      && info->status != svn_wc__db_status_server_excluded
1102      && !(info->kind == svn_node_unknown
1103           && info->status == svn_wc__db_status_normal))
1104    {
1105      if (depth == svn_depth_files
1106          && info->kind == svn_node_dir)
1107        {
1108          return SVN_NO_ERROR;
1109        }
1110
1111      SVN_ERR(send_status_structure(wb, local_abspath,
1112                                    dir_repos_root_url,
1113                                    dir_repos_relpath,
1114                                    dir_repos_uuid,
1115                                    info, dirent, get_all,
1116                                    status_func, status_baton,
1117                                    scratch_pool));
1118
1119      /* Descend in subdirectories. */
1120      if (depth == svn_depth_infinity
1121          && info->has_descendants /* is dir, or was dir and tc descendants */)
1122        {
1123          SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
1124                                 dir_repos_root_url, dir_repos_relpath,
1125                                 dir_repos_uuid, info,
1126                                 dirent, ignore_patterns,
1127                                 svn_depth_infinity, get_all,
1128                                 no_ignore,
1129                                 status_func, status_baton,
1130                                 cancel_func, cancel_baton,
1131                                 scratch_pool));
1132        }
1133
1134      return SVN_NO_ERROR;
1135    }
1136
1137  /* If conflicted, fall right through to unversioned.
1138   * With depth_files, show all conflicts, even if their report is only
1139   * about directories. A tree conflict may actually report two different
1140   * kinds, so it's not so easy to define what depth=files means. We could go
1141   * look up the kinds in the conflict ... just show all. */
1142  if (! conflicted)
1143    {
1144      /* We have a node, but its not visible in the WC. It can be a marker
1145         node (not present, (server) excluded), *or* it can be the explictly
1146         passed target of the status walk operation that doesn't exist.
1147
1148         We only report the node when the caller explicitly as
1149      */
1150      if (dirent == NULL && strcmp(wb->target_abspath, local_abspath) != 0)
1151        return SVN_NO_ERROR; /* Marker node */
1152
1153      if (depth == svn_depth_files && dirent && dirent->kind == svn_node_dir)
1154        return SVN_NO_ERROR;
1155
1156      if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
1157                            scratch_pool))
1158        return SVN_NO_ERROR;
1159    }
1160
1161  /* The node exists on disk but there is no versioned information about it,
1162   * or it doesn't exist but is a tree conflicted path or should be
1163   * reported not-present. */
1164
1165  /* Why pass ignore patterns on a tree conflicted node, even if it should
1166   * always show up in clients' status reports anyway? Because the calling
1167   * client decides whether to ignore, and thus this flag needs to be
1168   * determined.  For example, in 'svn status', plain unversioned nodes show
1169   * as '?  C', where ignored ones show as 'I  C'. */
1170
1171  if (ignore_patterns && ! *collected_ignore_patterns)
1172    SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
1173                                    wb->db, parent_abspath, ignore_patterns,
1174                                    result_pool, scratch_pool));
1175
1176  SVN_ERR(send_unversioned_item(wb,
1177                                local_abspath,
1178                                dirent,
1179                                conflicted,
1180                                *collected_ignore_patterns,
1181                                no_ignore,
1182                                status_func, status_baton,
1183                                scratch_pool));
1184
1185  return SVN_NO_ERROR;
1186}
1187
1188/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
1189   for all its child nodes (according to DEPTH) through STATUS_FUNC /
1190   STATUS_BATON.
1191
1192   If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
1193   All subdirs reached by recursion will be reported regardless of this
1194   parameter's value.
1195
1196   PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
1197   URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
1198   retrieving them again. Otherwise they must be NULL.
1199
1200   DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
1201   it again. Otherwise it must be NULL.
1202
1203   DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
1204   so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
1205
1206   Other arguments are the same as those passed to
1207   svn_wc_get_status_editor5().  */
1208static svn_error_t *
1209get_dir_status(const struct walk_status_baton *wb,
1210               const char *local_abspath,
1211               svn_boolean_t skip_this_dir,
1212               const char *parent_repos_root_url,
1213               const char *parent_repos_relpath,
1214               const char *parent_repos_uuid,
1215               const struct svn_wc__db_info_t *dir_info,
1216               const svn_io_dirent2_t *dirent,
1217               const apr_array_header_t *ignore_patterns,
1218               svn_depth_t depth,
1219               svn_boolean_t get_all,
1220               svn_boolean_t no_ignore,
1221               svn_wc_status_func4_t status_func,
1222               void *status_baton,
1223               svn_cancel_func_t cancel_func,
1224               void *cancel_baton,
1225               apr_pool_t *scratch_pool)
1226{
1227  const char *dir_repos_root_url;
1228  const char *dir_repos_relpath;
1229  const char *dir_repos_uuid;
1230  apr_hash_t *dirents, *nodes, *conflicts, *all_children;
1231  apr_array_header_t *sorted_children;
1232  apr_array_header_t *collected_ignore_patterns = NULL;
1233  apr_pool_t *iterpool;
1234  svn_error_t *err;
1235  int i;
1236
1237  if (cancel_func)
1238    SVN_ERR(cancel_func(cancel_baton));
1239
1240  if (depth == svn_depth_unknown)
1241    depth = svn_depth_infinity;
1242
1243  iterpool = svn_pool_create(scratch_pool);
1244
1245  if (wb->check_working_copy)
1246    {
1247      err = svn_io_get_dirents3(&dirents, local_abspath,
1248                                wb->ignore_text_mods /* only_check_type*/,
1249                                scratch_pool, iterpool);
1250      if (err
1251          && (APR_STATUS_IS_ENOENT(err->apr_err)
1252              || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
1253        {
1254          svn_error_clear(err);
1255          dirents = apr_hash_make(scratch_pool);
1256        }
1257      else
1258        SVN_ERR(err);
1259    }
1260  else
1261    dirents = apr_hash_make(scratch_pool);
1262
1263  if (!dir_info)
1264    SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath,
1265                                        !wb->check_working_copy,
1266                                        scratch_pool, iterpool));
1267
1268  SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1269                                     &dir_repos_uuid, dir_info,
1270                                     parent_repos_relpath,
1271                                     parent_repos_root_url, parent_repos_uuid,
1272                                     wb->db, local_abspath,
1273                                     scratch_pool, iterpool));
1274
1275  /* Create a hash containing all children.  The source hashes
1276     don't all map the same types, but only the keys of the result
1277     hash are subsequently used. */
1278  SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1279                                        wb->db, local_abspath,
1280                                        !wb->check_working_copy,
1281                                        scratch_pool, iterpool));
1282
1283  all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
1284  if (apr_hash_count(conflicts) > 0)
1285    all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
1286
1287  /* Handle "this-dir" first. */
1288  if (! skip_this_dir)
1289    {
1290      /* This code is not conditional on HAVE_SYMLINK as some systems that do
1291         not allow creating symlinks (!HAVE_SYMLINK) can still encounter
1292         symlinks (or in case of Windows also 'Junctions') created by other
1293         methods.
1294
1295         Without this block a working copy in the root of a junction is
1296         reported as an obstruction, because the junction itself is reported as
1297         special.
1298
1299         Systems that have no symlink support at all, would always see
1300         dirent->special as FALSE, so even there enabling this code shouldn't
1301         produce problems.
1302       */
1303      if (dirent->special)
1304        {
1305          svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
1306
1307          /* We're being pointed to "this-dir" via a symlink.
1308           * Get the real node kind and pretend the path is not a symlink.
1309           * This prevents send_status_structure() from treating this-dir
1310           * as a directory obstructed by a file. */
1311          SVN_ERR(svn_io_check_resolved_path(local_abspath,
1312                                             &this_dirent->kind, iterpool));
1313          this_dirent->special = FALSE;
1314          SVN_ERR(send_status_structure(wb, local_abspath,
1315                                        parent_repos_root_url,
1316                                        parent_repos_relpath,
1317                                        parent_repos_uuid,
1318                                        dir_info, this_dirent, get_all,
1319                                        status_func, status_baton,
1320                                        iterpool));
1321        }
1322     else
1323        SVN_ERR(send_status_structure(wb, local_abspath,
1324                                      parent_repos_root_url,
1325                                      parent_repos_relpath,
1326                                      parent_repos_uuid,
1327                                      dir_info, dirent, get_all,
1328                                      status_func, status_baton,
1329                                      iterpool));
1330    }
1331
1332  /* If the requested depth is empty, we only need status on this-dir. */
1333  if (depth == svn_depth_empty)
1334    return SVN_NO_ERROR;
1335
1336  /* Walk all the children of this directory. */
1337  sorted_children = svn_sort__hash(all_children,
1338                                   svn_sort_compare_items_lexically,
1339                                   scratch_pool);
1340  for (i = 0; i < sorted_children->nelts; i++)
1341    {
1342      const void *key;
1343      apr_ssize_t klen;
1344      svn_sort__item_t item;
1345      const char *child_abspath;
1346      svn_io_dirent2_t *child_dirent;
1347      const struct svn_wc__db_info_t *child_info;
1348
1349      svn_pool_clear(iterpool);
1350
1351      item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
1352      key = item.key;
1353      klen = item.klen;
1354
1355      child_abspath = svn_dirent_join(local_abspath, key, iterpool);
1356      child_dirent = apr_hash_get(dirents, key, klen);
1357      child_info = apr_hash_get(nodes, key, klen);
1358
1359      SVN_ERR(one_child_status(wb,
1360                               child_abspath,
1361                               local_abspath,
1362                               child_info,
1363                               child_dirent,
1364                               dir_repos_root_url,
1365                               dir_repos_relpath,
1366                               dir_repos_uuid,
1367                               apr_hash_get(conflicts, key, klen) != NULL,
1368                               &collected_ignore_patterns,
1369                               ignore_patterns,
1370                               depth,
1371                               get_all,
1372                               no_ignore,
1373                               status_func,
1374                               status_baton,
1375                               cancel_func,
1376                               cancel_baton,
1377                               scratch_pool,
1378                               iterpool));
1379    }
1380
1381  /* Destroy our subpools. */
1382  svn_pool_destroy(iterpool);
1383
1384  return SVN_NO_ERROR;
1385}
1386
1387/* Send an svn_wc_status3_t * structure for the versioned file, or for the
1388 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
1389 * explicit target). Does not recurse.
1390 *
1391 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
1392 * unversioned nodes. An unversioned and tree-conflicted node however should
1393 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
1394 *
1395 * DIRENT should reflect LOCAL_ABSPATH.
1396 *
1397 * All allocations made in SCRATCH_POOL.
1398 *
1399 * The remaining parameters correspond to get_dir_status(). */
1400static svn_error_t *
1401get_child_status(const struct walk_status_baton *wb,
1402                 const char *local_abspath,
1403                 const struct svn_wc__db_info_t *info,
1404                 const svn_io_dirent2_t *dirent,
1405                 const apr_array_header_t *ignore_patterns,
1406                 svn_boolean_t get_all,
1407                 svn_wc_status_func4_t status_func,
1408                 void *status_baton,
1409                 svn_cancel_func_t cancel_func,
1410                 void *cancel_baton,
1411                 apr_pool_t *scratch_pool)
1412{
1413  const char *dir_repos_root_url;
1414  const char *dir_repos_relpath;
1415  const char *dir_repos_uuid;
1416  const struct svn_wc__db_info_t *dir_info;
1417  apr_array_header_t *collected_ignore_patterns = NULL;
1418  const char *parent_abspath = svn_dirent_dirname(local_abspath,
1419                                                  scratch_pool);
1420
1421  if (cancel_func)
1422    SVN_ERR(cancel_func(cancel_baton));
1423
1424  if (dirent->kind == svn_node_none)
1425    dirent = NULL;
1426
1427  SVN_ERR(svn_wc__db_read_single_info(&dir_info,
1428                                      wb->db, parent_abspath,
1429                                      !wb->check_working_copy,
1430                                      scratch_pool, scratch_pool));
1431
1432  SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1433                                     &dir_repos_uuid, dir_info,
1434                                     NULL, NULL, NULL,
1435                                     wb->db, parent_abspath,
1436                                     scratch_pool, scratch_pool));
1437
1438  /* An unversioned node with a tree conflict will see an INFO != NULL here,
1439   * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
1440   * effect and INFO->CONFLICTED counts.
1441   * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
1442   * ### alike? */
1443  SVN_ERR(one_child_status(wb,
1444                           local_abspath,
1445                           parent_abspath,
1446                           info,
1447                           dirent,
1448                           dir_repos_root_url,
1449                           dir_repos_relpath,
1450                           dir_repos_uuid,
1451                           FALSE, /* unversioned_tree_conflicted */
1452                           &collected_ignore_patterns,
1453                           ignore_patterns,
1454                           svn_depth_empty,
1455                           get_all,
1456                           TRUE, /* no_ignore. This is an explicit target. */
1457                           status_func,
1458                           status_baton,
1459                           cancel_func,
1460                           cancel_baton,
1461                           scratch_pool,
1462                           scratch_pool));
1463  return SVN_NO_ERROR;
1464}
1465
1466
1467
1468/*** Helpers ***/
1469
1470/* A faux status callback function for stashing STATUS item in an hash
1471   (which is the BATON), keyed on PATH.  This implements the
1472   svn_wc_status_func4_t interface. */
1473static svn_error_t *
1474hash_stash(void *baton,
1475           const char *path,
1476           const svn_wc_status3_t *status,
1477           apr_pool_t *scratch_pool)
1478{
1479  apr_hash_t *stat_hash = baton;
1480  apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1481  void *new_status = svn_wc_dup_status3(status, hash_pool);
1482  const svn_wc__internal_status_t *old_status = (const void*)status;
1483
1484  /* Copy the internal/private data. */
1485  svn_wc__internal_status_t *is = new_status;
1486  is->has_descendants = old_status->has_descendants;
1487
1488  assert(! svn_hash_gets(stat_hash, path));
1489  svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), new_status);
1490
1491  return SVN_NO_ERROR;
1492}
1493
1494
1495/* Look up the key PATH in BATON->STATII.  IS_DIR_BATON indicates whether
1496   baton is a struct *dir_baton or struct *file_baton.  If the value doesn't
1497   yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
1498   create a new status struct using the hash's pool.
1499
1500   If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1501   of date (ood) information we want to set in BATON.  This is necessary
1502   because this function tweaks the status of out-of-date directories
1503   (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1504   (BATON == THIS_DIR_BATON->parent_baton).  In the latter case THIS_DIR_BATON
1505   contains the ood info we want to bubble up to ancestor directories so these
1506   accurately reflect the fact they have an ood descendant.
1507
1508   Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
1509   status structure's "network" fields.
1510
1511   Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1512   is ignored:
1513
1514       If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
1515       optionally the revision path was deleted, in all other cases it must
1516       be set to SVN_INVALID_REVNUM.  If DELETED_REV is not
1517       SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1518       then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1519       If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
1520       svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1521       ood_last_cmt_rev value - see comment below.
1522
1523   If a new struct was added, set the repos_lock to REPOS_LOCK. */
1524static svn_error_t *
1525tweak_statushash(void *baton,
1526                 void *this_dir_baton,
1527                 svn_boolean_t is_dir_baton,
1528                 svn_wc__db_t *db,
1529                 svn_boolean_t check_working_copy,
1530                 const char *local_abspath,
1531                 enum svn_wc_status_kind repos_node_status,
1532                 enum svn_wc_status_kind repos_text_status,
1533                 enum svn_wc_status_kind repos_prop_status,
1534                 svn_revnum_t deleted_rev,
1535                 const svn_lock_t *repos_lock,
1536                 apr_pool_t *scratch_pool)
1537{
1538  svn_wc_status3_t *statstruct;
1539  apr_pool_t *pool;
1540  apr_hash_t *statushash;
1541
1542  if (is_dir_baton)
1543    statushash = ((struct dir_baton *) baton)->statii;
1544  else
1545    statushash = ((struct file_baton *) baton)->dir_baton->statii;
1546  pool = apr_hash_pool_get(statushash);
1547
1548  /* Is PATH already a hash-key? */
1549  statstruct = svn_hash_gets(statushash, local_abspath);
1550
1551  /* If not, make it so. */
1552  if (! statstruct)
1553    {
1554      svn_wc__internal_status_t *i_stat;
1555      /* If this item isn't being added, then we're most likely
1556         dealing with a non-recursive (or at least partially
1557         non-recursive) working copy.  Due to bugs in how the client
1558         reports the state of non-recursive working copies, the
1559         repository can send back responses about paths that don't
1560         even exist locally.  Our best course here is just to ignore
1561         those responses.  After all, if the client had reported
1562         correctly in the first, that path would either be mentioned
1563         as an 'add' or not mentioned at all, depending on how we
1564         eventually fix the bugs in non-recursivity.  See issue
1565         #2122 for details. */
1566      if (repos_node_status != svn_wc_status_added)
1567        return SVN_NO_ERROR;
1568
1569      /* Use the public API to get a statstruct, and put it into the hash. */
1570      SVN_ERR(internal_status(&i_stat, db, local_abspath,
1571                              check_working_copy, pool, scratch_pool));
1572      statstruct = &i_stat->s;
1573      statstruct->repos_lock = repos_lock;
1574      svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
1575    }
1576
1577  /* Merge a repos "delete" + "add" into a single "replace". */
1578  if ((repos_node_status == svn_wc_status_added)
1579      && (statstruct->repos_node_status == svn_wc_status_deleted))
1580    repos_node_status = svn_wc_status_replaced;
1581
1582  /* Tweak the structure's repos fields. */
1583  if (repos_node_status)
1584    statstruct->repos_node_status = repos_node_status;
1585  if (repos_text_status)
1586    statstruct->repos_text_status = repos_text_status;
1587  if (repos_prop_status)
1588    statstruct->repos_prop_status = repos_prop_status;
1589
1590  /* Copy out-of-date info. */
1591  if (is_dir_baton)
1592    {
1593      struct dir_baton *b = this_dir_baton;
1594
1595      if (!statstruct->repos_relpath && b->repos_relpath)
1596        {
1597          if (statstruct->repos_node_status == svn_wc_status_deleted)
1598            {
1599              /* When deleting PATH, BATON is for PATH's parent,
1600                 so we must construct PATH's real statstruct->url. */
1601              statstruct->repos_relpath =
1602                            svn_relpath_join(b->repos_relpath,
1603                                             svn_dirent_basename(local_abspath,
1604                                                                 NULL),
1605                                             pool);
1606            }
1607          else
1608            statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1609
1610          statstruct->repos_root_url =
1611                              b->edit_baton->anchor_status->s.repos_root_url;
1612          statstruct->repos_uuid =
1613                              b->edit_baton->anchor_status->s.repos_uuid;
1614        }
1615
1616      /* The last committed date, and author for deleted items
1617         isn't available. */
1618      if (statstruct->repos_node_status == svn_wc_status_deleted)
1619        {
1620          statstruct->ood_kind = statstruct->kind;
1621
1622          /* Pre 1.5 servers don't provide the revision a path was deleted.
1623             So we punt and use the last committed revision of the path's
1624             parent, which has some chance of being correct.  At worse it
1625             is a higher revision than the path was deleted, but this is
1626             better than nothing... */
1627          if (deleted_rev == SVN_INVALID_REVNUM)
1628            statstruct->ood_changed_rev =
1629              ((struct dir_baton *) baton)->ood_changed_rev;
1630          else
1631            statstruct->ood_changed_rev = deleted_rev;
1632        }
1633      else
1634        {
1635          statstruct->ood_kind = b->ood_kind;
1636          statstruct->ood_changed_rev = b->ood_changed_rev;
1637          statstruct->ood_changed_date = b->ood_changed_date;
1638          if (b->ood_changed_author)
1639            statstruct->ood_changed_author =
1640              apr_pstrdup(pool, b->ood_changed_author);
1641        }
1642
1643    }
1644  else
1645    {
1646      struct file_baton *b = baton;
1647      statstruct->ood_changed_rev = b->ood_changed_rev;
1648      statstruct->ood_changed_date = b->ood_changed_date;
1649      if (!statstruct->repos_relpath && b->repos_relpath)
1650        {
1651          statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1652          statstruct->repos_root_url =
1653                          b->edit_baton->anchor_status->s.repos_root_url;
1654          statstruct->repos_uuid =
1655                          b->edit_baton->anchor_status->s.repos_uuid;
1656        }
1657      statstruct->ood_kind = b->ood_kind;
1658      if (b->ood_changed_author)
1659        statstruct->ood_changed_author =
1660          apr_pstrdup(pool, b->ood_changed_author);
1661    }
1662  return SVN_NO_ERROR;
1663}
1664
1665/* Returns the URL for DB */
1666static const char *
1667find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
1668{
1669  /* If we have no name, we're the root, return the anchor URL. */
1670  if (! db->name)
1671    return db->edit_baton->anchor_status->s.repos_relpath;
1672  else
1673    {
1674      const char *repos_relpath;
1675      struct dir_baton *pb = db->parent_baton;
1676      const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
1677                                                     db->local_abspath);
1678      /* Note that status->repos_relpath could be NULL in the case of a missing
1679       * directory, which means we need to recurse up another level to get
1680       * a useful relpath. */
1681      if (status && status->repos_relpath)
1682        return status->repos_relpath;
1683
1684      repos_relpath = find_dir_repos_relpath(pb, pool);
1685      return svn_relpath_join(repos_relpath, db->name, pool);
1686    }
1687}
1688
1689
1690
1691/* Create a new dir_baton for subdir PATH. */
1692static svn_error_t *
1693make_dir_baton(void **dir_baton,
1694               const char *path,
1695               struct edit_baton *edit_baton,
1696               struct dir_baton *parent_baton,
1697               apr_pool_t *result_pool)
1698{
1699  struct dir_baton *pb = parent_baton;
1700  struct edit_baton *eb = edit_baton;
1701  struct dir_baton *d;
1702  const char *local_abspath;
1703  const svn_wc__internal_status_t *status_in_parent;
1704  apr_pool_t *dir_pool;
1705
1706  if (parent_baton)
1707    dir_pool = svn_pool_create(parent_baton->pool);
1708  else
1709    dir_pool = svn_pool_create(result_pool);
1710
1711  d = apr_pcalloc(dir_pool, sizeof(*d));
1712
1713  SVN_ERR_ASSERT(path || (! pb));
1714
1715  /* Construct the absolute path of this directory. */
1716  if (pb)
1717    local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
1718  else
1719    local_abspath = eb->anchor_abspath;
1720
1721  /* Finish populating the baton members. */
1722  d->pool = dir_pool;
1723  d->local_abspath = local_abspath;
1724  d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
1725  d->edit_baton = edit_baton;
1726  d->parent_baton = parent_baton;
1727  d->statii = apr_hash_make(dir_pool);
1728  d->ood_changed_rev = SVN_INVALID_REVNUM;
1729  d->ood_changed_date = 0;
1730  d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
1731  d->ood_kind = svn_node_dir;
1732  d->ood_changed_author = NULL;
1733
1734  if (pb)
1735    {
1736      if (pb->excluded)
1737        d->excluded = TRUE;
1738      else if (pb->depth == svn_depth_immediates)
1739        d->depth = svn_depth_empty;
1740      else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1741        d->excluded = TRUE;
1742      else if (pb->depth == svn_depth_unknown)
1743        /* This is only tentative, it can be overridden from d's entry
1744           later. */
1745        d->depth = svn_depth_unknown;
1746      else
1747        d->depth = svn_depth_infinity;
1748    }
1749  else
1750    {
1751      d->depth = eb->default_depth;
1752    }
1753
1754  /* Get the status for this path's children.  Of course, we only want
1755     to do this if the path is versioned as a directory. */
1756  if (pb)
1757    status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
1758  else
1759    status_in_parent = eb->anchor_status;
1760
1761  if (status_in_parent
1762      && (status_in_parent->has_descendants)
1763      && (! d->excluded)
1764      && (d->depth == svn_depth_unknown
1765          || d->depth == svn_depth_infinity
1766          || d->depth == svn_depth_files
1767          || d->depth == svn_depth_immediates)
1768          )
1769    {
1770      const svn_wc_status3_t *this_dir_status;
1771      const apr_array_header_t *ignores = eb->ignores;
1772
1773      SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
1774                             status_in_parent->s.repos_root_url,
1775                             NULL /*parent_repos_relpath*/,
1776                             status_in_parent->s.repos_uuid,
1777                             NULL,
1778                             NULL /* dirent */, ignores,
1779                             d->depth == svn_depth_files
1780                                      ? svn_depth_files
1781                                      : svn_depth_immediates,
1782                             TRUE, TRUE,
1783                             hash_stash, d->statii,
1784                             eb->cancel_func, eb->cancel_baton,
1785                             dir_pool));
1786
1787      /* If we found a depth here, it should govern. */
1788      this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
1789      if (this_dir_status && this_dir_status->versioned
1790          && (d->depth == svn_depth_unknown
1791              || d->depth > status_in_parent->s.depth))
1792        {
1793          d->depth = this_dir_status->depth;
1794        }
1795    }
1796
1797  *dir_baton = d;
1798  return SVN_NO_ERROR;
1799}
1800
1801
1802/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1803   NAME is just one component, not a path. */
1804static struct file_baton *
1805make_file_baton(struct dir_baton *parent_dir_baton,
1806                const char *path,
1807                apr_pool_t *pool)
1808{
1809  struct dir_baton *pb = parent_dir_baton;
1810  struct edit_baton *eb = pb->edit_baton;
1811  struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1812
1813  /* Finish populating the baton members. */
1814  f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1815  f->name = svn_dirent_basename(f->local_abspath, NULL);
1816  f->pool = pool;
1817  f->dir_baton = pb;
1818  f->edit_baton = eb;
1819  f->ood_changed_rev = SVN_INVALID_REVNUM;
1820  f->ood_changed_date = 0;
1821  f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
1822                                      f->name, pool);
1823  f->ood_kind = svn_node_file;
1824  f->ood_changed_author = NULL;
1825  return f;
1826}
1827
1828
1829/**
1830 * Return a boolean answer to the question "Is @a status something that
1831 * should be reported?".  @a no_ignore and @a get_all are the same as
1832 * svn_wc_get_status_editor4().
1833 *
1834 * This implementation should match the filter in assemble_status()
1835 */
1836static svn_boolean_t
1837is_sendable_status(const svn_wc__internal_status_t *i_status,
1838                   svn_boolean_t no_ignore,
1839                   svn_boolean_t get_all)
1840{
1841  const svn_wc_status3_t *status = &i_status->s;
1842  /* If the repository status was touched at all, it's interesting. */
1843  if (status->repos_node_status != svn_wc_status_none)
1844    return TRUE;
1845
1846  /* If there is a lock in the repository, send it. */
1847  if (status->repos_lock)
1848    return TRUE;
1849
1850  if (status->conflicted)
1851    return TRUE;
1852
1853  /* If the item is ignored, and we don't want ignores, skip it. */
1854  if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
1855    return FALSE;
1856
1857  /* If we want everything, we obviously want this single-item subset
1858     of everything. */
1859  if (get_all)
1860    return TRUE;
1861
1862  /* If the item is unversioned, display it. */
1863  if (status->node_status == svn_wc_status_unversioned)
1864    return TRUE;
1865
1866  /* If the text, property or tree state is interesting, send it. */
1867  if ((status->node_status != svn_wc_status_none)
1868       && (status->node_status != svn_wc_status_normal))
1869    return TRUE;
1870
1871  /* If it's switched, send it. */
1872  if (status->switched)
1873    return TRUE;
1874
1875  /* If there is a lock token, send it. */
1876  if (status->versioned && status->lock)
1877    return TRUE;
1878
1879  /* If the entry is associated with a changelist, send it. */
1880  if (status->changelist)
1881    return TRUE;
1882
1883  if (status->moved_to_abspath)
1884    return TRUE;
1885
1886  /* Otherwise, don't send it. */
1887  return FALSE;
1888}
1889
1890
1891/* Baton for mark_status. */
1892struct status_baton
1893{
1894  svn_wc_status_func4_t real_status_func;  /* real status function */
1895  void *real_status_baton;                 /* real status baton */
1896};
1897
1898/* A status callback function which wraps the *real* status
1899   function/baton.   It simply sets the "repos_node_status" field of the
1900   STATUS to svn_wc_status_deleted and passes it off to the real
1901   status func/baton. Implements svn_wc_status_func4_t */
1902static svn_error_t *
1903mark_deleted(void *baton,
1904             const char *local_abspath,
1905             const svn_wc_status3_t *status,
1906             apr_pool_t *scratch_pool)
1907{
1908  struct status_baton *sb = baton;
1909  svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
1910  new_status->repos_node_status = svn_wc_status_deleted;
1911  return sb->real_status_func(sb->real_status_baton, local_abspath,
1912                              new_status, scratch_pool);
1913}
1914
1915
1916/* Handle a directory's STATII hash.  EB is the edit baton.  DIR_PATH
1917   and DIR_ENTRY are the on-disk path and entry, respectively, for the
1918   directory itself.  Descend into subdirectories according to DEPTH.
1919   Also, if DIR_WAS_DELETED is set, each status that is reported
1920   through this function will have its repos_text_status field showing
1921   a deletion.  Use POOL for all allocations. */
1922static svn_error_t *
1923handle_statii(struct edit_baton *eb,
1924              const char *dir_repos_root_url,
1925              const char *dir_repos_relpath,
1926              const char *dir_repos_uuid,
1927              apr_hash_t *statii,
1928              svn_boolean_t dir_was_deleted,
1929              svn_depth_t depth,
1930              apr_pool_t *pool)
1931{
1932  const apr_array_header_t *ignores = eb->ignores;
1933  apr_hash_index_t *hi;
1934  apr_pool_t *iterpool = svn_pool_create(pool);
1935  svn_wc_status_func4_t status_func = eb->status_func;
1936  void *status_baton = eb->status_baton;
1937  struct status_baton sb;
1938
1939  if (dir_was_deleted)
1940    {
1941      sb.real_status_func = eb->status_func;
1942      sb.real_status_baton = eb->status_baton;
1943      status_func = mark_deleted;
1944      status_baton = &sb;
1945    }
1946
1947  /* Loop over all the statii still in our hash, handling each one. */
1948  for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1949    {
1950      const char *local_abspath = apr_hash_this_key(hi);
1951      svn_wc__internal_status_t *status = apr_hash_this_val(hi);
1952
1953      /* Clear the subpool. */
1954      svn_pool_clear(iterpool);
1955
1956      /* Now, handle the status.  We don't recurse for svn_depth_immediates
1957         because we already have the subdirectories' statii. */
1958      if (status->has_descendants
1959          && (depth == svn_depth_unknown
1960              || depth == svn_depth_infinity))
1961        {
1962          SVN_ERR(get_dir_status(&eb->wb,
1963                                 local_abspath, TRUE,
1964                                 dir_repos_root_url, dir_repos_relpath,
1965                                 dir_repos_uuid,
1966                                 NULL,
1967                                 NULL /* dirent */,
1968                                 ignores, depth, eb->get_all, eb->no_ignore,
1969                                 status_func, status_baton,
1970                                 eb->cancel_func, eb->cancel_baton,
1971                                 iterpool));
1972        }
1973      if (dir_was_deleted)
1974        status->s.repos_node_status = svn_wc_status_deleted;
1975      if (is_sendable_status(status, eb->no_ignore, eb->get_all))
1976        SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, &status->s,
1977                                  iterpool));
1978    }
1979
1980  /* Destroy the subpool. */
1981  svn_pool_destroy(iterpool);
1982
1983  return SVN_NO_ERROR;
1984}
1985
1986
1987/*----------------------------------------------------------------------*/
1988
1989/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1990
1991/* An svn_delta_editor_t function. */
1992static svn_error_t *
1993set_target_revision(void *edit_baton,
1994                    svn_revnum_t target_revision,
1995                    apr_pool_t *pool)
1996{
1997  struct edit_baton *eb = edit_baton;
1998  *(eb->target_revision) = target_revision;
1999  return SVN_NO_ERROR;
2000}
2001
2002
2003/* An svn_delta_editor_t function. */
2004static svn_error_t *
2005open_root(void *edit_baton,
2006          svn_revnum_t base_revision,
2007          apr_pool_t *pool,
2008          void **dir_baton)
2009{
2010  struct edit_baton *eb = edit_baton;
2011  eb->root_opened = TRUE;
2012  return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
2013}
2014
2015
2016/* An svn_delta_editor_t function. */
2017static svn_error_t *
2018delete_entry(const char *path,
2019             svn_revnum_t revision,
2020             void *parent_baton,
2021             apr_pool_t *pool)
2022{
2023  struct dir_baton *db = parent_baton;
2024  struct edit_baton *eb = db->edit_baton;
2025  const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
2026
2027  /* Note:  when something is deleted, it's okay to tweak the
2028     statushash immediately.  No need to wait until close_file or
2029     close_dir, because there's no risk of having to honor the 'added'
2030     flag.  We already know this item exists in the working copy. */
2031  SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, eb->wb.check_working_copy,
2032                           local_abspath,
2033                           svn_wc_status_deleted, 0, 0, revision, NULL, pool));
2034
2035  /* Mark the parent dir -- it lost an entry (unless that parent dir
2036     is the root node and we're not supposed to report on the root
2037     node).  */
2038  if (db->parent_baton && (! *eb->target_basename))
2039    SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,
2040                             eb->db, eb->wb.check_working_copy,
2041                             db->local_abspath,
2042                             svn_wc_status_modified, svn_wc_status_modified,
2043                             0, SVN_INVALID_REVNUM, NULL, pool));
2044
2045  return SVN_NO_ERROR;
2046}
2047
2048
2049/* An svn_delta_editor_t function. */
2050static svn_error_t *
2051add_directory(const char *path,
2052              void *parent_baton,
2053              const char *copyfrom_path,
2054              svn_revnum_t copyfrom_revision,
2055              apr_pool_t *pool,
2056              void **child_baton)
2057{
2058  struct dir_baton *pb = parent_baton;
2059  struct edit_baton *eb = pb->edit_baton;
2060  struct dir_baton *new_db;
2061
2062  SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
2063
2064  /* Make this dir as added. */
2065  new_db = *child_baton;
2066  new_db->added = TRUE;
2067
2068  /* Mark the parent as changed;  it gained an entry. */
2069  pb->text_changed = TRUE;
2070
2071  return SVN_NO_ERROR;
2072}
2073
2074
2075/* An svn_delta_editor_t function. */
2076static svn_error_t *
2077open_directory(const char *path,
2078               void *parent_baton,
2079               svn_revnum_t base_revision,
2080               apr_pool_t *pool,
2081               void **child_baton)
2082{
2083  struct dir_baton *pb = parent_baton;
2084  return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
2085}
2086
2087
2088/* An svn_delta_editor_t function. */
2089static svn_error_t *
2090change_dir_prop(void *dir_baton,
2091                const char *name,
2092                const svn_string_t *value,
2093                apr_pool_t *pool)
2094{
2095  struct dir_baton *db = dir_baton;
2096  if (svn_wc_is_normal_prop(name))
2097    db->prop_changed = TRUE;
2098
2099  /* Note any changes to the repository. */
2100  if (value != NULL)
2101    {
2102      if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2103        db->ood_changed_rev = SVN_STR_TO_REV(value->data);
2104      else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2105        db->ood_changed_author = apr_pstrdup(db->pool, value->data);
2106      else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2107        {
2108          apr_time_t tm;
2109          SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
2110          db->ood_changed_date = tm;
2111        }
2112    }
2113
2114  return SVN_NO_ERROR;
2115}
2116
2117
2118
2119/* An svn_delta_editor_t function. */
2120static svn_error_t *
2121close_directory(void *dir_baton,
2122                apr_pool_t *pool)
2123{
2124  struct dir_baton *db = dir_baton;
2125  struct dir_baton *pb = db->parent_baton;
2126  struct edit_baton *eb = db->edit_baton;
2127  apr_pool_t *scratch_pool = db->pool;
2128
2129  /* If nothing has changed and directory has no out of
2130     date descendants, return. */
2131  if (db->added || db->prop_changed || db->text_changed
2132      || db->ood_changed_rev != SVN_INVALID_REVNUM)
2133    {
2134      enum svn_wc_status_kind repos_node_status;
2135      enum svn_wc_status_kind repos_text_status;
2136      enum svn_wc_status_kind repos_prop_status;
2137
2138      /* If this is a new directory, add it to the statushash. */
2139      if (db->added)
2140        {
2141          repos_node_status = svn_wc_status_added;
2142          repos_text_status = svn_wc_status_none;
2143          repos_prop_status = db->prop_changed ? svn_wc_status_added
2144                              : svn_wc_status_none;
2145        }
2146      else
2147        {
2148          repos_node_status = (db->text_changed || db->prop_changed)
2149                                       ? svn_wc_status_modified
2150                                       : svn_wc_status_none;
2151          repos_text_status = db->text_changed ? svn_wc_status_modified
2152                              : svn_wc_status_none;
2153          repos_prop_status = db->prop_changed ? svn_wc_status_modified
2154                              : svn_wc_status_none;
2155        }
2156
2157      /* Maybe add this directory to its parent's status hash.  Note
2158         that tweak_statushash won't do anything if repos_text_status
2159         is not svn_wc_status_added. */
2160      if (pb)
2161        {
2162          /* ### When we add directory locking, we need to find a
2163             ### directory lock here. */
2164          SVN_ERR(tweak_statushash(pb, db, TRUE,
2165                                   eb->db,  eb->wb.check_working_copy,
2166                                   db->local_abspath,
2167                                   repos_node_status, repos_text_status,
2168                                   repos_prop_status, SVN_INVALID_REVNUM, NULL,
2169                                   scratch_pool));
2170        }
2171      else
2172        {
2173          /* We're editing the root dir of the WC.  As its repos
2174             status info isn't otherwise set, set it directly to
2175             trigger invocation of the status callback below. */
2176          eb->anchor_status->s.repos_node_status = repos_node_status;
2177          eb->anchor_status->s.repos_prop_status = repos_prop_status;
2178          eb->anchor_status->s.repos_text_status = repos_text_status;
2179
2180          /* If the root dir is out of date set the ood info directly too. */
2181          if (db->ood_changed_rev != eb->anchor_status->s.revision)
2182            {
2183              eb->anchor_status->s.ood_changed_rev = db->ood_changed_rev;
2184              eb->anchor_status->s.ood_changed_date = db->ood_changed_date;
2185              eb->anchor_status->s.ood_kind = db->ood_kind;
2186              eb->anchor_status->s.ood_changed_author =
2187                apr_pstrdup(pool, db->ood_changed_author);
2188            }
2189        }
2190    }
2191
2192  /* Handle this directory's statuses, and then note in the parent
2193     that this has been done. */
2194  if (pb && ! db->excluded)
2195    {
2196      svn_boolean_t was_deleted = FALSE;
2197      svn_wc__internal_status_t *dir_status;
2198
2199      /* See if the directory was deleted or replaced. */
2200      dir_status = svn_hash_gets(pb->statii, db->local_abspath);
2201      if (dir_status &&
2202          ((dir_status->s.repos_node_status == svn_wc_status_deleted)
2203           || (dir_status->s.repos_node_status == svn_wc_status_replaced)))
2204        was_deleted = TRUE;
2205
2206      /* Now do the status reporting. */
2207      SVN_ERR(handle_statii(eb,
2208                            dir_status ? dir_status->s.repos_root_url : NULL,
2209                            dir_status ? dir_status->s.repos_relpath : NULL,
2210                            dir_status ? dir_status->s.repos_uuid : NULL,
2211                            db->statii, was_deleted, db->depth, scratch_pool));
2212      if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
2213                                           eb->get_all))
2214        SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2215                                  &dir_status->s, scratch_pool));
2216      svn_hash_sets(pb->statii, db->local_abspath, NULL);
2217    }
2218  else if (! pb)
2219    {
2220      /* If this is the top-most directory, and the operation had a
2221         target, we should only report the target. */
2222      if (*eb->target_basename)
2223        {
2224          const svn_wc__internal_status_t *tgt_status;
2225
2226          tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
2227          if (tgt_status)
2228            {
2229              if (tgt_status->has_descendants)
2230                {
2231                  SVN_ERR(get_dir_status(&eb->wb,
2232                                         eb->target_abspath, TRUE,
2233                                         NULL, NULL, NULL, NULL,
2234                                         NULL /* dirent */,
2235                                         eb->ignores,
2236                                         eb->default_depth,
2237                                         eb->get_all, eb->no_ignore,
2238                                         eb->status_func, eb->status_baton,
2239                                         eb->cancel_func, eb->cancel_baton,
2240                                         scratch_pool));
2241                }
2242              if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
2243                SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
2244                                          &tgt_status->s, scratch_pool));
2245            }
2246        }
2247      else
2248        {
2249          /* Otherwise, we report on all our children and ourself.
2250             Note that our directory couldn't have been deleted,
2251             because it is the root of the edit drive. */
2252          SVN_ERR(handle_statii(eb,
2253                                eb->anchor_status->s.repos_root_url,
2254                                eb->anchor_status->s.repos_relpath,
2255                                eb->anchor_status->s.repos_uuid,
2256                                db->statii, FALSE, eb->default_depth,
2257                                scratch_pool));
2258          if (is_sendable_status(eb->anchor_status, eb->no_ignore,
2259                                 eb->get_all))
2260            SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2261                                      &eb->anchor_status->s, scratch_pool));
2262          eb->anchor_status = NULL;
2263        }
2264    }
2265
2266  svn_pool_clear(scratch_pool); /* Clear baton and its pool */
2267
2268  return SVN_NO_ERROR;
2269}
2270
2271
2272
2273/* An svn_delta_editor_t function. */
2274static svn_error_t *
2275add_file(const char *path,
2276         void *parent_baton,
2277         const char *copyfrom_path,
2278         svn_revnum_t copyfrom_revision,
2279         apr_pool_t *pool,
2280         void **file_baton)
2281{
2282  struct dir_baton *pb = parent_baton;
2283  struct file_baton *new_fb = make_file_baton(pb, path, pool);
2284
2285  /* Mark parent dir as changed */
2286  pb->text_changed = TRUE;
2287
2288  /* Make this file as added. */
2289  new_fb->added = TRUE;
2290
2291  *file_baton = new_fb;
2292  return SVN_NO_ERROR;
2293}
2294
2295
2296/* An svn_delta_editor_t function. */
2297static svn_error_t *
2298open_file(const char *path,
2299          void *parent_baton,
2300          svn_revnum_t base_revision,
2301          apr_pool_t *pool,
2302          void **file_baton)
2303{
2304  struct dir_baton *pb = parent_baton;
2305  struct file_baton *new_fb = make_file_baton(pb, path, pool);
2306
2307  *file_baton = new_fb;
2308  return SVN_NO_ERROR;
2309}
2310
2311
2312/* An svn_delta_editor_t function. */
2313static svn_error_t *
2314apply_textdelta(void *file_baton,
2315                const char *base_checksum,
2316                apr_pool_t *pool,
2317                svn_txdelta_window_handler_t *handler,
2318                void **handler_baton)
2319{
2320  struct file_baton *fb = file_baton;
2321
2322  /* Mark file as having textual mods. */
2323  fb->text_changed = TRUE;
2324
2325  /* Send back a NULL window handler -- we don't need the actual diffs. */
2326  *handler_baton = NULL;
2327  *handler = svn_delta_noop_window_handler;
2328
2329  return SVN_NO_ERROR;
2330}
2331
2332
2333/* An svn_delta_editor_t function. */
2334static svn_error_t *
2335change_file_prop(void *file_baton,
2336                 const char *name,
2337                 const svn_string_t *value,
2338                 apr_pool_t *pool)
2339{
2340  struct file_baton *fb = file_baton;
2341  if (svn_wc_is_normal_prop(name))
2342    fb->prop_changed = TRUE;
2343
2344  /* Note any changes to the repository. */
2345  if (value != NULL)
2346    {
2347      if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2348        fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
2349      else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2350        fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
2351                                              value->data);
2352      else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2353        {
2354          apr_time_t tm;
2355          SVN_ERR(svn_time_from_cstring(&tm, value->data,
2356                                        fb->dir_baton->pool));
2357          fb->ood_changed_date = tm;
2358        }
2359    }
2360
2361  return SVN_NO_ERROR;
2362}
2363
2364
2365/* An svn_delta_editor_t function. */
2366static svn_error_t *
2367close_file(void *file_baton,
2368           const char *text_checksum,  /* ignored, as we receive no data */
2369           apr_pool_t *pool)
2370{
2371  struct file_baton *fb = file_baton;
2372  enum svn_wc_status_kind repos_node_status;
2373  enum svn_wc_status_kind repos_text_status;
2374  enum svn_wc_status_kind repos_prop_status;
2375  const svn_lock_t *repos_lock = NULL;
2376
2377  /* If nothing has changed, return. */
2378  if (! (fb->added || fb->prop_changed || fb->text_changed))
2379    return SVN_NO_ERROR;
2380
2381  /* If this is a new file, add it to the statushash. */
2382  if (fb->added)
2383    {
2384      repos_node_status = svn_wc_status_added;
2385      repos_text_status = fb->text_changed ? svn_wc_status_modified
2386                                           : 0 /* don't tweak */;
2387      repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2388                                           : 0 /* don't tweak */;
2389
2390      if (fb->edit_baton->wb.repos_locks)
2391        {
2392          const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
2393                                                                 pool);
2394
2395          /* repos_lock still uses the deprecated filesystem absolute path
2396             format */
2397          const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
2398                                                       fb->name, pool);
2399
2400          repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
2401                                     svn_fspath__join("/", repos_relpath,
2402                                                      pool));
2403        }
2404    }
2405  else
2406    {
2407      repos_node_status = (fb->text_changed || fb->prop_changed)
2408                                 ? svn_wc_status_modified
2409                                 : 0 /* don't tweak */;
2410      repos_text_status = fb->text_changed ? svn_wc_status_modified
2411                                           : 0 /* don't tweak */;
2412      repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2413                                           : 0 /* don't tweak */;
2414    }
2415
2416  return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
2417                          fb->edit_baton->wb.check_working_copy,
2418                          fb->local_abspath, repos_node_status,
2419                          repos_text_status, repos_prop_status,
2420                          SVN_INVALID_REVNUM, repos_lock, pool);
2421}
2422
2423/* An svn_delta_editor_t function. */
2424static svn_error_t *
2425close_edit(void *edit_baton,
2426           apr_pool_t *pool)
2427{
2428  struct edit_baton *eb = edit_baton;
2429
2430  /* If we get here and the root was not opened as part of the edit,
2431     we need to transmit statuses for everything.  Otherwise, we
2432     should be done. */
2433  if (eb->root_opened)
2434    return SVN_NO_ERROR;
2435
2436  SVN_ERR(svn_wc__internal_walk_status(eb->db,
2437                                       eb->target_abspath,
2438                                       eb->default_depth,
2439                                       eb->get_all,
2440                                       eb->no_ignore,
2441                                       FALSE,
2442                                       eb->ignores,
2443                                       eb->status_func,
2444                                       eb->status_baton,
2445                                       eb->cancel_func,
2446                                       eb->cancel_baton,
2447                                       pool));
2448
2449  return SVN_NO_ERROR;
2450}
2451
2452
2453
2454/*** Public API ***/
2455
2456svn_error_t *
2457svn_wc__get_status_editor(const svn_delta_editor_t **editor,
2458                          void **edit_baton,
2459                          void **set_locks_baton,
2460                          svn_revnum_t *edit_revision,
2461                          svn_wc_context_t *wc_ctx,
2462                          const char *anchor_abspath,
2463                          const char *target_basename,
2464                          svn_depth_t depth,
2465                          svn_boolean_t get_all,
2466                          svn_boolean_t check_working_copy,
2467                          svn_boolean_t no_ignore,
2468                          svn_boolean_t depth_as_sticky,
2469                          svn_boolean_t server_performs_filtering,
2470                          const apr_array_header_t *ignore_patterns,
2471                          svn_wc_status_func4_t status_func,
2472                          void *status_baton,
2473                          svn_cancel_func_t cancel_func,
2474                          void *cancel_baton,
2475                          apr_pool_t *result_pool,
2476                          apr_pool_t *scratch_pool)
2477{
2478  struct edit_baton *eb;
2479  svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
2480  void *inner_baton;
2481  struct svn_wc__shim_fetch_baton_t *sfb;
2482  const svn_delta_editor_t *inner_editor;
2483  svn_delta_shim_callbacks_t *shim_callbacks =
2484                                svn_delta_shim_callbacks_default(result_pool);
2485
2486  /* Construct an edit baton. */
2487  eb = apr_pcalloc(result_pool, sizeof(*eb));
2488  eb->default_depth     = depth;
2489  eb->target_revision   = edit_revision;
2490  eb->db                = wc_ctx->db;
2491  eb->get_all           = get_all;
2492  eb->no_ignore         = no_ignore;
2493  eb->status_func       = status_func;
2494  eb->status_baton      = status_baton;
2495  eb->cancel_func       = cancel_func;
2496  eb->cancel_baton      = cancel_baton;
2497  eb->anchor_abspath    = apr_pstrdup(result_pool, anchor_abspath);
2498  eb->target_abspath    = svn_dirent_join(anchor_abspath, target_basename,
2499                                          result_pool);
2500
2501  eb->target_basename   = apr_pstrdup(result_pool, target_basename);
2502  eb->root_opened       = FALSE;
2503
2504  eb->wb.db               = wc_ctx->db;
2505  eb->wb.target_abspath   = eb->target_abspath;
2506  eb->wb.ignore_text_mods = !check_working_copy;
2507  eb->wb.check_working_copy = check_working_copy;
2508  eb->wb.repos_locks      = NULL;
2509  eb->wb.repos_root       = NULL;
2510
2511  SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
2512                                             wc_ctx->db, eb->target_abspath,
2513                                             result_pool, scratch_pool));
2514
2515  /* Use the caller-provided ignore patterns if provided; the build-time
2516     configured defaults otherwise. */
2517  if (ignore_patterns)
2518    {
2519      eb->ignores = ignore_patterns;
2520    }
2521  else
2522    {
2523      apr_array_header_t *ignores;
2524
2525      SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
2526      eb->ignores = ignores;
2527    }
2528
2529  /* The edit baton's status structure maps to PATH, and the editor
2530     have to be aware of whether that is the anchor or the target. */
2531  SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
2532                          check_working_copy, result_pool, scratch_pool));
2533
2534  /* Construct an editor. */
2535  tree_editor->set_target_revision = set_target_revision;
2536  tree_editor->open_root = open_root;
2537  tree_editor->delete_entry = delete_entry;
2538  tree_editor->add_directory = add_directory;
2539  tree_editor->open_directory = open_directory;
2540  tree_editor->change_dir_prop = change_dir_prop;
2541  tree_editor->close_directory = close_directory;
2542  tree_editor->add_file = add_file;
2543  tree_editor->open_file = open_file;
2544  tree_editor->apply_textdelta = apply_textdelta;
2545  tree_editor->change_file_prop = change_file_prop;
2546  tree_editor->close_file = close_file;
2547  tree_editor->close_edit = close_edit;
2548
2549  inner_editor = tree_editor;
2550  inner_baton = eb;
2551
2552  if (!server_performs_filtering
2553      && !depth_as_sticky)
2554    SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2555                                                &inner_baton,
2556                                                wc_ctx->db,
2557                                                anchor_abspath,
2558                                                target_basename,
2559                                                inner_editor,
2560                                                inner_baton,
2561                                                result_pool));
2562
2563  /* Conjoin a cancellation editor with our status editor. */
2564  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2565                                            inner_editor, inner_baton,
2566                                            editor, edit_baton,
2567                                            result_pool));
2568
2569  if (set_locks_baton)
2570    *set_locks_baton = eb;
2571
2572  sfb = apr_palloc(result_pool, sizeof(*sfb));
2573  sfb->db = wc_ctx->db;
2574  sfb->base_abspath = eb->anchor_abspath;
2575  sfb->fetch_base = FALSE;
2576
2577  shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2578  shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2579  shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2580  shim_callbacks->fetch_baton = sfb;
2581
2582  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2583                                   NULL, NULL, shim_callbacks,
2584                                   result_pool, scratch_pool));
2585
2586  return SVN_NO_ERROR;
2587}
2588
2589/* Like svn_io_stat_dirent, but works case sensitive inside working
2590   copies. Before 1.8 we handled this with a selection filter inside
2591   a directory */
2592static svn_error_t *
2593stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
2594                              svn_wc__db_t *db,
2595                              const char *local_abspath,
2596                              apr_pool_t *result_pool,
2597                              apr_pool_t *scratch_pool)
2598{
2599  svn_boolean_t is_wcroot;
2600
2601  /* The wcroot is "" inside the wc; handle it as not in the wc, as
2602     the case of the root is indifferent to us. */
2603
2604  /* Note that for performance this is really just a few hashtable lookups,
2605     as we just used local_abspath for a db call in both our callers */
2606  SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
2607                               scratch_pool));
2608
2609  return svn_error_trace(
2610            svn_io_stat_dirent2(dirent, local_abspath,
2611                                ! is_wcroot /* verify_truename */,
2612                                TRUE        /* ignore_enoent */,
2613                                result_pool, scratch_pool));
2614}
2615
2616svn_error_t *
2617svn_wc__internal_walk_status(svn_wc__db_t *db,
2618                             const char *local_abspath,
2619                             svn_depth_t depth,
2620                             svn_boolean_t get_all,
2621                             svn_boolean_t no_ignore,
2622                             svn_boolean_t ignore_text_mods,
2623                             const apr_array_header_t *ignore_patterns,
2624                             svn_wc_status_func4_t status_func,
2625                             void *status_baton,
2626                             svn_cancel_func_t cancel_func,
2627                             void *cancel_baton,
2628                             apr_pool_t *scratch_pool)
2629{
2630  struct walk_status_baton wb;
2631  const svn_io_dirent2_t *dirent;
2632  const struct svn_wc__db_info_t *info;
2633  svn_error_t *err;
2634
2635  wb.db = db;
2636  wb.target_abspath = local_abspath;
2637  wb.ignore_text_mods = ignore_text_mods;
2638  wb.check_working_copy = TRUE;
2639  wb.repos_root = NULL;
2640  wb.repos_locks = NULL;
2641
2642  /* Use the caller-provided ignore patterns if provided; the build-time
2643     configured defaults otherwise. */
2644  if (!ignore_patterns)
2645    {
2646      apr_array_header_t *ignores;
2647
2648      SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
2649      ignore_patterns = ignores;
2650    }
2651
2652  err = svn_wc__db_read_single_info(&info, db, local_abspath,
2653                                    FALSE /* base_tree_only */,
2654                                    scratch_pool, scratch_pool);
2655
2656  if (err)
2657    {
2658      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2659        {
2660          svn_error_clear(err);
2661          info = NULL;
2662        }
2663      else
2664        return svn_error_trace(err);
2665
2666      wb.externals = apr_hash_make(scratch_pool);
2667
2668      SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2669                                  scratch_pool, scratch_pool));
2670    }
2671  else
2672    {
2673      SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
2674                                                 db, local_abspath,
2675                                                 scratch_pool, scratch_pool));
2676
2677      SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2678                                            scratch_pool, scratch_pool));
2679    }
2680
2681  if (info
2682      && info->has_descendants /* is dir, or was dir and has tc descendants */
2683      && info->status != svn_wc__db_status_not_present
2684      && info->status != svn_wc__db_status_excluded
2685      && info->status != svn_wc__db_status_server_excluded)
2686    {
2687      SVN_ERR(get_dir_status(&wb,
2688                             local_abspath,
2689                             FALSE /* skip_root */,
2690                             NULL, NULL, NULL,
2691                             info,
2692                             dirent,
2693                             ignore_patterns,
2694                             depth,
2695                             get_all,
2696                             no_ignore,
2697                             status_func, status_baton,
2698                             cancel_func, cancel_baton,
2699                             scratch_pool));
2700    }
2701  else
2702    {
2703      /* It may be a file or an unversioned item. And this is an explicit
2704       * target, so no ignoring. An unversioned item (file or dir) shows a
2705       * status like '?', and can yield a tree conflicted path. */
2706      err = get_child_status(&wb,
2707                             local_abspath,
2708                             info,
2709                             dirent,
2710                             ignore_patterns,
2711                             get_all,
2712                             status_func, status_baton,
2713                             cancel_func, cancel_baton,
2714                             scratch_pool);
2715
2716      if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2717        {
2718          /* The parent is also not versioned, but it is not nice to show
2719             an error about a path a user didn't intend to touch. */
2720          svn_error_clear(err);
2721          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
2722                                   _("The node '%s' was not found."),
2723                                   svn_dirent_local_style(local_abspath,
2724                                                          scratch_pool));
2725        }
2726      SVN_ERR(err);
2727    }
2728
2729  return SVN_NO_ERROR;
2730}
2731
2732svn_error_t *
2733svn_wc_walk_status(svn_wc_context_t *wc_ctx,
2734                   const char *local_abspath,
2735                   svn_depth_t depth,
2736                   svn_boolean_t get_all,
2737                   svn_boolean_t no_ignore,
2738                   svn_boolean_t ignore_text_mods,
2739                   const apr_array_header_t *ignore_patterns,
2740                   svn_wc_status_func4_t status_func,
2741                   void *status_baton,
2742                   svn_cancel_func_t cancel_func,
2743                   void *cancel_baton,
2744                   apr_pool_t *scratch_pool)
2745{
2746  return svn_error_trace(
2747           svn_wc__internal_walk_status(wc_ctx->db,
2748                                        local_abspath,
2749                                        depth,
2750                                        get_all,
2751                                        no_ignore,
2752                                        ignore_text_mods,
2753                                        ignore_patterns,
2754                                        status_func,
2755                                        status_baton,
2756                                        cancel_func,
2757                                        cancel_baton,
2758                                        scratch_pool));
2759}
2760
2761
2762svn_error_t *
2763svn_wc_status_set_repos_locks(void *edit_baton,
2764                              apr_hash_t *locks,
2765                              const char *repos_root,
2766                              apr_pool_t *pool)
2767{
2768  struct edit_baton *eb = edit_baton;
2769
2770  eb->wb.repos_locks = locks;
2771  eb->wb.repos_root = apr_pstrdup(pool, repos_root);
2772
2773  return SVN_NO_ERROR;
2774}
2775
2776
2777svn_error_t *
2778svn_wc_get_default_ignores(apr_array_header_t **patterns,
2779                           apr_hash_t *config,
2780                           apr_pool_t *pool)
2781{
2782  svn_config_t *cfg = config
2783                      ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
2784                      : NULL;
2785  const char *val;
2786
2787  /* Check the Subversion run-time configuration for global ignores.
2788     If no configuration value exists, we fall back to our defaults. */
2789  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2790                 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2791                 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2792  *patterns = apr_array_make(pool, 16, sizeof(const char *));
2793
2794  /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2795  svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2796  return SVN_NO_ERROR;
2797}
2798
2799
2800/* */
2801static svn_error_t *
2802internal_status(svn_wc__internal_status_t **status,
2803                svn_wc__db_t *db,
2804                const char *local_abspath,
2805                svn_boolean_t check_working_copy,
2806                apr_pool_t *result_pool,
2807                apr_pool_t *scratch_pool)
2808{
2809  const svn_io_dirent2_t *dirent = NULL;
2810  const char *parent_repos_relpath;
2811  const char *parent_repos_root_url;
2812  const char *parent_repos_uuid;
2813  const struct svn_wc__db_info_t *info;
2814  svn_boolean_t is_root = FALSE;
2815  svn_error_t *err;
2816
2817  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2818
2819  err = svn_wc__db_read_single_info(&info, db, local_abspath,
2820                                    !check_working_copy,
2821                                    scratch_pool, scratch_pool);
2822
2823  if (err)
2824    {
2825      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
2826        return svn_error_trace(err);
2827
2828      svn_error_clear(err);
2829      info = NULL;
2830
2831      if (check_working_copy)
2832        SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2833                                    scratch_pool, scratch_pool));
2834    }
2835  else if (check_working_copy)
2836    SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2837                                          scratch_pool, scratch_pool));
2838
2839  if (!info
2840      || info->kind == svn_node_unknown
2841      || info->status == svn_wc__db_status_not_present
2842      || info->status == svn_wc__db_status_server_excluded
2843      || info->status == svn_wc__db_status_excluded)
2844    return svn_error_trace(assemble_unversioned(status,
2845                                                db, local_abspath,
2846                                                dirent,
2847                                                info ? info->conflicted : FALSE,
2848                                                FALSE /* is_ignored */,
2849                                                result_pool, scratch_pool));
2850
2851  if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
2852    is_root = TRUE;
2853  else
2854    SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
2855
2856  /* Even though passing parent_repos_* is not required, assemble_status needs
2857     these values to determine if a node is switched */
2858  if (!is_root)
2859    {
2860      const char *const parent_abspath = svn_dirent_dirname(local_abspath,
2861                                                            scratch_pool);
2862      if (check_working_copy)
2863        SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL,
2864                                     &parent_repos_relpath,
2865                                     &parent_repos_root_url,
2866                                     &parent_repos_uuid,
2867                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2868                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2869                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2870                                     db, parent_abspath,
2871                                     result_pool, scratch_pool));
2872      else
2873        SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL,
2874                                         &parent_repos_relpath,
2875                                         &parent_repos_root_url,
2876                                         &parent_repos_uuid,
2877                                         NULL, NULL, NULL, NULL, NULL,
2878                                         NULL, NULL, NULL, NULL, NULL,
2879                                         db, parent_abspath,
2880                                         result_pool, scratch_pool));
2881    }
2882  else
2883    {
2884      parent_repos_root_url = NULL;
2885      parent_repos_relpath = NULL;
2886      parent_repos_uuid = NULL;
2887    }
2888
2889  return svn_error_trace(assemble_status(status, db, local_abspath,
2890                                         parent_repos_root_url,
2891                                         parent_repos_relpath,
2892                                         parent_repos_uuid,
2893                                         info,
2894                                         dirent,
2895                                         TRUE /* get_all */,
2896                                         FALSE, check_working_copy,
2897                                         NULL /* repos_lock */,
2898                                         result_pool, scratch_pool));
2899}
2900
2901
2902svn_error_t *
2903svn_wc_status3(svn_wc_status3_t **status,
2904               svn_wc_context_t *wc_ctx,
2905               const char *local_abspath,
2906               apr_pool_t *result_pool,
2907               apr_pool_t *scratch_pool)
2908{
2909  svn_wc__internal_status_t *stat;
2910  SVN_ERR(internal_status(&stat, wc_ctx->db, local_abspath,
2911                          TRUE /* check_working_copy */,
2912                          result_pool, scratch_pool));
2913  *status = &stat->s;
2914  return SVN_NO_ERROR;
2915}
2916
2917svn_wc_status3_t *
2918svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
2919                   apr_pool_t *pool)
2920{
2921  /* Allocate slightly more room */
2922  svn_wc__internal_status_t *new_istat = apr_palloc(pool, sizeof(*new_istat));
2923  svn_wc_status3_t *new_stat = &new_istat->s;
2924
2925  /* Shallow copy all members. */
2926  *new_stat = *orig_stat;
2927
2928  /* Now go back and dup the deep items into this pool. */
2929  if (orig_stat->repos_lock)
2930    new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2931
2932  if (orig_stat->changed_author)
2933    new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
2934
2935  if (orig_stat->ood_changed_author)
2936    new_stat->ood_changed_author
2937      = apr_pstrdup(pool, orig_stat->ood_changed_author);
2938
2939  if (orig_stat->lock)
2940    new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
2941
2942  if (orig_stat->changelist)
2943    new_stat->changelist
2944      = apr_pstrdup(pool, orig_stat->changelist);
2945
2946  if (orig_stat->repos_root_url)
2947    new_stat->repos_root_url
2948      = apr_pstrdup(pool, orig_stat->repos_root_url);
2949
2950  if (orig_stat->repos_relpath)
2951    new_stat->repos_relpath
2952      = apr_pstrdup(pool, orig_stat->repos_relpath);
2953
2954  if (orig_stat->repos_uuid)
2955    new_stat->repos_uuid
2956      = apr_pstrdup(pool, orig_stat->repos_uuid);
2957
2958  if (orig_stat->moved_from_abspath)
2959    new_stat->moved_from_abspath
2960      = apr_pstrdup(pool, orig_stat->moved_from_abspath);
2961
2962  if (orig_stat->moved_to_abspath)
2963    new_stat->moved_to_abspath
2964      = apr_pstrdup(pool, orig_stat->moved_to_abspath);
2965
2966  /* Return the new hotness. */
2967  return new_stat;
2968}
2969
2970svn_error_t *
2971svn_wc_get_ignores2(apr_array_header_t **patterns,
2972                    svn_wc_context_t *wc_ctx,
2973                    const char *local_abspath,
2974                    apr_hash_t *config,
2975                    apr_pool_t *result_pool,
2976                    apr_pool_t *scratch_pool)
2977{
2978  apr_array_header_t *default_ignores;
2979
2980  SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
2981  return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
2982                                                 local_abspath,
2983                                                 default_ignores,
2984                                                 result_pool, scratch_pool));
2985}
2986