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