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