]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_wc/status.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_wc / status.c
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 \f
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 \f
59
60 /*** Baton used for walking the local status */
61 struct 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
86 struct 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
140 struct 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
200 struct 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. */
251 static svn_error_t *
252 get_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
332 static svn_error_t *
333 internal_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 */
357 static svn_error_t *
358 assemble_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  */
716 static svn_error_t *
717 assemble_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().  */
781 static svn_error_t *
782 send_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 */
851 static svn_error_t *
852 collect_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.  */
926 static svn_boolean_t
927 is_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 */
970 static svn_error_t *
971 send_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
1014 static svn_error_t *
1015 get_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(). */
1064 static svn_error_t *
1065 one_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().  */
1194 static svn_error_t *
1195 get_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(). */
1378 static svn_error_t *
1379 get_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 \f
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. */
1450 static svn_error_t *
1451 hash_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. */
1495 static svn_error_t *
1496 tweak_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 */
1634 static const char *
1635 find_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 \f
1659 /* Create a new dir_baton for subdir PATH. */
1660 static svn_error_t *
1661 make_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. */
1773 static struct file_baton *
1774 make_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  */
1803 static svn_boolean_t
1804 is_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. */
1855 struct 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 */
1865 static svn_error_t *
1866 mark_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. */
1885 static svn_error_t *
1886 handle_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 \f
1952 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1953
1954 /* An svn_delta_editor_t function. */
1955 static svn_error_t *
1956 set_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. */
1967 static svn_error_t *
1968 open_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. */
1980 static svn_error_t *
1981 delete_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. */
2012 static svn_error_t *
2013 add_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. */
2038 static svn_error_t *
2039 open_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. */
2051 static svn_error_t *
2052 change_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. */
2082 static svn_error_t *
2083 close_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. */
2235 static svn_error_t *
2236 add_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. */
2258 static svn_error_t *
2259 open_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. */
2274 static svn_error_t *
2275 apply_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. */
2295 static svn_error_t *
2296 change_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. */
2327 static svn_error_t *
2328 close_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. */
2384 static svn_error_t *
2385 close_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 \f
2414 /*** Public API ***/
2415
2416 svn_error_t *
2417 svn_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 */
2551 static svn_error_t *
2552 stat_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
2575 svn_error_t *
2576 svn_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
2689 svn_error_t *
2690 svn_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
2719 svn_error_t *
2720 svn_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
2734 svn_error_t *
2735 svn_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 /* */
2758 static svn_error_t *
2759 internal_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
2867 svn_error_t *
2868 svn_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
2879 svn_wc_status3_t *
2880 svn_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
2930 svn_error_t *
2931 svn_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 }