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