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