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