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