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