2 * status.c: construct a status structure from an entry structure
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
29 #include <apr_pools.h>
30 #include <apr_file_io.h>
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"
41 #include "svn_config.h"
44 #include "svn_sorts.h"
46 #include "svn_private_config.h"
51 #include "translate.h"
52 #include "tree_conflicts.h"
54 #include "private/svn_wc_private.h"
55 #include "private/svn_fspath.h"
56 #include "private/svn_editor.h"
60 /*** Baton used for walking the local status */
61 struct walk_status_baton
63 /* The DB handle for managing the working copy state. */
66 /*** External handling ***/
67 /* Target of the status */
68 const char *target_abspath;
70 /* Should we ignore text modifications? */
71 svn_boolean_t ignore_text_mods;
73 /* Externals info harvested during the status run. */
74 apr_hash_t *externals;
76 /*** Repository lock handling ***/
77 /* The repository root URL, if set. */
78 const char *repos_root;
80 /* Repository locks, if set. */
81 apr_hash_t *repos_locks;
84 /*** Editor batons ***/
88 /* For status, the "destination" of the edit. */
89 const char *anchor_abspath;
90 const char *target_abspath;
91 const char *target_basename;
93 /* The DB handle for managing the working copy state. */
95 svn_wc_context_t *wc_ctx;
97 /* The overall depth of this edit (a dir baton may override this).
99 * If this is svn_depth_unknown, the depths found in the working
100 * copy will govern the edit; or if the edit depth indicates a
101 * descent deeper than the found depths are capable of, the found
102 * depths also govern, of course (there's no point descending into
103 * something that's not there).
105 svn_depth_t default_depth;
107 /* Do we want all statuses (instead of just the interesting ones) ? */
108 svn_boolean_t get_all;
110 /* Ignore the svn:ignores. */
111 svn_boolean_t no_ignore;
113 /* The comparison revision in the repository. This is a reference
114 because this editor returns this rev to the driver directly, as
115 well as in each statushash entry. */
116 svn_revnum_t *target_revision;
118 /* Status function/baton. */
119 svn_wc_status_func4_t status_func;
122 /* Cancellation function/baton. */
123 svn_cancel_func_t cancel_func;
126 /* The configured set of default ignores. */
127 const apr_array_header_t *ignores;
129 /* Status item for the path represented by the anchor of the edit. */
130 svn_wc_status3_t *anchor_status;
132 /* Was open_root() called for this edit drive? */
133 svn_boolean_t root_opened;
135 /* The local status baton */
136 struct walk_status_baton wb;
142 /* The path to this directory. */
143 const char *local_abspath;
145 /* Basename of this directory. */
148 /* The global edit baton. */
149 struct edit_baton *edit_baton;
151 /* Baton for this directory's parent, or NULL if this is the root
153 struct dir_baton *parent_baton;
155 /* The ambient requested depth below this point in the edit. This
156 can differ from the parent baton's depth (with the edit baton
157 considered the ultimate parent baton). For example, if the
158 parent baton has svn_depth_immediates, then here we should have
159 svn_depth_empty, because there would be no further recursion, not
160 even to file children. */
163 /* Is this directory filtered out due to depth? (Note that if this
164 is TRUE, the depth field is undefined.) */
165 svn_boolean_t excluded;
167 /* 'svn status' shouldn't print status lines for things that are
168 added; we're only interest in asking if objects that the user
169 *already* has are up-to-date or not. Thus if this flag is set,
170 the next two will be ignored. :-) */
173 /* Gets set iff there's a change to this directory's properties, to
174 guide us when syncing adm files later. */
175 svn_boolean_t prop_changed;
177 /* This means (in terms of 'svn status') that some child was deleted
178 or added to the directory */
179 svn_boolean_t text_changed;
181 /* Working copy status structures for children of this directory.
182 This hash maps const char * abspaths to svn_wc_status3_t *
186 /* The pool in which this baton itself is allocated. */
189 /* The repository root relative path to this item in the repository. */
190 const char *repos_relpath;
192 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
193 svn_node_kind_t ood_kind;
194 svn_revnum_t ood_changed_rev;
195 apr_time_t ood_changed_date;
196 const char *ood_changed_author;
202 /* Absolute local path to this file */
203 const char *local_abspath;
205 /* The global edit baton. */
206 struct edit_baton *edit_baton;
208 /* Baton for this file's parent directory. */
209 struct dir_baton *dir_baton;
211 /* Pool specific to this file_baton. */
214 /* Basename of this file */
217 /* 'svn status' shouldn't print status lines for things that are
218 added; we're only interest in asking if objects that the user
219 *already* has are up-to-date or not. Thus if this flag is set,
220 the next two will be ignored. :-) */
223 /* This gets set if the file underwent a text change, which guides
224 the code that syncs up the adm dir and working copy. */
225 svn_boolean_t text_changed;
227 /* This gets set if the file underwent a prop change, which guides
228 the code that syncs up the adm dir and working copy. */
229 svn_boolean_t prop_changed;
231 /* The repository root relative path to this item in the repository. */
232 const char *repos_relpath;
234 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
235 svn_node_kind_t ood_kind;
236 svn_revnum_t ood_changed_rev;
237 apr_time_t ood_changed_date;
239 const char *ood_changed_author;
247 /* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
248 information in INFO if available, falling back on
249 PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
250 finally falling back on querying DB. */
252 get_repos_root_url_relpath(const char **repos_relpath,
253 const char **repos_root_url,
254 const char **repos_uuid,
255 const struct svn_wc__db_info_t *info,
256 const char *parent_repos_relpath,
257 const char *parent_repos_root_url,
258 const char *parent_repos_uuid,
260 const char *local_abspath,
261 apr_pool_t *result_pool,
262 apr_pool_t *scratch_pool)
264 if (info->repos_relpath && info->repos_root_url)
266 *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
267 *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
268 *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
270 else if (parent_repos_relpath && parent_repos_root_url)
272 *repos_relpath = svn_relpath_join(parent_repos_relpath,
273 svn_dirent_basename(local_abspath,
276 *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
277 *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
279 else if (info->status == svn_wc__db_status_added)
281 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
282 repos_relpath, repos_root_url,
283 repos_uuid, NULL, NULL, NULL, NULL,
285 result_pool, scratch_pool));
287 else if (info->status == svn_wc__db_status_deleted
288 && !info->have_more_work
291 SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url,
294 result_pool, scratch_pool));
296 else if (info->status == svn_wc__db_status_deleted)
298 const char *work_del_abspath;
299 const char *add_abspath;
301 /* Handles working DELETE and the special case where there is just
302 svn_wc__db_status_not_present in WORKING */
304 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, &work_del_abspath, NULL,
306 scratch_pool, scratch_pool));
308 /* The parent of what has been deleted must be added */
309 add_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool);
311 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath,
312 repos_root_url, repos_uuid, NULL,
315 result_pool, scratch_pool));
317 *repos_relpath = svn_relpath_join(*repos_relpath,
318 svn_dirent_skip_ancestor(
325 *repos_relpath = NULL;
326 *repos_root_url = NULL;
333 internal_status(svn_wc_status3_t **status,
335 const char *local_abspath,
336 apr_pool_t *result_pool,
337 apr_pool_t *scratch_pool);
339 /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
340 RESULT_POOL and use SCRATCH_POOL for temporary allocations.
342 PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
343 and repository relative path of the parent of LOCAL_ABSPATH or NULL if
344 LOCAL_ABSPATH doesn't have a versioned parent directory.
346 DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
347 NULL if the node does not exist on disk.
349 If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
350 *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
351 allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
352 don't check for text mods, assume there are none and set and *STATUS
353 returned to reflect that assumption.
355 The status struct's repos_lock field will be set to REPOS_LOCK.
358 assemble_status(svn_wc_status3_t **status,
360 const char *local_abspath,
361 const char *parent_repos_root_url,
362 const char *parent_repos_relpath,
363 const char *parent_repos_uuid,
364 const struct svn_wc__db_info_t *info,
365 const svn_io_dirent2_t *dirent,
366 svn_boolean_t get_all,
367 svn_boolean_t ignore_text_mods,
368 const svn_lock_t *repos_lock,
369 apr_pool_t *result_pool,
370 apr_pool_t *scratch_pool)
372 svn_wc_status3_t *stat;
373 svn_boolean_t switched_p = FALSE;
374 svn_boolean_t copied = FALSE;
375 svn_boolean_t conflicted;
376 const char *moved_from_abspath = NULL;
377 svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file))
379 : SVN_INVALID_FILESIZE;
381 /* Defaults for two main variables. */
382 enum svn_wc_status_kind node_status = svn_wc_status_normal;
383 enum svn_wc_status_kind text_status = svn_wc_status_normal;
384 enum svn_wc_status_kind prop_status = svn_wc_status_none;
388 SVN_ERR(svn_wc__db_read_single_info(&info, db, local_abspath,
389 result_pool, scratch_pool));
391 if (!info->repos_relpath || !parent_repos_relpath)
395 /* A node is switched if it doesn't have the implied repos_relpath */
396 const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
397 info->repos_relpath);
398 switched_p = !name || (strcmp(name,
399 svn_dirent_basename(local_abspath, NULL))
403 if (info->status == svn_wc__db_status_incomplete || info->incomplete)
405 /* Highest precedence. */
406 node_status = svn_wc_status_incomplete;
408 else if (info->status == svn_wc__db_status_deleted)
410 node_status = svn_wc_status_deleted;
412 if (!info->have_base || info->have_more_work || info->copied)
414 else if (!info->have_more_work && info->have_base)
418 const char *work_del_abspath;
420 /* Find out details of our deletion. */
421 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
422 &work_del_abspath, NULL,
424 scratch_pool, scratch_pool));
425 if (work_del_abspath)
426 copied = TRUE; /* Working deletion */
431 /* Examine whether our target is missing or obstructed. To detect
432 * obstructions, we have to look at the on-disk status in DIRENT. */
433 svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
437 if (!dirent || dirent->kind != expected_kind)
439 /* A present or added node should be on disk, so it is
440 reported missing or obstructed. */
441 if (!dirent || dirent->kind == svn_node_none)
442 node_status = svn_wc_status_missing;
444 node_status = svn_wc_status_obstructed;
448 /* Does the node have props? */
449 if (info->status != svn_wc__db_status_deleted)
452 prop_status = svn_wc_status_modified;
453 else if (info->had_props)
454 prop_status = svn_wc_status_normal;
457 /* If NODE_STATUS is still normal, after the above checks, then
458 we should proceed to refine the status.
460 If it was changed, then the subdir is incomplete or missing/obstructed.
462 if (info->kind != svn_node_dir
463 && node_status == svn_wc_status_normal)
465 svn_boolean_t text_modified_p = FALSE;
467 /* Implement predecence rules: */
469 /* 1. Set the two main variables to "discovered" values first (M, C).
470 Together, these two stati are of lowest precedence, and C has
471 precedence over M. */
473 /* If the entry is a file, check for textual modifications */
474 if ((info->kind == svn_node_file
475 || info->kind == svn_node_symlink)
477 && (info->special == (dirent && dirent->special))
478 #endif /* HAVE_SYMLINK */
481 /* If the on-disk dirent exactly matches the expected state
482 skip all operations in svn_wc__internal_text_modified_p()
483 to avoid an extra filestat for every file, which can be
484 expensive on network drives as a filestat usually can't
486 if (!info->has_checksum)
487 text_modified_p = TRUE; /* Local addition -> Modified */
488 else if (ignore_text_mods
490 && info->recorded_size != SVN_INVALID_FILESIZE
491 && info->recorded_time != 0
492 && info->recorded_size == dirent->filesize
493 && info->recorded_time == dirent->mtime))
494 text_modified_p = FALSE;
498 err = svn_wc__internal_file_modified_p(&text_modified_p,
500 FALSE, scratch_pool);
504 if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
505 return svn_error_trace(err);
507 /* An access denied is very common on Windows when another
508 application has the file open. Previously we ignored
509 this error in svn_wc__text_modified_internal_p, where it
510 should have really errored. */
511 svn_error_clear(err);
512 text_modified_p = TRUE;
517 else if (info->special != (dirent && dirent->special))
518 node_status = svn_wc_status_obstructed;
519 #endif /* HAVE_SYMLINK */
522 text_status = svn_wc_status_modified;
525 conflicted = info->conflicted;
528 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
530 /* ### Check if the conflict was resolved by removing the marker files.
531 ### This should really be moved to the users of this API */
532 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
534 db, local_abspath, scratch_pool));
536 if (!text_conflicted && !prop_conflicted && !tree_conflicted)
540 if (node_status == svn_wc_status_normal)
542 /* 2. Possibly overwrite the text_status variable with "scheduled"
543 states from the entry (A, D, R). As a group, these states are
544 of medium precedence. They also override any C or M that may
545 be in the prop_status field at this point, although they do not
546 override a C text status.*/
547 if (info->status == svn_wc__db_status_added)
549 copied = info->copied;
551 { /* Keep status normal */ }
552 else if (!info->have_base && !info->have_more_work)
554 /* Simple addition or copy, no replacement */
555 node_status = svn_wc_status_added;
559 svn_wc__db_status_t below_working;
560 svn_boolean_t have_base, have_work;
562 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
567 /* If the node is not present or deleted (read: not present
568 in working), then the node is not a replacement */
569 if (below_working != svn_wc__db_status_not_present
570 && below_working != svn_wc__db_status_deleted)
572 node_status = svn_wc_status_replaced;
575 node_status = svn_wc_status_added;
578 /* Get moved-from info (only for potential op-roots of a move). */
579 if (info->moved_here && info->op_root)
582 err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
584 result_pool, scratch_pool);
588 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
589 return svn_error_trace(err);
591 svn_error_clear(err);
592 /* We are no longer moved... So most likely we are somehow
593 changing the db for things like resolving conflicts. */
595 moved_from_abspath = NULL;
602 if (node_status == svn_wc_status_normal)
603 node_status = text_status;
605 if (node_status == svn_wc_status_normal
606 && prop_status != svn_wc_status_none)
607 node_status = prop_status;
609 /* 5. Easy out: unless we're fetching -every- entry, don't bother
610 to allocate a struct for an uninteresting entry. */
613 if (((node_status == svn_wc_status_none)
614 || (node_status == svn_wc_status_normal))
620 && (! info->changelist)
627 /* 6. Build and return a status structure. */
629 stat = apr_pcalloc(result_pool, sizeof(**status));
634 stat->kind = svn_node_dir;
637 case svn_node_symlink:
638 stat->kind = svn_node_file;
640 case svn_node_unknown:
642 stat->kind = svn_node_unknown;
644 stat->depth = info->depth;
645 stat->filesize = filesize;
646 stat->node_status = node_status;
647 stat->text_status = text_status;
648 stat->prop_status = prop_status;
649 stat->repos_node_status = svn_wc_status_none; /* default */
650 stat->repos_text_status = svn_wc_status_none; /* default */
651 stat->repos_prop_status = svn_wc_status_none; /* default */
652 stat->switched = switched_p;
653 stat->copied = copied;
654 stat->repos_lock = repos_lock;
655 stat->revision = info->revnum;
656 stat->changed_rev = info->changed_rev;
657 if (info->changed_author)
658 stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
659 stat->changed_date = info->changed_date;
661 stat->ood_kind = svn_node_none;
662 stat->ood_changed_rev = SVN_INVALID_REVNUM;
663 stat->ood_changed_date = 0;
664 stat->ood_changed_author = NULL;
666 SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
667 &stat->repos_root_url,
668 &stat->repos_uuid, info,
669 parent_repos_relpath,
670 parent_repos_root_url,
673 result_pool, scratch_pool));
677 svn_lock_t *lck = svn_lock_create(result_pool);
678 lck->path = stat->repos_relpath;
679 lck->token = info->lock->token;
680 lck->owner = info->lock->owner;
681 lck->comment = info->lock->comment;
682 lck->creation_date = info->lock->date;
688 stat->locked = info->locked;
689 stat->conflicted = conflicted;
690 stat->versioned = TRUE;
691 if (info->changelist)
692 stat->changelist = apr_pstrdup(result_pool, info->changelist);
694 stat->moved_from_abspath = moved_from_abspath;
696 /* ### TODO: Handle multiple moved_to values properly */
698 stat->moved_to_abspath = apr_pstrdup(result_pool,
699 info->moved_to->moved_to_abspath);
701 stat->file_external = info->file_external;
708 /* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
709 available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
710 temporary allocations.
712 If IS_IGNORED is non-zero and this is a non-versioned entity, set
713 the node_status to svn_wc_status_none. Otherwise set the
714 node_status to svn_wc_status_unversioned.
717 assemble_unversioned(svn_wc_status3_t **status,
719 const char *local_abspath,
720 const svn_io_dirent2_t *dirent,
721 svn_boolean_t tree_conflicted,
722 svn_boolean_t is_ignored,
723 apr_pool_t *result_pool,
724 apr_pool_t *scratch_pool)
726 svn_wc_status3_t *stat;
728 /* return a fairly blank structure. */
729 stat = apr_pcalloc(result_pool, sizeof(*stat));
731 /*stat->versioned = FALSE;*/
732 stat->kind = svn_node_unknown; /* not versioned */
733 stat->depth = svn_depth_unknown;
734 stat->filesize = (dirent && dirent->kind == svn_node_file)
736 : SVN_INVALID_FILESIZE;
737 stat->node_status = svn_wc_status_none;
738 stat->text_status = svn_wc_status_none;
739 stat->prop_status = svn_wc_status_none;
740 stat->repos_node_status = svn_wc_status_none;
741 stat->repos_text_status = svn_wc_status_none;
742 stat->repos_prop_status = svn_wc_status_none;
744 /* If this path has no entry, but IS present on disk, it's
745 unversioned. If this file is being explicitly ignored (due
746 to matching an ignore-pattern), the node_status is set to
747 svn_wc_status_ignored. Otherwise the node_status is set to
748 svn_wc_status_unversioned. */
749 if (dirent && dirent->kind != svn_node_none)
752 stat->node_status = svn_wc_status_ignored;
754 stat->node_status = svn_wc_status_unversioned;
756 else if (tree_conflicted)
758 /* If this path has no entry, is NOT present on disk, and IS a
759 tree conflict victim, report it as conflicted. */
760 stat->node_status = svn_wc_status_conflicted;
763 stat->revision = SVN_INVALID_REVNUM;
764 stat->changed_rev = SVN_INVALID_REVNUM;
765 stat->ood_changed_rev = SVN_INVALID_REVNUM;
766 stat->ood_kind = svn_node_none;
768 /* For the case of an incoming delete to a locally deleted path during
769 an update, we get a tree conflict. */
770 stat->conflicted = tree_conflicted;
771 stat->changelist = NULL;
778 /* Given an ENTRY object representing PATH, build a status structure
779 and pass it off to the STATUS_FUNC/STATUS_BATON. All other
780 arguments are the same as those passed to assemble_status(). */
782 send_status_structure(const struct walk_status_baton *wb,
783 const char *local_abspath,
784 const char *parent_repos_root_url,
785 const char *parent_repos_relpath,
786 const char *parent_repos_uuid,
787 const struct svn_wc__db_info_t *info,
788 const svn_io_dirent2_t *dirent,
789 svn_boolean_t get_all,
790 svn_wc_status_func4_t status_func,
792 apr_pool_t *scratch_pool)
794 svn_wc_status3_t *statstruct;
795 const svn_lock_t *repos_lock = NULL;
797 /* Check for a repository lock. */
800 const char *repos_relpath, *repos_root_url, *repos_uuid;
802 SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
804 info, parent_repos_relpath,
805 parent_repos_root_url,
807 wb->db, local_abspath,
808 scratch_pool, scratch_pool));
811 /* repos_lock still uses the deprecated filesystem absolute path
813 repos_lock = svn_hash_gets(wb->repos_locks,
814 svn_fspath__join("/", repos_relpath,
819 SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
820 parent_repos_root_url, parent_repos_relpath,
822 info, dirent, get_all, wb->ignore_text_mods,
823 repos_lock, scratch_pool, scratch_pool));
825 if (statstruct && status_func)
826 return svn_error_trace((*status_func)(status_baton, local_abspath,
827 statstruct, scratch_pool));
833 /* Store in *PATTERNS a list of ignores collected from svn:ignore properties
834 on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
835 repository ancestors (as cached in the working copy), including the default
836 ignores passed in as IGNORES.
838 Upon return, *PATTERNS will contain zero or more (const char *)
839 patterns from the value of the SVN_PROP_IGNORE property set on
840 the working directory path.
842 IGNORES is a list of patterns to include; typically this will
843 be the default ignores as, for example, specified in a config file.
845 DB, LOCAL_ABSPATH is used to access the working copy.
847 Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
849 None of the arguments may be NULL.
852 collect_ignore_patterns(apr_array_header_t **patterns,
854 const char *local_abspath,
855 const apr_array_header_t *ignores,
856 apr_pool_t *result_pool,
857 apr_pool_t *scratch_pool)
861 apr_array_header_t *inherited_props;
864 /* ### assert we are passed a directory? */
866 *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
868 /* Copy default ignores into the local PATTERNS array. */
869 for (i = 0; i < ignores->nelts; i++)
871 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
872 APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
876 err = svn_wc__db_read_inherited_props(&inherited_props, &props,
878 SVN_PROP_INHERITABLE_IGNORES,
879 scratch_pool, scratch_pool);
883 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
884 return svn_error_trace(err);
886 svn_error_clear(err);
892 const svn_string_t *value;
894 value = svn_hash_gets(props, SVN_PROP_IGNORE);
896 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
899 value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
901 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
905 for (i = 0; i < inherited_props->nelts; i++)
907 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
908 inherited_props, i, svn_prop_inherited_item_t *);
909 const svn_string_t *value;
911 value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
914 svn_cstring_split_append(*patterns, value->data,
915 "\n\r", FALSE, result_pool);
922 /* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
923 LOCAL_ABSPATH is the drop location for, or an intermediate directory
924 of the drop location for, an externals definition. Use SCRATCH_POOL
927 is_external_path(apr_hash_t *externals,
928 const char *local_abspath,
929 apr_pool_t *scratch_pool)
931 apr_hash_index_t *hi;
933 /* First try: does the path exist as a key in the hash? */
934 if (svn_hash_gets(externals, local_abspath))
937 /* Failing that, we need to check if any external is a child of
939 for (hi = apr_hash_first(scratch_pool, externals);
941 hi = apr_hash_next(hi))
943 const char *external_abspath = svn__apr_hash_index_key(hi);
945 if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
953 /* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
954 for it through STATUS_FUNC/STATUS_BATON unless this path is being
955 ignored. This function should never be called on a versioned entry.
957 LOCAL_ABSPATH is the path to the unversioned file whose status is being
958 requested. PATH_KIND is the node kind of NAME as determined by the
959 caller. PATH_SPECIAL is the special status of the path, also determined
961 PATTERNS points to a list of filename patterns which are marked as ignored.
962 None of these parameter may be NULL.
964 If NO_IGNORE is TRUE, the item will be added regardless of
965 whether it is ignored; otherwise we will only add the item if it
966 does not match any of the patterns in PATTERN or INHERITED_IGNORES.
968 Allocate everything in POOL.
971 send_unversioned_item(const struct walk_status_baton *wb,
972 const char *local_abspath,
973 const svn_io_dirent2_t *dirent,
974 svn_boolean_t tree_conflicted,
975 const apr_array_header_t *patterns,
976 svn_boolean_t no_ignore,
977 svn_wc_status_func4_t status_func,
979 apr_pool_t *scratch_pool)
981 svn_boolean_t is_ignored;
982 svn_boolean_t is_external;
983 svn_wc_status3_t *status;
984 const char *base_name = svn_dirent_basename(local_abspath, NULL);
986 is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
987 SVN_ERR(assemble_unversioned(&status,
988 wb->db, local_abspath,
989 dirent, tree_conflicted,
991 scratch_pool, scratch_pool));
993 is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
995 status->node_status = svn_wc_status_external;
997 /* We can have a tree conflict on an unversioned path, i.e. an incoming
998 * delete on a locally deleted path during an update. Don't ever ignore
1000 if (status->conflicted)
1003 /* If we aren't ignoring it, or if it's an externals path, pass this
1004 entry to the status func. */
1008 return svn_error_trace((*status_func)(status_baton, local_abspath,
1009 status, scratch_pool));
1011 return SVN_NO_ERROR;
1014 static svn_error_t *
1015 get_dir_status(const struct walk_status_baton *wb,
1016 const char *local_abspath,
1017 svn_boolean_t skip_this_dir,
1018 const char *parent_repos_root_url,
1019 const char *parent_repos_relpath,
1020 const char *parent_repos_uuid,
1021 const struct svn_wc__db_info_t *dir_info,
1022 const svn_io_dirent2_t *dirent,
1023 const apr_array_header_t *ignore_patterns,
1025 svn_boolean_t get_all,
1026 svn_boolean_t no_ignore,
1027 svn_wc_status_func4_t status_func,
1029 svn_cancel_func_t cancel_func,
1031 apr_pool_t *scratch_pool);
1033 /* Send out a status structure according to the information gathered on one
1034 * child node. (Basically this function is the guts of the loop in
1035 * get_dir_status() and of get_child_status().)
1037 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
1038 * dirname of LOCAL_ABSPATH.
1040 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
1041 * be an unversioned file or dir, or a versioned file. For versioned
1042 * directories use get_dir_status() instead.
1044 * INFO may be NULL for an unversioned node. If such node has a tree conflict,
1045 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
1046 * UNVERSIONED_TREE_CONFLICTED is ignored.
1048 * DIRENT should reflect LOCAL_ABSPATH's dirent information.
1050 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
1051 * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
1053 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
1054 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
1055 * containing all ignore patterns, as returned by collect_ignore_patterns() on
1056 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
1057 * non-NULL, it is assumed it already holds those results.
1058 * This speeds up repeated calls with the same PARENT_ABSPATH.
1060 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
1061 * allocations are made in SCRATCH_POOL.
1063 * The remaining parameters correspond to get_dir_status(). */
1064 static svn_error_t *
1065 one_child_status(const struct walk_status_baton *wb,
1066 const char *local_abspath,
1067 const char *parent_abspath,
1068 const struct svn_wc__db_info_t *info,
1069 const svn_io_dirent2_t *dirent,
1070 const char *dir_repos_root_url,
1071 const char *dir_repos_relpath,
1072 const char *dir_repos_uuid,
1073 svn_boolean_t unversioned_tree_conflicted,
1074 apr_array_header_t **collected_ignore_patterns,
1075 const apr_array_header_t *ignore_patterns,
1077 svn_boolean_t get_all,
1078 svn_boolean_t no_ignore,
1079 svn_wc_status_func4_t status_func,
1081 svn_cancel_func_t cancel_func,
1083 apr_pool_t *result_pool,
1084 apr_pool_t *scratch_pool)
1086 svn_boolean_t conflicted = info ? info->conflicted
1087 : unversioned_tree_conflicted;
1090 && info->status != svn_wc__db_status_not_present
1091 && info->status != svn_wc__db_status_excluded
1092 && info->status != svn_wc__db_status_server_excluded
1093 && !(info->kind == svn_node_unknown
1094 && info->status == svn_wc__db_status_normal))
1096 if (depth == svn_depth_files
1097 && info->kind == svn_node_dir)
1099 return SVN_NO_ERROR;
1102 SVN_ERR(send_status_structure(wb, local_abspath,
1106 info, dirent, get_all,
1107 status_func, status_baton,
1110 /* Descend in subdirectories. */
1111 if (depth == svn_depth_infinity
1112 && info->kind == svn_node_dir)
1114 SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
1115 dir_repos_root_url, dir_repos_relpath,
1116 dir_repos_uuid, info,
1117 dirent, ignore_patterns,
1118 svn_depth_infinity, get_all,
1120 status_func, status_baton,
1121 cancel_func, cancel_baton,
1125 return SVN_NO_ERROR;
1128 /* If conflicted, fall right through to unversioned.
1129 * With depth_files, show all conflicts, even if their report is only
1130 * about directories. A tree conflict may actually report two different
1131 * kinds, so it's not so easy to define what depth=files means. We could go
1132 * look up the kinds in the conflict ... just show all. */
1135 /* Selected node, but not found */
1137 return SVN_NO_ERROR;
1139 if (depth == svn_depth_files && dirent->kind == svn_node_dir)
1140 return SVN_NO_ERROR;
1142 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
1144 return SVN_NO_ERROR;
1147 /* The node exists on disk but there is no versioned information about it,
1148 * or it doesn't exist but is a tree conflicted path or should be
1149 * reported not-present. */
1151 /* Why pass ignore patterns on a tree conflicted node, even if it should
1152 * always show up in clients' status reports anyway? Because the calling
1153 * client decides whether to ignore, and thus this flag needs to be
1154 * determined. For example, in 'svn status', plain unversioned nodes show
1155 * as '? C', where ignored ones show as 'I C'. */
1157 if (ignore_patterns && ! *collected_ignore_patterns)
1158 SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
1159 wb->db, parent_abspath, ignore_patterns,
1160 result_pool, scratch_pool));
1162 SVN_ERR(send_unversioned_item(wb,
1166 *collected_ignore_patterns,
1168 status_func, status_baton,
1171 return SVN_NO_ERROR;
1174 /* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
1175 for all its child nodes (according to DEPTH) through STATUS_FUNC /
1178 If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
1179 All subdirs reached by recursion will be reported regardless of this
1182 PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
1183 URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
1184 retrieving them again. Otherwise they must be NULL.
1186 DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
1187 it again. Otherwise it must be NULL.
1189 DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
1190 so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
1192 Other arguments are the same as those passed to
1193 svn_wc_get_status_editor5(). */
1194 static svn_error_t *
1195 get_dir_status(const struct walk_status_baton *wb,
1196 const char *local_abspath,
1197 svn_boolean_t skip_this_dir,
1198 const char *parent_repos_root_url,
1199 const char *parent_repos_relpath,
1200 const char *parent_repos_uuid,
1201 const struct svn_wc__db_info_t *dir_info,
1202 const svn_io_dirent2_t *dirent,
1203 const apr_array_header_t *ignore_patterns,
1205 svn_boolean_t get_all,
1206 svn_boolean_t no_ignore,
1207 svn_wc_status_func4_t status_func,
1209 svn_cancel_func_t cancel_func,
1211 apr_pool_t *scratch_pool)
1213 const char *dir_repos_root_url;
1214 const char *dir_repos_relpath;
1215 const char *dir_repos_uuid;
1216 apr_hash_t *dirents, *nodes, *conflicts, *all_children;
1217 apr_array_header_t *sorted_children;
1218 apr_array_header_t *collected_ignore_patterns = NULL;
1219 apr_pool_t *iterpool;
1224 SVN_ERR(cancel_func(cancel_baton));
1226 if (depth == svn_depth_unknown)
1227 depth = svn_depth_infinity;
1229 iterpool = svn_pool_create(scratch_pool);
1231 err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool,
1234 && (APR_STATUS_IS_ENOENT(err->apr_err)
1235 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
1237 svn_error_clear(err);
1238 dirents = apr_hash_make(scratch_pool);
1244 SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath,
1245 scratch_pool, iterpool));
1247 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1248 &dir_repos_uuid, dir_info,
1249 parent_repos_relpath,
1250 parent_repos_root_url, parent_repos_uuid,
1251 wb->db, local_abspath,
1252 scratch_pool, iterpool));
1254 /* Create a hash containing all children. The source hashes
1255 don't all map the same types, but only the keys of the result
1256 hash are subsequently used. */
1257 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1258 wb->db, local_abspath,
1259 scratch_pool, iterpool));
1261 all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
1262 if (apr_hash_count(conflicts) > 0)
1263 all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
1265 /* Handle "this-dir" first. */
1266 if (! skip_this_dir)
1268 /* This code is not conditional on HAVE_SYMLINK as some systems that do
1269 not allow creating symlinks (!HAVE_SYMLINK) can still encounter
1270 symlinks (or in case of Windows also 'Junctions') created by other
1273 Without this block a working copy in the root of a junction is
1274 reported as an obstruction, because the junction itself is reported as
1277 Systems that have no symlink support at all, would always see
1278 dirent->special as FALSE, so even there enabling this code shouldn't
1281 if (dirent->special)
1283 svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
1285 /* We're being pointed to "this-dir" via a symlink.
1286 * Get the real node kind and pretend the path is not a symlink.
1287 * This prevents send_status_structure() from treating this-dir
1288 * as a directory obstructed by a file. */
1289 SVN_ERR(svn_io_check_resolved_path(local_abspath,
1290 &this_dirent->kind, iterpool));
1291 this_dirent->special = FALSE;
1292 SVN_ERR(send_status_structure(wb, local_abspath,
1293 parent_repos_root_url,
1294 parent_repos_relpath,
1296 dir_info, this_dirent, get_all,
1297 status_func, status_baton,
1301 SVN_ERR(send_status_structure(wb, local_abspath,
1302 parent_repos_root_url,
1303 parent_repos_relpath,
1305 dir_info, dirent, get_all,
1306 status_func, status_baton,
1310 /* If the requested depth is empty, we only need status on this-dir. */
1311 if (depth == svn_depth_empty)
1312 return SVN_NO_ERROR;
1314 /* Walk all the children of this directory. */
1315 sorted_children = svn_sort__hash(all_children,
1316 svn_sort_compare_items_lexically,
1318 for (i = 0; i < sorted_children->nelts; i++)
1322 svn_sort__item_t item;
1323 const char *child_abspath;
1324 svn_io_dirent2_t *child_dirent;
1325 const struct svn_wc__db_info_t *child_info;
1327 svn_pool_clear(iterpool);
1329 item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
1333 child_abspath = svn_dirent_join(local_abspath, key, iterpool);
1334 child_dirent = apr_hash_get(dirents, key, klen);
1335 child_info = apr_hash_get(nodes, key, klen);
1337 SVN_ERR(one_child_status(wb,
1345 apr_hash_get(conflicts, key, klen) != NULL,
1346 &collected_ignore_patterns,
1359 /* Destroy our subpools. */
1360 svn_pool_destroy(iterpool);
1362 return SVN_NO_ERROR;
1365 /* Send an svn_wc_status3_t * structure for the versioned file, or for the
1366 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
1367 * explicit target). Does not recurse.
1369 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
1370 * unversioned nodes. An unversioned and tree-conflicted node however should
1371 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
1373 * DIRENT should reflect LOCAL_ABSPATH.
1375 * All allocations made in SCRATCH_POOL.
1377 * The remaining parameters correspond to get_dir_status(). */
1378 static svn_error_t *
1379 get_child_status(const struct walk_status_baton *wb,
1380 const char *local_abspath,
1381 const struct svn_wc__db_info_t *info,
1382 const svn_io_dirent2_t *dirent,
1383 const apr_array_header_t *ignore_patterns,
1384 svn_boolean_t get_all,
1385 svn_wc_status_func4_t status_func,
1387 svn_cancel_func_t cancel_func,
1389 apr_pool_t *scratch_pool)
1391 const char *dir_repos_root_url;
1392 const char *dir_repos_relpath;
1393 const char *dir_repos_uuid;
1394 const struct svn_wc__db_info_t *dir_info;
1395 apr_array_header_t *collected_ignore_patterns = NULL;
1396 const char *parent_abspath = svn_dirent_dirname(local_abspath,
1400 SVN_ERR(cancel_func(cancel_baton));
1402 if (dirent->kind == svn_node_none)
1405 SVN_ERR(svn_wc__db_read_single_info(&dir_info,
1406 wb->db, parent_abspath,
1407 scratch_pool, scratch_pool));
1409 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1410 &dir_repos_uuid, dir_info,
1412 wb->db, parent_abspath,
1413 scratch_pool, scratch_pool));
1415 /* An unversioned node with a tree conflict will see an INFO != NULL here,
1416 * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
1417 * effect and INFO->CONFLICTED counts.
1418 * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
1420 SVN_ERR(one_child_status(wb,
1428 FALSE, /* unversioned_tree_conflicted */
1429 &collected_ignore_patterns,
1433 TRUE, /* no_ignore. This is an explicit target. */
1440 return SVN_NO_ERROR;
1447 /* A faux status callback function for stashing STATUS item in an hash
1448 (which is the BATON), keyed on PATH. This implements the
1449 svn_wc_status_func4_t interface. */
1450 static svn_error_t *
1451 hash_stash(void *baton,
1453 const svn_wc_status3_t *status,
1454 apr_pool_t *scratch_pool)
1456 apr_hash_t *stat_hash = baton;
1457 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1458 assert(! svn_hash_gets(stat_hash, path));
1459 svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path),
1460 svn_wc_dup_status3(status, hash_pool));
1462 return SVN_NO_ERROR;
1466 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1467 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1468 yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
1469 create a new status struct using the hash's pool.
1471 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1472 of date (ood) information we want to set in BATON. This is necessary
1473 because this function tweaks the status of out-of-date directories
1474 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1475 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1476 contains the ood info we want to bubble up to ancestor directories so these
1477 accurately reflect the fact they have an ood descendant.
1479 Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
1480 status structure's "network" fields.
1482 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1485 If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
1486 optionally the revision path was deleted, in all other cases it must
1487 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1488 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1489 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1490 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
1491 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1492 ood_last_cmt_rev value - see comment below.
1494 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1495 static svn_error_t *
1496 tweak_statushash(void *baton,
1497 void *this_dir_baton,
1498 svn_boolean_t is_dir_baton,
1500 const char *local_abspath,
1501 enum svn_wc_status_kind repos_node_status,
1502 enum svn_wc_status_kind repos_text_status,
1503 enum svn_wc_status_kind repos_prop_status,
1504 svn_revnum_t deleted_rev,
1505 const svn_lock_t *repos_lock,
1506 apr_pool_t *scratch_pool)
1508 svn_wc_status3_t *statstruct;
1510 apr_hash_t *statushash;
1513 statushash = ((struct dir_baton *) baton)->statii;
1515 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1516 pool = apr_hash_pool_get(statushash);
1518 /* Is PATH already a hash-key? */
1519 statstruct = svn_hash_gets(statushash, local_abspath);
1521 /* If not, make it so. */
1524 /* If this item isn't being added, then we're most likely
1525 dealing with a non-recursive (or at least partially
1526 non-recursive) working copy. Due to bugs in how the client
1527 reports the state of non-recursive working copies, the
1528 repository can send back responses about paths that don't
1529 even exist locally. Our best course here is just to ignore
1530 those responses. After all, if the client had reported
1531 correctly in the first, that path would either be mentioned
1532 as an 'add' or not mentioned at all, depending on how we
1533 eventually fix the bugs in non-recursivity. See issue
1534 #2122 for details. */
1535 if (repos_node_status != svn_wc_status_added)
1536 return SVN_NO_ERROR;
1538 /* Use the public API to get a statstruct, and put it into the hash. */
1539 SVN_ERR(internal_status(&statstruct, db, local_abspath, pool,
1541 statstruct->repos_lock = repos_lock;
1542 svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
1545 /* Merge a repos "delete" + "add" into a single "replace". */
1546 if ((repos_node_status == svn_wc_status_added)
1547 && (statstruct->repos_node_status == svn_wc_status_deleted))
1548 repos_node_status = svn_wc_status_replaced;
1550 /* Tweak the structure's repos fields. */
1551 if (repos_node_status)
1552 statstruct->repos_node_status = repos_node_status;
1553 if (repos_text_status)
1554 statstruct->repos_text_status = repos_text_status;
1555 if (repos_prop_status)
1556 statstruct->repos_prop_status = repos_prop_status;
1558 /* Copy out-of-date info. */
1561 struct dir_baton *b = this_dir_baton;
1563 if (!statstruct->repos_relpath && b->repos_relpath)
1565 if (statstruct->repos_node_status == svn_wc_status_deleted)
1567 /* When deleting PATH, BATON is for PATH's parent,
1568 so we must construct PATH's real statstruct->url. */
1569 statstruct->repos_relpath =
1570 svn_relpath_join(b->repos_relpath,
1571 svn_dirent_basename(local_abspath,
1576 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1578 statstruct->repos_root_url =
1579 b->edit_baton->anchor_status->repos_root_url;
1580 statstruct->repos_uuid =
1581 b->edit_baton->anchor_status->repos_uuid;
1584 /* The last committed date, and author for deleted items
1586 if (statstruct->repos_node_status == svn_wc_status_deleted)
1588 statstruct->ood_kind = statstruct->kind;
1590 /* Pre 1.5 servers don't provide the revision a path was deleted.
1591 So we punt and use the last committed revision of the path's
1592 parent, which has some chance of being correct. At worse it
1593 is a higher revision than the path was deleted, but this is
1594 better than nothing... */
1595 if (deleted_rev == SVN_INVALID_REVNUM)
1596 statstruct->ood_changed_rev =
1597 ((struct dir_baton *) baton)->ood_changed_rev;
1599 statstruct->ood_changed_rev = deleted_rev;
1603 statstruct->ood_kind = b->ood_kind;
1604 statstruct->ood_changed_rev = b->ood_changed_rev;
1605 statstruct->ood_changed_date = b->ood_changed_date;
1606 if (b->ood_changed_author)
1607 statstruct->ood_changed_author =
1608 apr_pstrdup(pool, b->ood_changed_author);
1614 struct file_baton *b = baton;
1615 statstruct->ood_changed_rev = b->ood_changed_rev;
1616 statstruct->ood_changed_date = b->ood_changed_date;
1617 if (!statstruct->repos_relpath && b->repos_relpath)
1619 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1620 statstruct->repos_root_url =
1621 b->edit_baton->anchor_status->repos_root_url;
1622 statstruct->repos_uuid =
1623 b->edit_baton->anchor_status->repos_uuid;
1625 statstruct->ood_kind = b->ood_kind;
1626 if (b->ood_changed_author)
1627 statstruct->ood_changed_author =
1628 apr_pstrdup(pool, b->ood_changed_author);
1630 return SVN_NO_ERROR;
1633 /* Returns the URL for DB */
1635 find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
1637 /* If we have no name, we're the root, return the anchor URL. */
1639 return db->edit_baton->anchor_status->repos_relpath;
1642 const char *repos_relpath;
1643 struct dir_baton *pb = db->parent_baton;
1644 const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
1646 /* Note that status->repos_relpath could be NULL in the case of a missing
1647 * directory, which means we need to recurse up another level to get
1648 * a useful relpath. */
1649 if (status && status->repos_relpath)
1650 return status->repos_relpath;
1652 repos_relpath = find_dir_repos_relpath(pb, pool);
1653 return svn_relpath_join(repos_relpath, db->name, pool);
1659 /* Create a new dir_baton for subdir PATH. */
1660 static svn_error_t *
1661 make_dir_baton(void **dir_baton,
1663 struct edit_baton *edit_baton,
1664 struct dir_baton *parent_baton,
1665 apr_pool_t *result_pool)
1667 struct dir_baton *pb = parent_baton;
1668 struct edit_baton *eb = edit_baton;
1669 struct dir_baton *d;
1670 const char *local_abspath;
1671 const svn_wc_status3_t *status_in_parent;
1672 apr_pool_t *dir_pool;
1675 dir_pool = svn_pool_create(parent_baton->pool);
1677 dir_pool = svn_pool_create(result_pool);
1679 d = apr_pcalloc(dir_pool, sizeof(*d));
1681 SVN_ERR_ASSERT(path || (! pb));
1683 /* Construct the absolute path of this directory. */
1685 local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
1687 local_abspath = eb->anchor_abspath;
1689 /* Finish populating the baton members. */
1691 d->local_abspath = local_abspath;
1692 d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
1693 d->edit_baton = edit_baton;
1694 d->parent_baton = parent_baton;
1695 d->statii = apr_hash_make(dir_pool);
1696 d->ood_changed_rev = SVN_INVALID_REVNUM;
1697 d->ood_changed_date = 0;
1698 d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
1699 d->ood_kind = svn_node_dir;
1700 d->ood_changed_author = NULL;
1706 else if (pb->depth == svn_depth_immediates)
1707 d->depth = svn_depth_empty;
1708 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1710 else if (pb->depth == svn_depth_unknown)
1711 /* This is only tentative, it can be overridden from d's entry
1713 d->depth = svn_depth_unknown;
1715 d->depth = svn_depth_infinity;
1719 d->depth = eb->default_depth;
1722 /* Get the status for this path's children. Of course, we only want
1723 to do this if the path is versioned as a directory. */
1725 status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
1727 status_in_parent = eb->anchor_status;
1729 if (status_in_parent
1730 && status_in_parent->versioned
1731 && (status_in_parent->kind == svn_node_dir)
1733 && (d->depth == svn_depth_unknown
1734 || d->depth == svn_depth_infinity
1735 || d->depth == svn_depth_files
1736 || d->depth == svn_depth_immediates)
1739 const svn_wc_status3_t *this_dir_status;
1740 const apr_array_header_t *ignores = eb->ignores;
1742 SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
1743 status_in_parent->repos_root_url,
1744 NULL /*parent_repos_relpath*/,
1745 status_in_parent->repos_uuid,
1747 NULL /* dirent */, ignores,
1748 d->depth == svn_depth_files
1750 : svn_depth_immediates,
1752 hash_stash, d->statii,
1753 eb->cancel_func, eb->cancel_baton,
1756 /* If we found a depth here, it should govern. */
1757 this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
1758 if (this_dir_status && this_dir_status->versioned
1759 && (d->depth == svn_depth_unknown
1760 || d->depth > status_in_parent->depth))
1762 d->depth = this_dir_status->depth;
1767 return SVN_NO_ERROR;
1771 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1772 NAME is just one component, not a path. */
1773 static struct file_baton *
1774 make_file_baton(struct dir_baton *parent_dir_baton,
1778 struct dir_baton *pb = parent_dir_baton;
1779 struct edit_baton *eb = pb->edit_baton;
1780 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1782 /* Finish populating the baton members. */
1783 f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1784 f->name = svn_dirent_basename(f->local_abspath, NULL);
1788 f->ood_changed_rev = SVN_INVALID_REVNUM;
1789 f->ood_changed_date = 0;
1790 f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
1792 f->ood_kind = svn_node_file;
1793 f->ood_changed_author = NULL;
1799 * Return a boolean answer to the question "Is @a status something that
1800 * should be reported?". @a no_ignore and @a get_all are the same as
1801 * svn_wc_get_status_editor4().
1803 static svn_boolean_t
1804 is_sendable_status(const svn_wc_status3_t *status,
1805 svn_boolean_t no_ignore,
1806 svn_boolean_t get_all)
1808 /* If the repository status was touched at all, it's interesting. */
1809 if (status->repos_node_status != svn_wc_status_none)
1812 /* If there is a lock in the repository, send it. */
1813 if (status->repos_lock)
1816 if (status->conflicted)
1819 /* If the item is ignored, and we don't want ignores, skip it. */
1820 if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
1823 /* If we want everything, we obviously want this single-item subset
1828 /* If the item is unversioned, display it. */
1829 if (status->node_status == svn_wc_status_unversioned)
1832 /* If the text, property or tree state is interesting, send it. */
1833 if ((status->node_status != svn_wc_status_none
1834 && (status->node_status != svn_wc_status_normal)))
1837 /* If it's switched, send it. */
1838 if (status->switched)
1841 /* If there is a lock token, send it. */
1842 if (status->versioned && status->lock)
1845 /* If the entry is associated with a changelist, send it. */
1846 if (status->changelist)
1849 /* Otherwise, don't send it. */
1854 /* Baton for mark_status. */
1857 svn_wc_status_func4_t real_status_func; /* real status function */
1858 void *real_status_baton; /* real status baton */
1861 /* A status callback function which wraps the *real* status
1862 function/baton. It simply sets the "repos_node_status" field of the
1863 STATUS to svn_wc_status_deleted and passes it off to the real
1864 status func/baton. Implements svn_wc_status_func4_t */
1865 static svn_error_t *
1866 mark_deleted(void *baton,
1867 const char *local_abspath,
1868 const svn_wc_status3_t *status,
1869 apr_pool_t *scratch_pool)
1871 struct status_baton *sb = baton;
1872 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
1873 new_status->repos_node_status = svn_wc_status_deleted;
1874 return sb->real_status_func(sb->real_status_baton, local_abspath,
1875 new_status, scratch_pool);
1879 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1880 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1881 directory itself. Descend into subdirectories according to DEPTH.
1882 Also, if DIR_WAS_DELETED is set, each status that is reported
1883 through this function will have its repos_text_status field showing
1884 a deletion. Use POOL for all allocations. */
1885 static svn_error_t *
1886 handle_statii(struct edit_baton *eb,
1887 const char *dir_repos_root_url,
1888 const char *dir_repos_relpath,
1889 const char *dir_repos_uuid,
1891 svn_boolean_t dir_was_deleted,
1895 const apr_array_header_t *ignores = eb->ignores;
1896 apr_hash_index_t *hi;
1897 apr_pool_t *iterpool = svn_pool_create(pool);
1898 svn_wc_status_func4_t status_func = eb->status_func;
1899 void *status_baton = eb->status_baton;
1900 struct status_baton sb;
1902 if (dir_was_deleted)
1904 sb.real_status_func = eb->status_func;
1905 sb.real_status_baton = eb->status_baton;
1906 status_func = mark_deleted;
1910 /* Loop over all the statii still in our hash, handling each one. */
1911 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1913 const char *local_abspath = svn__apr_hash_index_key(hi);
1914 svn_wc_status3_t *status = svn__apr_hash_index_val(hi);
1916 /* Clear the subpool. */
1917 svn_pool_clear(iterpool);
1919 /* Now, handle the status. We don't recurse for svn_depth_immediates
1920 because we already have the subdirectories' statii. */
1921 if (status->versioned && status->kind == svn_node_dir
1922 && (depth == svn_depth_unknown
1923 || depth == svn_depth_infinity))
1925 SVN_ERR(get_dir_status(&eb->wb,
1926 local_abspath, TRUE,
1927 dir_repos_root_url, dir_repos_relpath,
1931 ignores, depth, eb->get_all, eb->no_ignore,
1932 status_func, status_baton,
1933 eb->cancel_func, eb->cancel_baton,
1936 if (dir_was_deleted)
1937 status->repos_node_status = svn_wc_status_deleted;
1938 if (is_sendable_status(status, eb->no_ignore, eb->get_all))
1939 SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status,
1943 /* Destroy the subpool. */
1944 svn_pool_destroy(iterpool);
1946 return SVN_NO_ERROR;
1950 /*----------------------------------------------------------------------*/
1952 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1954 /* An svn_delta_editor_t function. */
1955 static svn_error_t *
1956 set_target_revision(void *edit_baton,
1957 svn_revnum_t target_revision,
1960 struct edit_baton *eb = edit_baton;
1961 *(eb->target_revision) = target_revision;
1962 return SVN_NO_ERROR;
1966 /* An svn_delta_editor_t function. */
1967 static svn_error_t *
1968 open_root(void *edit_baton,
1969 svn_revnum_t base_revision,
1973 struct edit_baton *eb = edit_baton;
1974 eb->root_opened = TRUE;
1975 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
1979 /* An svn_delta_editor_t function. */
1980 static svn_error_t *
1981 delete_entry(const char *path,
1982 svn_revnum_t revision,
1986 struct dir_baton *db = parent_baton;
1987 struct edit_baton *eb = db->edit_baton;
1988 const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1990 /* Note: when something is deleted, it's okay to tweak the
1991 statushash immediately. No need to wait until close_file or
1992 close_dir, because there's no risk of having to honor the 'added'
1993 flag. We already know this item exists in the working copy. */
1994 SVN_ERR(tweak_statushash(db, db, TRUE, eb->db,
1996 svn_wc_status_deleted, 0, 0, revision, NULL, pool));
1998 /* Mark the parent dir -- it lost an entry (unless that parent dir
1999 is the root node and we're not supposed to report on the root
2001 if (db->parent_baton && (! *eb->target_basename))
2002 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db,
2004 svn_wc_status_modified, svn_wc_status_modified,
2005 0, SVN_INVALID_REVNUM, NULL, pool));
2007 return SVN_NO_ERROR;
2011 /* An svn_delta_editor_t function. */
2012 static svn_error_t *
2013 add_directory(const char *path,
2015 const char *copyfrom_path,
2016 svn_revnum_t copyfrom_revision,
2020 struct dir_baton *pb = parent_baton;
2021 struct edit_baton *eb = pb->edit_baton;
2022 struct dir_baton *new_db;
2024 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
2026 /* Make this dir as added. */
2027 new_db = *child_baton;
2028 new_db->added = TRUE;
2030 /* Mark the parent as changed; it gained an entry. */
2031 pb->text_changed = TRUE;
2033 return SVN_NO_ERROR;
2037 /* An svn_delta_editor_t function. */
2038 static svn_error_t *
2039 open_directory(const char *path,
2041 svn_revnum_t base_revision,
2045 struct dir_baton *pb = parent_baton;
2046 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
2050 /* An svn_delta_editor_t function. */
2051 static svn_error_t *
2052 change_dir_prop(void *dir_baton,
2054 const svn_string_t *value,
2057 struct dir_baton *db = dir_baton;
2058 if (svn_wc_is_normal_prop(name))
2059 db->prop_changed = TRUE;
2061 /* Note any changes to the repository. */
2064 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2065 db->ood_changed_rev = SVN_STR_TO_REV(value->data);
2066 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2067 db->ood_changed_author = apr_pstrdup(db->pool, value->data);
2068 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2071 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
2072 db->ood_changed_date = tm;
2076 return SVN_NO_ERROR;
2081 /* An svn_delta_editor_t function. */
2082 static svn_error_t *
2083 close_directory(void *dir_baton,
2086 struct dir_baton *db = dir_baton;
2087 struct dir_baton *pb = db->parent_baton;
2088 struct edit_baton *eb = db->edit_baton;
2089 apr_pool_t *scratch_pool = db->pool;
2091 /* If nothing has changed and directory has no out of
2092 date descendants, return. */
2093 if (db->added || db->prop_changed || db->text_changed
2094 || db->ood_changed_rev != SVN_INVALID_REVNUM)
2096 enum svn_wc_status_kind repos_node_status;
2097 enum svn_wc_status_kind repos_text_status;
2098 enum svn_wc_status_kind repos_prop_status;
2100 /* If this is a new directory, add it to the statushash. */
2103 repos_node_status = svn_wc_status_added;
2104 repos_text_status = svn_wc_status_none;
2105 repos_prop_status = db->prop_changed ? svn_wc_status_added
2106 : svn_wc_status_none;
2110 repos_node_status = (db->text_changed || db->prop_changed)
2111 ? svn_wc_status_modified
2112 : svn_wc_status_none;
2113 repos_text_status = db->text_changed ? svn_wc_status_modified
2114 : svn_wc_status_none;
2115 repos_prop_status = db->prop_changed ? svn_wc_status_modified
2116 : svn_wc_status_none;
2119 /* Maybe add this directory to its parent's status hash. Note
2120 that tweak_statushash won't do anything if repos_text_status
2121 is not svn_wc_status_added. */
2124 /* ### When we add directory locking, we need to find a
2125 ### directory lock here. */
2126 SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath,
2127 repos_node_status, repos_text_status,
2128 repos_prop_status, SVN_INVALID_REVNUM, NULL,
2133 /* We're editing the root dir of the WC. As its repos
2134 status info isn't otherwise set, set it directly to
2135 trigger invocation of the status callback below. */
2136 eb->anchor_status->repos_node_status = repos_node_status;
2137 eb->anchor_status->repos_prop_status = repos_prop_status;
2138 eb->anchor_status->repos_text_status = repos_text_status;
2140 /* If the root dir is out of date set the ood info directly too. */
2141 if (db->ood_changed_rev != eb->anchor_status->revision)
2143 eb->anchor_status->ood_changed_rev = db->ood_changed_rev;
2144 eb->anchor_status->ood_changed_date = db->ood_changed_date;
2145 eb->anchor_status->ood_kind = db->ood_kind;
2146 eb->anchor_status->ood_changed_author =
2147 apr_pstrdup(pool, db->ood_changed_author);
2152 /* Handle this directory's statuses, and then note in the parent
2153 that this has been done. */
2154 if (pb && ! db->excluded)
2156 svn_boolean_t was_deleted = FALSE;
2157 const svn_wc_status3_t *dir_status;
2159 /* See if the directory was deleted or replaced. */
2160 dir_status = svn_hash_gets(pb->statii, db->local_abspath);
2162 ((dir_status->repos_node_status == svn_wc_status_deleted)
2163 || (dir_status->repos_node_status == svn_wc_status_replaced)))
2166 /* Now do the status reporting. */
2167 SVN_ERR(handle_statii(eb,
2168 dir_status ? dir_status->repos_root_url : NULL,
2169 dir_status ? dir_status->repos_relpath : NULL,
2170 dir_status ? dir_status->repos_uuid : NULL,
2171 db->statii, was_deleted, db->depth, scratch_pool));
2172 if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
2174 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2175 dir_status, scratch_pool));
2176 svn_hash_sets(pb->statii, db->local_abspath, NULL);
2180 /* If this is the top-most directory, and the operation had a
2181 target, we should only report the target. */
2182 if (*eb->target_basename)
2184 const svn_wc_status3_t *tgt_status;
2186 tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
2189 if (tgt_status->versioned
2190 && tgt_status->kind == svn_node_dir)
2192 SVN_ERR(get_dir_status(&eb->wb,
2193 eb->target_abspath, TRUE,
2194 NULL, NULL, NULL, NULL,
2198 eb->get_all, eb->no_ignore,
2199 eb->status_func, eb->status_baton,
2200 eb->cancel_func, eb->cancel_baton,
2203 if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
2204 SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
2205 tgt_status, scratch_pool));
2210 /* Otherwise, we report on all our children and ourself.
2211 Note that our directory couldn't have been deleted,
2212 because it is the root of the edit drive. */
2213 SVN_ERR(handle_statii(eb,
2214 eb->anchor_status->repos_root_url,
2215 eb->anchor_status->repos_relpath,
2216 eb->anchor_status->repos_uuid,
2217 db->statii, FALSE, eb->default_depth,
2219 if (is_sendable_status(eb->anchor_status, eb->no_ignore,
2221 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2222 eb->anchor_status, scratch_pool));
2223 eb->anchor_status = NULL;
2227 svn_pool_clear(scratch_pool); /* Clear baton and its pool */
2229 return SVN_NO_ERROR;
2234 /* An svn_delta_editor_t function. */
2235 static svn_error_t *
2236 add_file(const char *path,
2238 const char *copyfrom_path,
2239 svn_revnum_t copyfrom_revision,
2243 struct dir_baton *pb = parent_baton;
2244 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2246 /* Mark parent dir as changed */
2247 pb->text_changed = TRUE;
2249 /* Make this file as added. */
2250 new_fb->added = TRUE;
2252 *file_baton = new_fb;
2253 return SVN_NO_ERROR;
2257 /* An svn_delta_editor_t function. */
2258 static svn_error_t *
2259 open_file(const char *path,
2261 svn_revnum_t base_revision,
2265 struct dir_baton *pb = parent_baton;
2266 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2268 *file_baton = new_fb;
2269 return SVN_NO_ERROR;
2273 /* An svn_delta_editor_t function. */
2274 static svn_error_t *
2275 apply_textdelta(void *file_baton,
2276 const char *base_checksum,
2278 svn_txdelta_window_handler_t *handler,
2279 void **handler_baton)
2281 struct file_baton *fb = file_baton;
2283 /* Mark file as having textual mods. */
2284 fb->text_changed = TRUE;
2286 /* Send back a NULL window handler -- we don't need the actual diffs. */
2287 *handler_baton = NULL;
2288 *handler = svn_delta_noop_window_handler;
2290 return SVN_NO_ERROR;
2294 /* An svn_delta_editor_t function. */
2295 static svn_error_t *
2296 change_file_prop(void *file_baton,
2298 const svn_string_t *value,
2301 struct file_baton *fb = file_baton;
2302 if (svn_wc_is_normal_prop(name))
2303 fb->prop_changed = TRUE;
2305 /* Note any changes to the repository. */
2308 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2309 fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
2310 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2311 fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
2313 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2316 SVN_ERR(svn_time_from_cstring(&tm, value->data,
2317 fb->dir_baton->pool));
2318 fb->ood_changed_date = tm;
2322 return SVN_NO_ERROR;
2326 /* An svn_delta_editor_t function. */
2327 static svn_error_t *
2328 close_file(void *file_baton,
2329 const char *text_checksum, /* ignored, as we receive no data */
2332 struct file_baton *fb = file_baton;
2333 enum svn_wc_status_kind repos_node_status;
2334 enum svn_wc_status_kind repos_text_status;
2335 enum svn_wc_status_kind repos_prop_status;
2336 const svn_lock_t *repos_lock = NULL;
2338 /* If nothing has changed, return. */
2339 if (! (fb->added || fb->prop_changed || fb->text_changed))
2340 return SVN_NO_ERROR;
2342 /* If this is a new file, add it to the statushash. */
2345 repos_node_status = svn_wc_status_added;
2346 repos_text_status = fb->text_changed ? svn_wc_status_modified
2347 : 0 /* don't tweak */;
2348 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2349 : 0 /* don't tweak */;
2351 if (fb->edit_baton->wb.repos_locks)
2353 const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
2356 /* repos_lock still uses the deprecated filesystem absolute path
2358 const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
2361 repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
2362 svn_fspath__join("/", repos_relpath,
2368 repos_node_status = (fb->text_changed || fb->prop_changed)
2369 ? svn_wc_status_modified
2370 : 0 /* don't tweak */;
2371 repos_text_status = fb->text_changed ? svn_wc_status_modified
2372 : 0 /* don't tweak */;
2373 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2374 : 0 /* don't tweak */;
2377 return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
2378 fb->local_abspath, repos_node_status,
2379 repos_text_status, repos_prop_status,
2380 SVN_INVALID_REVNUM, repos_lock, pool);
2383 /* An svn_delta_editor_t function. */
2384 static svn_error_t *
2385 close_edit(void *edit_baton,
2388 struct edit_baton *eb = edit_baton;
2390 /* If we get here and the root was not opened as part of the edit,
2391 we need to transmit statuses for everything. Otherwise, we
2393 if (eb->root_opened)
2394 return SVN_NO_ERROR;
2396 SVN_ERR(svn_wc_walk_status(eb->wc_ctx,
2409 return SVN_NO_ERROR;
2414 /*** Public API ***/
2417 svn_wc__get_status_editor(const svn_delta_editor_t **editor,
2419 void **set_locks_baton,
2420 svn_revnum_t *edit_revision,
2421 svn_wc_context_t *wc_ctx,
2422 const char *anchor_abspath,
2423 const char *target_basename,
2425 svn_boolean_t get_all,
2426 svn_boolean_t no_ignore,
2427 svn_boolean_t depth_as_sticky,
2428 svn_boolean_t server_performs_filtering,
2429 const apr_array_header_t *ignore_patterns,
2430 svn_wc_status_func4_t status_func,
2432 svn_cancel_func_t cancel_func,
2434 apr_pool_t *result_pool,
2435 apr_pool_t *scratch_pool)
2437 struct edit_baton *eb;
2438 svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
2440 struct svn_wc__shim_fetch_baton_t *sfb;
2441 const svn_delta_editor_t *inner_editor;
2442 svn_delta_shim_callbacks_t *shim_callbacks =
2443 svn_delta_shim_callbacks_default(result_pool);
2445 /* Construct an edit baton. */
2446 eb = apr_pcalloc(result_pool, sizeof(*eb));
2447 eb->default_depth = depth;
2448 eb->target_revision = edit_revision;
2449 eb->db = wc_ctx->db;
2450 eb->wc_ctx = wc_ctx;
2451 eb->get_all = get_all;
2452 eb->no_ignore = no_ignore;
2453 eb->status_func = status_func;
2454 eb->status_baton = status_baton;
2455 eb->cancel_func = cancel_func;
2456 eb->cancel_baton = cancel_baton;
2457 eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
2458 eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
2461 eb->target_basename = apr_pstrdup(result_pool, target_basename);
2462 eb->root_opened = FALSE;
2464 eb->wb.db = wc_ctx->db;
2465 eb->wb.target_abspath = eb->target_abspath;
2466 eb->wb.ignore_text_mods = FALSE;
2467 eb->wb.repos_locks = NULL;
2468 eb->wb.repos_root = NULL;
2470 SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
2471 wc_ctx->db, eb->target_abspath,
2472 result_pool, scratch_pool));
2474 /* Use the caller-provided ignore patterns if provided; the build-time
2475 configured defaults otherwise. */
2476 if (ignore_patterns)
2478 eb->ignores = ignore_patterns;
2482 apr_array_header_t *ignores;
2484 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
2485 eb->ignores = ignores;
2488 /* The edit baton's status structure maps to PATH, and the editor
2489 have to be aware of whether that is the anchor or the target. */
2490 SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
2491 result_pool, scratch_pool));
2493 /* Construct an editor. */
2494 tree_editor->set_target_revision = set_target_revision;
2495 tree_editor->open_root = open_root;
2496 tree_editor->delete_entry = delete_entry;
2497 tree_editor->add_directory = add_directory;
2498 tree_editor->open_directory = open_directory;
2499 tree_editor->change_dir_prop = change_dir_prop;
2500 tree_editor->close_directory = close_directory;
2501 tree_editor->add_file = add_file;
2502 tree_editor->open_file = open_file;
2503 tree_editor->apply_textdelta = apply_textdelta;
2504 tree_editor->change_file_prop = change_file_prop;
2505 tree_editor->close_file = close_file;
2506 tree_editor->close_edit = close_edit;
2508 inner_editor = tree_editor;
2511 if (!server_performs_filtering
2512 && !depth_as_sticky)
2513 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2522 /* Conjoin a cancellation editor with our status editor. */
2523 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2524 inner_editor, inner_baton,
2528 if (set_locks_baton)
2529 *set_locks_baton = eb;
2531 sfb = apr_palloc(result_pool, sizeof(*sfb));
2532 sfb->db = wc_ctx->db;
2533 sfb->base_abspath = eb->anchor_abspath;
2534 sfb->fetch_base = FALSE;
2536 shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2537 shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2538 shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2539 shim_callbacks->fetch_baton = sfb;
2541 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2542 NULL, NULL, shim_callbacks,
2543 result_pool, scratch_pool));
2545 return SVN_NO_ERROR;
2548 /* Like svn_io_stat_dirent, but works case sensitive inside working
2549 copies. Before 1.8 we handled this with a selection filter inside
2551 static svn_error_t *
2552 stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
2554 const char *local_abspath,
2555 apr_pool_t *result_pool,
2556 apr_pool_t *scratch_pool)
2558 svn_boolean_t is_wcroot;
2560 /* The wcroot is "" inside the wc; handle it as not in the wc, as
2561 the case of the root is indifferent to us. */
2563 /* Note that for performance this is really just a few hashtable lookups,
2564 as we just used local_abspath for a db call in both our callers */
2565 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
2568 return svn_error_trace(
2569 svn_io_stat_dirent2(dirent, local_abspath,
2570 ! is_wcroot /* verify_truename */,
2571 TRUE /* ignore_enoent */,
2572 result_pool, scratch_pool));
2576 svn_wc__internal_walk_status(svn_wc__db_t *db,
2577 const char *local_abspath,
2579 svn_boolean_t get_all,
2580 svn_boolean_t no_ignore,
2581 svn_boolean_t ignore_text_mods,
2582 const apr_array_header_t *ignore_patterns,
2583 svn_wc_status_func4_t status_func,
2585 svn_cancel_func_t cancel_func,
2587 apr_pool_t *scratch_pool)
2589 struct walk_status_baton wb;
2590 const svn_io_dirent2_t *dirent;
2591 const struct svn_wc__db_info_t *info;
2595 wb.target_abspath = local_abspath;
2596 wb.ignore_text_mods = ignore_text_mods;
2597 wb.repos_root = NULL;
2598 wb.repos_locks = NULL;
2600 /* Use the caller-provided ignore patterns if provided; the build-time
2601 configured defaults otherwise. */
2602 if (!ignore_patterns)
2604 apr_array_header_t *ignores;
2606 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
2607 ignore_patterns = ignores;
2610 err = svn_wc__db_read_single_info(&info, db, local_abspath,
2611 scratch_pool, scratch_pool);
2615 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2617 svn_error_clear(err);
2621 return svn_error_trace(err);
2623 wb.externals = apr_hash_make(scratch_pool);
2625 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2626 scratch_pool, scratch_pool));
2630 SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
2632 scratch_pool, scratch_pool));
2634 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2635 scratch_pool, scratch_pool));
2639 && info->kind == svn_node_dir
2640 && info->status != svn_wc__db_status_not_present
2641 && info->status != svn_wc__db_status_excluded
2642 && info->status != svn_wc__db_status_server_excluded)
2644 SVN_ERR(get_dir_status(&wb,
2646 FALSE /* skip_root */,
2654 status_func, status_baton,
2655 cancel_func, cancel_baton,
2660 /* It may be a file or an unversioned item. And this is an explicit
2661 * target, so no ignoring. An unversioned item (file or dir) shows a
2662 * status like '?', and can yield a tree conflicted path. */
2663 err = get_child_status(&wb,
2669 status_func, status_baton,
2670 cancel_func, cancel_baton,
2673 if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2675 /* The parent is also not versioned, but it is not nice to show
2676 an error about a path a user didn't intend to touch. */
2677 svn_error_clear(err);
2678 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
2679 _("The node '%s' was not found."),
2680 svn_dirent_local_style(local_abspath,
2686 return SVN_NO_ERROR;
2690 svn_wc_walk_status(svn_wc_context_t *wc_ctx,
2691 const char *local_abspath,
2693 svn_boolean_t get_all,
2694 svn_boolean_t no_ignore,
2695 svn_boolean_t ignore_text_mods,
2696 const apr_array_header_t *ignore_patterns,
2697 svn_wc_status_func4_t status_func,
2699 svn_cancel_func_t cancel_func,
2701 apr_pool_t *scratch_pool)
2703 return svn_error_trace(
2704 svn_wc__internal_walk_status(wc_ctx->db,
2720 svn_wc_status_set_repos_locks(void *edit_baton,
2722 const char *repos_root,
2725 struct edit_baton *eb = edit_baton;
2727 eb->wb.repos_locks = locks;
2728 eb->wb.repos_root = apr_pstrdup(pool, repos_root);
2730 return SVN_NO_ERROR;
2735 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2739 svn_config_t *cfg = config
2740 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
2744 /* Check the Subversion run-time configuration for global ignores.
2745 If no configuration value exists, we fall back to our defaults. */
2746 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2747 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2748 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2749 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2751 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2752 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2753 return SVN_NO_ERROR;
2758 static svn_error_t *
2759 internal_status(svn_wc_status3_t **status,
2761 const char *local_abspath,
2762 apr_pool_t *result_pool,
2763 apr_pool_t *scratch_pool)
2765 const svn_io_dirent2_t *dirent;
2766 svn_node_kind_t node_kind;
2767 const char *parent_repos_relpath;
2768 const char *parent_repos_root_url;
2769 const char *parent_repos_uuid;
2770 svn_wc__db_status_t node_status;
2771 svn_boolean_t conflicted;
2772 svn_boolean_t is_root = FALSE;
2775 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2777 err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL,
2778 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2779 NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
2780 NULL, NULL, NULL, NULL, NULL, NULL,
2782 scratch_pool, scratch_pool);
2786 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
2787 return svn_error_trace(err);
2789 svn_error_clear(err);
2790 node_kind = svn_node_unknown;
2791 /* Ensure conflicted is always set, but don't hide tree conflicts
2792 on 'hidden' nodes. */
2795 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2796 scratch_pool, scratch_pool));
2799 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2800 scratch_pool, scratch_pool));
2802 if (node_kind != svn_node_unknown
2803 && (node_status == svn_wc__db_status_not_present
2804 || node_status == svn_wc__db_status_server_excluded
2805 || node_status == svn_wc__db_status_excluded))
2807 node_kind = svn_node_unknown;
2810 if (node_kind == svn_node_unknown)
2811 return svn_error_trace(assemble_unversioned(status,
2814 FALSE /* is_ignored */,
2815 result_pool, scratch_pool));
2817 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
2820 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
2824 svn_wc__db_status_t parent_status;
2825 const char *parent_abspath = svn_dirent_dirname(local_abspath,
2828 err = svn_wc__db_read_info(&parent_status, NULL, NULL,
2829 &parent_repos_relpath, &parent_repos_root_url,
2830 &parent_repos_uuid, NULL, NULL, NULL,
2831 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2832 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2833 NULL, NULL, NULL, NULL,
2835 result_pool, scratch_pool);
2837 if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
2838 || SVN_WC__ERR_IS_NOT_CURRENT_WC(err)))
2840 svn_error_clear(err);
2841 parent_repos_root_url = NULL;
2842 parent_repos_relpath = NULL;
2843 parent_repos_uuid = NULL;
2849 parent_repos_root_url = NULL;
2850 parent_repos_relpath = NULL;
2851 parent_repos_uuid = NULL;
2854 return svn_error_trace(assemble_status(status, db, local_abspath,
2855 parent_repos_root_url,
2856 parent_repos_relpath,
2862 NULL /* repos_lock */,
2863 result_pool, scratch_pool));
2868 svn_wc_status3(svn_wc_status3_t **status,
2869 svn_wc_context_t *wc_ctx,
2870 const char *local_abspath,
2871 apr_pool_t *result_pool,
2872 apr_pool_t *scratch_pool)
2874 return svn_error_trace(
2875 internal_status(status, wc_ctx->db, local_abspath, result_pool,
2880 svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
2883 svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2885 /* Shallow copy all members. */
2886 *new_stat = *orig_stat;
2888 /* Now go back and dup the deep items into this pool. */
2889 if (orig_stat->repos_lock)
2890 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2892 if (orig_stat->changed_author)
2893 new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
2895 if (orig_stat->ood_changed_author)
2896 new_stat->ood_changed_author
2897 = apr_pstrdup(pool, orig_stat->ood_changed_author);
2899 if (orig_stat->lock)
2900 new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
2902 if (orig_stat->changelist)
2903 new_stat->changelist
2904 = apr_pstrdup(pool, orig_stat->changelist);
2906 if (orig_stat->repos_root_url)
2907 new_stat->repos_root_url
2908 = apr_pstrdup(pool, orig_stat->repos_root_url);
2910 if (orig_stat->repos_relpath)
2911 new_stat->repos_relpath
2912 = apr_pstrdup(pool, orig_stat->repos_relpath);
2914 if (orig_stat->repos_uuid)
2915 new_stat->repos_uuid
2916 = apr_pstrdup(pool, orig_stat->repos_uuid);
2918 if (orig_stat->moved_from_abspath)
2919 new_stat->moved_from_abspath
2920 = apr_pstrdup(pool, orig_stat->moved_from_abspath);
2922 if (orig_stat->moved_to_abspath)
2923 new_stat->moved_to_abspath
2924 = apr_pstrdup(pool, orig_stat->moved_to_abspath);
2926 /* Return the new hotness. */
2931 svn_wc_get_ignores2(apr_array_header_t **patterns,
2932 svn_wc_context_t *wc_ctx,
2933 const char *local_abspath,
2935 apr_pool_t *result_pool,
2936 apr_pool_t *scratch_pool)
2938 apr_array_header_t *default_ignores;
2940 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
2941 return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
2944 result_pool, scratch_pool));