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"
40 #include "svn_config.h"
43 #include "svn_sorts.h"
45 #include "svn_private_config.h"
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"
56 /* The file internal variant of svn_wc_status3_t, with slightly more
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.
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
67 typedef struct svn_wc__internal_status_t
69 svn_wc_status3_t s; /* First member; same pointer*/
71 svn_boolean_t has_descendants;
73 /* Make sure to update hash_stash() when adding values here */
74 } svn_wc__internal_status_t;
77 /*** Baton used for walking the local status */
78 struct walk_status_baton
80 /* The DB handle for managing the working copy state. */
83 /*** External handling ***/
84 /* Target of the status */
85 const char *target_abspath;
87 /* Should we ignore text modifications? */
88 svn_boolean_t ignore_text_mods;
90 /* Scan the working copy for local modifications and missing nodes. */
91 svn_boolean_t check_working_copy;
93 /* Externals info harvested during the status run. */
94 apr_hash_t *externals;
96 /*** Repository lock handling ***/
97 /* The repository root URL, if set. */
98 const char *repos_root;
100 /* Repository locks, if set. */
101 apr_hash_t *repos_locks;
104 /*** Editor batons ***/
108 /* For status, the "destination" of the edit. */
109 const char *anchor_abspath;
110 const char *target_abspath;
111 const char *target_basename;
113 /* The DB handle for managing the working copy state. */
116 /* The overall depth of this edit (a dir baton may override this).
118 * If this is svn_depth_unknown, the depths found in the working
119 * copy will govern the edit; or if the edit depth indicates a
120 * descent deeper than the found depths are capable of, the found
121 * depths also govern, of course (there's no point descending into
122 * something that's not there).
124 svn_depth_t default_depth;
126 /* Do we want all statuses (instead of just the interesting ones) ? */
127 svn_boolean_t get_all;
129 /* Ignore the svn:ignores. */
130 svn_boolean_t no_ignore;
132 /* The comparison revision in the repository. This is a reference
133 because this editor returns this rev to the driver directly, as
134 well as in each statushash entry. */
135 svn_revnum_t *target_revision;
137 /* Status function/baton. */
138 svn_wc_status_func4_t status_func;
141 /* Cancellation function/baton. */
142 svn_cancel_func_t cancel_func;
145 /* The configured set of default ignores. */
146 const apr_array_header_t *ignores;
148 /* Status item for the path represented by the anchor of the edit. */
149 svn_wc__internal_status_t *anchor_status;
151 /* Was open_root() called for this edit drive? */
152 svn_boolean_t root_opened;
154 /* The local status baton */
155 struct walk_status_baton wb;
161 /* The path to this directory. */
162 const char *local_abspath;
164 /* Basename of this directory. */
167 /* The global edit baton. */
168 struct edit_baton *edit_baton;
170 /* Baton for this directory's parent, or NULL if this is the root
172 struct dir_baton *parent_baton;
174 /* The ambient requested depth below this point in the edit. This
175 can differ from the parent baton's depth (with the edit baton
176 considered the ultimate parent baton). For example, if the
177 parent baton has svn_depth_immediates, then here we should have
178 svn_depth_empty, because there would be no further recursion, not
179 even to file children. */
182 /* Is this directory filtered out due to depth? (Note that if this
183 is TRUE, the depth field is undefined.) */
184 svn_boolean_t excluded;
186 /* 'svn status' shouldn't print status lines for things that are
187 added; we're only interest in asking if objects that the user
188 *already* has are up-to-date or not. Thus if this flag is set,
189 the next two will be ignored. :-) */
192 /* Gets set iff there's a change to this directory's properties, to
193 guide us when syncing adm files later. */
194 svn_boolean_t prop_changed;
196 /* This means (in terms of 'svn status') that some child was deleted
197 or added to the directory */
198 svn_boolean_t text_changed;
200 /* Working copy status structures for children of this directory.
201 This hash maps const char * abspaths to svn_wc_status3_t *
205 /* The pool in which this baton itself is allocated. */
208 /* The repository root relative path to this item in the repository. */
209 const char *repos_relpath;
211 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
212 svn_node_kind_t ood_kind;
213 svn_revnum_t ood_changed_rev;
214 apr_time_t ood_changed_date;
215 const char *ood_changed_author;
221 /* Absolute local path to this file */
222 const char *local_abspath;
224 /* The global edit baton. */
225 struct edit_baton *edit_baton;
227 /* Baton for this file's parent directory. */
228 struct dir_baton *dir_baton;
230 /* Pool specific to this file_baton. */
233 /* Basename of this file */
236 /* 'svn status' shouldn't print status lines for things that are
237 added; we're only interest in asking if objects that the user
238 *already* has are up-to-date or not. Thus if this flag is set,
239 the next two will be ignored. :-) */
242 /* This gets set if the file underwent a text change, which guides
243 the code that syncs up the adm dir and working copy. */
244 svn_boolean_t text_changed;
246 /* This gets set if the file underwent a prop change, which guides
247 the code that syncs up the adm dir and working copy. */
248 svn_boolean_t prop_changed;
250 /* The repository root relative path to this item in the repository. */
251 const char *repos_relpath;
253 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
254 svn_node_kind_t ood_kind;
255 svn_revnum_t ood_changed_rev;
256 apr_time_t ood_changed_date;
258 const char *ood_changed_author;
266 /* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
267 information in INFO if available, falling back on
268 PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
269 finally falling back on querying DB. */
271 get_repos_root_url_relpath(const char **repos_relpath,
272 const char **repos_root_url,
273 const char **repos_uuid,
274 const struct svn_wc__db_info_t *info,
275 const char *parent_repos_relpath,
276 const char *parent_repos_root_url,
277 const char *parent_repos_uuid,
279 const char *local_abspath,
280 apr_pool_t *result_pool,
281 apr_pool_t *scratch_pool)
283 if (info->repos_relpath && info->repos_root_url)
285 *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
286 *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
287 *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
289 else if (parent_repos_relpath && parent_repos_root_url)
291 *repos_relpath = svn_relpath_join(parent_repos_relpath,
292 svn_dirent_basename(local_abspath,
295 *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
296 *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
300 SVN_ERR(svn_wc__db_read_repos_info(NULL,
301 repos_relpath, repos_root_url,
304 result_pool, scratch_pool));
311 internal_status(svn_wc__internal_status_t **status,
313 const char *local_abspath,
314 svn_boolean_t check_working_copy,
315 apr_pool_t *result_pool,
316 apr_pool_t *scratch_pool);
318 /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
319 RESULT_POOL and use SCRATCH_POOL for temporary allocations.
321 PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
322 and repository relative path of the parent of LOCAL_ABSPATH or NULL if
323 LOCAL_ABSPATH doesn't have a versioned parent directory.
325 DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
326 NULL if the node does not exist on disk.
328 If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
329 *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
330 allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
331 don't check for text mods, assume there are none and set and *STATUS
332 returned to reflect that assumption. If CHECK_WORKING_COPY is FALSE,
333 do not adjust the result for missing working copy files.
335 The status struct's repos_lock field will be set to REPOS_LOCK.
338 assemble_status(svn_wc__internal_status_t **status,
340 const char *local_abspath,
341 const char *parent_repos_root_url,
342 const char *parent_repos_relpath,
343 const char *parent_repos_uuid,
344 const struct svn_wc__db_info_t *info,
345 const svn_io_dirent2_t *dirent,
346 svn_boolean_t get_all,
347 svn_boolean_t ignore_text_mods,
348 svn_boolean_t check_working_copy,
349 const svn_lock_t *repos_lock,
350 apr_pool_t *result_pool,
351 apr_pool_t *scratch_pool)
353 svn_wc__internal_status_t *inner_stat;
354 svn_wc_status3_t *stat;
355 svn_boolean_t switched_p = FALSE;
356 svn_boolean_t copied = FALSE;
357 svn_boolean_t conflicted;
358 const char *moved_from_abspath = NULL;
360 /* Defaults for two main variables. */
361 enum svn_wc_status_kind node_status = svn_wc_status_normal;
362 enum svn_wc_status_kind text_status = svn_wc_status_normal;
363 enum svn_wc_status_kind prop_status = svn_wc_status_none;
366 if (!info->repos_relpath || !parent_repos_relpath)
370 /* A node is switched if it doesn't have the implied repos_relpath */
371 const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
372 info->repos_relpath);
373 switched_p = !name || (strcmp(name,
374 svn_dirent_basename(local_abspath, NULL))
378 if (info->status == svn_wc__db_status_incomplete || info->incomplete)
380 /* Highest precedence. */
381 node_status = svn_wc_status_incomplete;
383 else if (info->status == svn_wc__db_status_deleted)
385 node_status = svn_wc_status_deleted;
387 if (!info->have_base || info->have_more_work || info->copied)
389 else if (!info->have_more_work && info->have_base)
393 const char *work_del_abspath;
395 /* Find out details of our deletion. */
396 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
397 &work_del_abspath, NULL,
399 scratch_pool, scratch_pool));
400 if (work_del_abspath)
401 copied = TRUE; /* Working deletion */
404 else if (check_working_copy)
406 /* Examine whether our target is missing or obstructed. To detect
407 * obstructions, we have to look at the on-disk status in DIRENT. */
408 svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
412 if (!dirent || dirent->kind != expected_kind)
414 /* A present or added node should be on disk, so it is
415 reported missing or obstructed. */
416 if (!dirent || dirent->kind == svn_node_none)
417 node_status = svn_wc_status_missing;
419 node_status = svn_wc_status_obstructed;
423 /* Does the node have props? */
424 if (info->status != svn_wc__db_status_deleted)
427 prop_status = svn_wc_status_modified;
428 else if (info->had_props)
429 prop_status = svn_wc_status_normal;
432 /* If NODE_STATUS is still normal, after the above checks, then
433 we should proceed to refine the status.
435 If it was changed, then the subdir is incomplete or missing/obstructed.
437 if (info->kind != svn_node_dir
438 && node_status == svn_wc_status_normal)
440 svn_boolean_t text_modified_p = FALSE;
442 /* Implement predecence rules: */
444 /* 1. Set the two main variables to "discovered" values first (M, C).
445 Together, these two stati are of lowest precedence, and C has
446 precedence over M. */
448 /* If the entry is a file, check for textual modifications */
449 if ((info->kind == svn_node_file
450 || info->kind == svn_node_symlink)
452 && (info->special == (dirent && dirent->special))
453 #endif /* HAVE_SYMLINK */
456 /* If the on-disk dirent exactly matches the expected state
457 skip all operations in svn_wc__internal_text_modified_p()
458 to avoid an extra filestat for every file, which can be
459 expensive on network drives as a filestat usually can't
461 if (!info->has_checksum)
462 text_modified_p = TRUE; /* Local addition -> Modified */
463 else if (ignore_text_mods
465 && info->recorded_size != SVN_INVALID_FILESIZE
466 && info->recorded_time != 0
467 && info->recorded_size == dirent->filesize
468 && info->recorded_time == dirent->mtime))
469 text_modified_p = FALSE;
473 err = svn_wc__internal_file_modified_p(&text_modified_p,
475 FALSE, scratch_pool);
479 if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
480 return svn_error_trace(err);
482 /* An access denied is very common on Windows when another
483 application has the file open. Previously we ignored
484 this error in svn_wc__text_modified_internal_p, where it
485 should have really errored. */
486 svn_error_clear(err);
487 text_modified_p = TRUE;
492 else if (info->special != (dirent && dirent->special))
493 node_status = svn_wc_status_obstructed;
494 #endif /* HAVE_SYMLINK */
497 text_status = svn_wc_status_modified;
500 conflicted = info->conflicted;
503 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
505 /* ### Check if the conflict was resolved by removing the marker files.
506 ### This should really be moved to the users of this API */
507 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
509 db, local_abspath, scratch_pool));
511 if (!text_conflicted && !prop_conflicted && !tree_conflicted)
515 if (node_status == svn_wc_status_normal)
517 /* 2. Possibly overwrite the text_status variable with "scheduled"
518 states from the entry (A, D, R). As a group, these states are
519 of medium precedence. They also override any C or M that may
520 be in the prop_status field at this point, although they do not
521 override a C text status.*/
522 if (info->status == svn_wc__db_status_added)
524 copied = info->copied;
526 { /* Keep status normal */ }
527 else if (!info->have_base && !info->have_more_work)
529 /* Simple addition or copy, no replacement */
530 node_status = svn_wc_status_added;
534 svn_wc__db_status_t below_working;
535 svn_boolean_t have_base, have_work;
537 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
542 /* If the node is not present or deleted (read: not present
543 in working), then the node is not a replacement */
544 if (below_working != svn_wc__db_status_not_present
545 && below_working != svn_wc__db_status_deleted)
547 node_status = svn_wc_status_replaced;
550 node_status = svn_wc_status_added;
553 /* Get moved-from info (only for potential op-roots of a move). */
554 if (info->moved_here && info->op_root)
557 err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
559 result_pool, scratch_pool);
563 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
564 return svn_error_trace(err);
566 svn_error_clear(err);
567 /* We are no longer moved... So most likely we are somehow
568 changing the db for things like resolving conflicts. */
570 moved_from_abspath = NULL;
577 if (node_status == svn_wc_status_normal)
578 node_status = text_status;
580 if (node_status == svn_wc_status_normal
581 && prop_status != svn_wc_status_none)
582 node_status = prop_status;
584 /* 5. Easy out: unless we're fetching -every- node, don't bother
585 to allocate a struct for an uninteresting node.
587 This filter should match the filter in is_sendable_status() */
589 if (((node_status == svn_wc_status_none)
590 || (node_status == svn_wc_status_normal))
596 && (! info->changelist)
598 && (! info->moved_to))
604 /* 6. Build and return a status structure. */
606 inner_stat = apr_pcalloc(result_pool, sizeof(*inner_stat));
607 stat = &inner_stat->s;
608 inner_stat->has_descendants = info->has_descendants;
613 stat->kind = svn_node_dir;
616 case svn_node_symlink:
617 stat->kind = svn_node_file;
619 case svn_node_unknown:
621 stat->kind = svn_node_unknown;
623 stat->depth = info->depth;
627 stat->filesize = (dirent->kind == svn_node_file)
629 : SVN_INVALID_FILESIZE;
630 stat->actual_kind = dirent->special ? svn_node_symlink
635 stat->filesize = SVN_INVALID_FILESIZE;
636 stat->actual_kind = ignore_text_mods ? svn_node_unknown
640 stat->node_status = node_status;
641 stat->text_status = text_status;
642 stat->prop_status = prop_status;
643 stat->repos_node_status = svn_wc_status_none; /* default */
644 stat->repos_text_status = svn_wc_status_none; /* default */
645 stat->repos_prop_status = svn_wc_status_none; /* default */
646 stat->switched = switched_p;
647 stat->copied = copied;
648 stat->repos_lock = repos_lock;
649 stat->revision = info->revnum;
650 stat->changed_rev = info->changed_rev;
651 if (info->changed_author)
652 stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
653 stat->changed_date = info->changed_date;
655 stat->ood_kind = svn_node_none;
656 stat->ood_changed_rev = SVN_INVALID_REVNUM;
657 stat->ood_changed_date = 0;
658 stat->ood_changed_author = NULL;
660 SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
661 &stat->repos_root_url,
662 &stat->repos_uuid, info,
663 parent_repos_relpath,
664 parent_repos_root_url,
667 result_pool, scratch_pool));
671 svn_lock_t *lck = svn_lock_create(result_pool);
672 lck->path = stat->repos_relpath;
673 lck->token = info->lock->token;
674 lck->owner = info->lock->owner;
675 lck->comment = info->lock->comment;
676 lck->creation_date = info->lock->date;
682 stat->locked = info->locked;
683 stat->conflicted = conflicted;
684 stat->versioned = TRUE;
685 if (info->changelist)
686 stat->changelist = apr_pstrdup(result_pool, info->changelist);
688 stat->moved_from_abspath = moved_from_abspath;
690 /* ### TODO: Handle multiple moved_to values properly */
692 stat->moved_to_abspath = apr_pstrdup(result_pool,
693 info->moved_to->moved_to_abspath);
695 stat->file_external = info->file_external;
697 *status = inner_stat;
702 /* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
703 available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
704 temporary allocations.
706 If IS_IGNORED is non-zero and this is a non-versioned entity, set
707 the node_status to svn_wc_status_none. Otherwise set the
708 node_status to svn_wc_status_unversioned.
711 assemble_unversioned(svn_wc__internal_status_t **status,
713 const char *local_abspath,
714 const svn_io_dirent2_t *dirent,
715 svn_boolean_t tree_conflicted,
716 svn_boolean_t is_ignored,
717 apr_pool_t *result_pool,
718 apr_pool_t *scratch_pool)
720 svn_wc__internal_status_t *inner_status;
721 svn_wc_status3_t *stat;
723 /* return a fairly blank structure. */
724 inner_status = apr_pcalloc(result_pool, sizeof(*inner_status));
725 stat = &inner_status->s;
727 /*stat->versioned = FALSE;*/
728 stat->kind = svn_node_unknown; /* not versioned */
729 stat->depth = svn_depth_unknown;
732 stat->actual_kind = dirent->special ? svn_node_symlink
734 stat->filesize = (dirent->kind == svn_node_file)
736 : SVN_INVALID_FILESIZE;
740 stat->actual_kind = svn_node_none;
741 stat->filesize = SVN_INVALID_FILESIZE;
744 stat->node_status = svn_wc_status_none;
745 stat->text_status = svn_wc_status_none;
746 stat->prop_status = svn_wc_status_none;
747 stat->repos_node_status = svn_wc_status_none;
748 stat->repos_text_status = svn_wc_status_none;
749 stat->repos_prop_status = svn_wc_status_none;
751 /* If this path has no entry, but IS present on disk, it's
752 unversioned. If this file is being explicitly ignored (due
753 to matching an ignore-pattern), the node_status is set to
754 svn_wc_status_ignored. Otherwise the node_status is set to
755 svn_wc_status_unversioned. */
756 if (dirent && dirent->kind != svn_node_none)
759 stat->node_status = svn_wc_status_ignored;
761 stat->node_status = svn_wc_status_unversioned;
763 else if (tree_conflicted)
765 /* If this path has no entry, is NOT present on disk, and IS a
766 tree conflict victim, report it as conflicted. */
767 stat->node_status = svn_wc_status_conflicted;
770 stat->revision = SVN_INVALID_REVNUM;
771 stat->changed_rev = SVN_INVALID_REVNUM;
772 stat->ood_changed_rev = SVN_INVALID_REVNUM;
773 stat->ood_kind = svn_node_none;
775 /* For the case of an incoming delete to a locally deleted path during
776 an update, we get a tree conflict. */
777 stat->conflicted = tree_conflicted;
778 stat->changelist = NULL;
780 *status = inner_status;
785 /* Given an ENTRY object representing PATH, build a status structure
786 and pass it off to the STATUS_FUNC/STATUS_BATON. All other
787 arguments are the same as those passed to assemble_status(). */
789 send_status_structure(const struct walk_status_baton *wb,
790 const char *local_abspath,
791 const char *parent_repos_root_url,
792 const char *parent_repos_relpath,
793 const char *parent_repos_uuid,
794 const struct svn_wc__db_info_t *info,
795 const svn_io_dirent2_t *dirent,
796 svn_boolean_t get_all,
797 svn_wc_status_func4_t status_func,
799 apr_pool_t *scratch_pool)
801 svn_wc__internal_status_t *statstruct;
802 const svn_lock_t *repos_lock = NULL;
804 /* Check for a repository lock. */
807 const char *repos_relpath, *repos_root_url, *repos_uuid;
809 SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
811 info, parent_repos_relpath,
812 parent_repos_root_url,
814 wb->db, local_abspath,
815 scratch_pool, scratch_pool));
818 /* repos_lock still uses the deprecated filesystem absolute path
820 repos_lock = svn_hash_gets(wb->repos_locks,
821 svn_fspath__join("/", repos_relpath,
826 SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
827 parent_repos_root_url, parent_repos_relpath,
829 info, dirent, get_all,
830 wb->ignore_text_mods, wb->check_working_copy,
831 repos_lock, scratch_pool, scratch_pool));
833 if (statstruct && status_func)
834 return svn_error_trace((*status_func)(status_baton, local_abspath,
842 /* Store in *PATTERNS a list of ignores collected from svn:ignore properties
843 on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
844 repository ancestors (as cached in the working copy), including the default
845 ignores passed in as IGNORES.
847 Upon return, *PATTERNS will contain zero or more (const char *)
848 patterns from the value of the SVN_PROP_IGNORE property set on
849 the working directory path.
851 IGNORES is a list of patterns to include; typically this will
852 be the default ignores as, for example, specified in a config file.
854 DB, LOCAL_ABSPATH is used to access the working copy.
856 Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
858 None of the arguments may be NULL.
861 collect_ignore_patterns(apr_array_header_t **patterns,
863 const char *local_abspath,
864 const apr_array_header_t *ignores,
865 apr_pool_t *result_pool,
866 apr_pool_t *scratch_pool)
870 apr_array_header_t *inherited_props;
873 /* ### assert we are passed a directory? */
875 *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
877 /* Copy default ignores into the local PATTERNS array. */
878 for (i = 0; i < ignores->nelts; i++)
880 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
881 APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
885 err = svn_wc__db_read_inherited_props(&inherited_props, &props,
887 SVN_PROP_INHERITABLE_IGNORES,
888 scratch_pool, scratch_pool);
892 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
893 return svn_error_trace(err);
895 svn_error_clear(err);
901 const svn_string_t *value;
903 value = svn_hash_gets(props, SVN_PROP_IGNORE);
905 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
908 value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
910 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
914 for (i = 0; i < inherited_props->nelts; i++)
916 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
917 inherited_props, i, svn_prop_inherited_item_t *);
918 const svn_string_t *value;
920 value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
923 svn_cstring_split_append(*patterns, value->data,
924 "\n\r", FALSE, result_pool);
931 /* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
932 LOCAL_ABSPATH is the drop location for, or an intermediate directory
933 of the drop location for, an externals definition. Use SCRATCH_POOL
936 is_external_path(apr_hash_t *externals,
937 const char *local_abspath,
938 apr_pool_t *scratch_pool)
940 apr_hash_index_t *hi;
942 /* First try: does the path exist as a key in the hash? */
943 if (svn_hash_gets(externals, local_abspath))
946 /* Failing that, we need to check if any external is a child of
948 for (hi = apr_hash_first(scratch_pool, externals);
950 hi = apr_hash_next(hi))
952 const char *external_abspath = apr_hash_this_key(hi);
954 if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
962 /* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
963 for it through STATUS_FUNC/STATUS_BATON unless this path is being
964 ignored. This function should never be called on a versioned entry.
966 LOCAL_ABSPATH is the path to the unversioned file whose status is being
967 requested. PATH_KIND is the node kind of NAME as determined by the
968 caller. PATH_SPECIAL is the special status of the path, also determined
970 PATTERNS points to a list of filename patterns which are marked as ignored.
971 None of these parameter may be NULL.
973 If NO_IGNORE is TRUE, the item will be added regardless of
974 whether it is ignored; otherwise we will only add the item if it
975 does not match any of the patterns in PATTERN or INHERITED_IGNORES.
977 Allocate everything in POOL.
980 send_unversioned_item(const struct walk_status_baton *wb,
981 const char *local_abspath,
982 const svn_io_dirent2_t *dirent,
983 svn_boolean_t tree_conflicted,
984 const apr_array_header_t *patterns,
985 svn_boolean_t no_ignore,
986 svn_wc_status_func4_t status_func,
988 apr_pool_t *scratch_pool)
990 svn_boolean_t is_ignored;
991 svn_boolean_t is_external;
992 svn_wc__internal_status_t *status;
993 const char *base_name = svn_dirent_basename(local_abspath, NULL);
995 is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
996 SVN_ERR(assemble_unversioned(&status,
997 wb->db, local_abspath,
998 dirent, tree_conflicted,
1000 scratch_pool, scratch_pool));
1002 is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
1004 status->s.node_status = svn_wc_status_external;
1006 /* We can have a tree conflict on an unversioned path, i.e. an incoming
1007 * delete on a locally deleted path during an update. Don't ever ignore
1009 if (status->s.conflicted)
1012 /* If we aren't ignoring it, or if it's an externals path, pass this
1013 entry to the status func. */
1017 return svn_error_trace((*status_func)(status_baton, local_abspath,
1018 &status->s, scratch_pool));
1020 return SVN_NO_ERROR;
1023 static svn_error_t *
1024 get_dir_status(const struct walk_status_baton *wb,
1025 const char *local_abspath,
1026 svn_boolean_t skip_this_dir,
1027 const char *parent_repos_root_url,
1028 const char *parent_repos_relpath,
1029 const char *parent_repos_uuid,
1030 const struct svn_wc__db_info_t *dir_info,
1031 const svn_io_dirent2_t *dirent,
1032 const apr_array_header_t *ignore_patterns,
1034 svn_boolean_t get_all,
1035 svn_boolean_t no_ignore,
1036 svn_wc_status_func4_t status_func,
1038 svn_cancel_func_t cancel_func,
1040 apr_pool_t *scratch_pool);
1042 /* Send out a status structure according to the information gathered on one
1043 * child node. (Basically this function is the guts of the loop in
1044 * get_dir_status() and of get_child_status().)
1046 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
1047 * dirname of LOCAL_ABSPATH.
1049 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
1050 * be an unversioned file or dir, or a versioned file. For versioned
1051 * directories use get_dir_status() instead.
1053 * INFO may be NULL for an unversioned node. If such node has a tree conflict,
1054 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
1055 * UNVERSIONED_TREE_CONFLICTED is ignored.
1057 * DIRENT should reflect LOCAL_ABSPATH's dirent information.
1059 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
1060 * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
1062 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
1063 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
1064 * containing all ignore patterns, as returned by collect_ignore_patterns() on
1065 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
1066 * non-NULL, it is assumed it already holds those results.
1067 * This speeds up repeated calls with the same PARENT_ABSPATH.
1069 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
1070 * allocations are made in SCRATCH_POOL.
1072 * The remaining parameters correspond to get_dir_status(). */
1073 static svn_error_t *
1074 one_child_status(const struct walk_status_baton *wb,
1075 const char *local_abspath,
1076 const char *parent_abspath,
1077 const struct svn_wc__db_info_t *info,
1078 const svn_io_dirent2_t *dirent,
1079 const char *dir_repos_root_url,
1080 const char *dir_repos_relpath,
1081 const char *dir_repos_uuid,
1082 svn_boolean_t unversioned_tree_conflicted,
1083 apr_array_header_t **collected_ignore_patterns,
1084 const apr_array_header_t *ignore_patterns,
1086 svn_boolean_t get_all,
1087 svn_boolean_t no_ignore,
1088 svn_wc_status_func4_t status_func,
1090 svn_cancel_func_t cancel_func,
1092 apr_pool_t *result_pool,
1093 apr_pool_t *scratch_pool)
1095 svn_boolean_t conflicted = info ? info->conflicted
1096 : unversioned_tree_conflicted;
1099 && info->status != svn_wc__db_status_not_present
1100 && info->status != svn_wc__db_status_excluded
1101 && info->status != svn_wc__db_status_server_excluded
1102 && !(info->kind == svn_node_unknown
1103 && info->status == svn_wc__db_status_normal))
1105 if (depth == svn_depth_files
1106 && info->kind == svn_node_dir)
1108 return SVN_NO_ERROR;
1111 SVN_ERR(send_status_structure(wb, local_abspath,
1115 info, dirent, get_all,
1116 status_func, status_baton,
1119 /* Descend in subdirectories. */
1120 if (depth == svn_depth_infinity
1121 && info->has_descendants /* is dir, or was dir and tc descendants */)
1123 SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
1124 dir_repos_root_url, dir_repos_relpath,
1125 dir_repos_uuid, info,
1126 dirent, ignore_patterns,
1127 svn_depth_infinity, get_all,
1129 status_func, status_baton,
1130 cancel_func, cancel_baton,
1134 return SVN_NO_ERROR;
1137 /* If conflicted, fall right through to unversioned.
1138 * With depth_files, show all conflicts, even if their report is only
1139 * about directories. A tree conflict may actually report two different
1140 * kinds, so it's not so easy to define what depth=files means. We could go
1141 * look up the kinds in the conflict ... just show all. */
1144 /* We have a node, but its not visible in the WC. It can be a marker
1145 node (not present, (server) excluded), *or* it can be the explictly
1146 passed target of the status walk operation that doesn't exist.
1148 We only report the node when the caller explicitly as
1150 if (dirent == NULL && strcmp(wb->target_abspath, local_abspath) != 0)
1151 return SVN_NO_ERROR; /* Marker node */
1153 if (depth == svn_depth_files && dirent && dirent->kind == svn_node_dir)
1154 return SVN_NO_ERROR;
1156 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
1158 return SVN_NO_ERROR;
1161 /* The node exists on disk but there is no versioned information about it,
1162 * or it doesn't exist but is a tree conflicted path or should be
1163 * reported not-present. */
1165 /* Why pass ignore patterns on a tree conflicted node, even if it should
1166 * always show up in clients' status reports anyway? Because the calling
1167 * client decides whether to ignore, and thus this flag needs to be
1168 * determined. For example, in 'svn status', plain unversioned nodes show
1169 * as '? C', where ignored ones show as 'I C'. */
1171 if (ignore_patterns && ! *collected_ignore_patterns)
1172 SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
1173 wb->db, parent_abspath, ignore_patterns,
1174 result_pool, scratch_pool));
1176 SVN_ERR(send_unversioned_item(wb,
1180 *collected_ignore_patterns,
1182 status_func, status_baton,
1185 return SVN_NO_ERROR;
1188 /* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
1189 for all its child nodes (according to DEPTH) through STATUS_FUNC /
1192 If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
1193 All subdirs reached by recursion will be reported regardless of this
1196 PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
1197 URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
1198 retrieving them again. Otherwise they must be NULL.
1200 DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
1201 it again. Otherwise it must be NULL.
1203 DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
1204 so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
1206 Other arguments are the same as those passed to
1207 svn_wc_get_status_editor5(). */
1208 static svn_error_t *
1209 get_dir_status(const struct walk_status_baton *wb,
1210 const char *local_abspath,
1211 svn_boolean_t skip_this_dir,
1212 const char *parent_repos_root_url,
1213 const char *parent_repos_relpath,
1214 const char *parent_repos_uuid,
1215 const struct svn_wc__db_info_t *dir_info,
1216 const svn_io_dirent2_t *dirent,
1217 const apr_array_header_t *ignore_patterns,
1219 svn_boolean_t get_all,
1220 svn_boolean_t no_ignore,
1221 svn_wc_status_func4_t status_func,
1223 svn_cancel_func_t cancel_func,
1225 apr_pool_t *scratch_pool)
1227 const char *dir_repos_root_url;
1228 const char *dir_repos_relpath;
1229 const char *dir_repos_uuid;
1230 apr_hash_t *dirents, *nodes, *conflicts, *all_children;
1231 apr_array_header_t *sorted_children;
1232 apr_array_header_t *collected_ignore_patterns = NULL;
1233 apr_pool_t *iterpool;
1238 SVN_ERR(cancel_func(cancel_baton));
1240 if (depth == svn_depth_unknown)
1241 depth = svn_depth_infinity;
1243 iterpool = svn_pool_create(scratch_pool);
1245 if (wb->check_working_copy)
1247 err = svn_io_get_dirents3(&dirents, local_abspath,
1248 wb->ignore_text_mods /* only_check_type*/,
1249 scratch_pool, iterpool);
1251 && (APR_STATUS_IS_ENOENT(err->apr_err)
1252 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
1254 svn_error_clear(err);
1255 dirents = apr_hash_make(scratch_pool);
1261 dirents = apr_hash_make(scratch_pool);
1264 SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath,
1265 !wb->check_working_copy,
1266 scratch_pool, iterpool));
1268 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1269 &dir_repos_uuid, dir_info,
1270 parent_repos_relpath,
1271 parent_repos_root_url, parent_repos_uuid,
1272 wb->db, local_abspath,
1273 scratch_pool, iterpool));
1275 /* Create a hash containing all children. The source hashes
1276 don't all map the same types, but only the keys of the result
1277 hash are subsequently used. */
1278 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1279 wb->db, local_abspath,
1280 !wb->check_working_copy,
1281 scratch_pool, iterpool));
1283 all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
1284 if (apr_hash_count(conflicts) > 0)
1285 all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
1287 /* Handle "this-dir" first. */
1288 if (! skip_this_dir)
1290 /* This code is not conditional on HAVE_SYMLINK as some systems that do
1291 not allow creating symlinks (!HAVE_SYMLINK) can still encounter
1292 symlinks (or in case of Windows also 'Junctions') created by other
1295 Without this block a working copy in the root of a junction is
1296 reported as an obstruction, because the junction itself is reported as
1299 Systems that have no symlink support at all, would always see
1300 dirent->special as FALSE, so even there enabling this code shouldn't
1303 if (dirent->special)
1305 svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
1307 /* We're being pointed to "this-dir" via a symlink.
1308 * Get the real node kind and pretend the path is not a symlink.
1309 * This prevents send_status_structure() from treating this-dir
1310 * as a directory obstructed by a file. */
1311 SVN_ERR(svn_io_check_resolved_path(local_abspath,
1312 &this_dirent->kind, iterpool));
1313 this_dirent->special = FALSE;
1314 SVN_ERR(send_status_structure(wb, local_abspath,
1315 parent_repos_root_url,
1316 parent_repos_relpath,
1318 dir_info, this_dirent, get_all,
1319 status_func, status_baton,
1323 SVN_ERR(send_status_structure(wb, local_abspath,
1324 parent_repos_root_url,
1325 parent_repos_relpath,
1327 dir_info, dirent, get_all,
1328 status_func, status_baton,
1332 /* If the requested depth is empty, we only need status on this-dir. */
1333 if (depth == svn_depth_empty)
1334 return SVN_NO_ERROR;
1336 /* Walk all the children of this directory. */
1337 sorted_children = svn_sort__hash(all_children,
1338 svn_sort_compare_items_lexically,
1340 for (i = 0; i < sorted_children->nelts; i++)
1344 svn_sort__item_t item;
1345 const char *child_abspath;
1346 svn_io_dirent2_t *child_dirent;
1347 const struct svn_wc__db_info_t *child_info;
1349 svn_pool_clear(iterpool);
1351 item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
1355 child_abspath = svn_dirent_join(local_abspath, key, iterpool);
1356 child_dirent = apr_hash_get(dirents, key, klen);
1357 child_info = apr_hash_get(nodes, key, klen);
1359 SVN_ERR(one_child_status(wb,
1367 apr_hash_get(conflicts, key, klen) != NULL,
1368 &collected_ignore_patterns,
1381 /* Destroy our subpools. */
1382 svn_pool_destroy(iterpool);
1384 return SVN_NO_ERROR;
1387 /* Send an svn_wc_status3_t * structure for the versioned file, or for the
1388 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
1389 * explicit target). Does not recurse.
1391 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
1392 * unversioned nodes. An unversioned and tree-conflicted node however should
1393 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
1395 * DIRENT should reflect LOCAL_ABSPATH.
1397 * All allocations made in SCRATCH_POOL.
1399 * The remaining parameters correspond to get_dir_status(). */
1400 static svn_error_t *
1401 get_child_status(const struct walk_status_baton *wb,
1402 const char *local_abspath,
1403 const struct svn_wc__db_info_t *info,
1404 const svn_io_dirent2_t *dirent,
1405 const apr_array_header_t *ignore_patterns,
1406 svn_boolean_t get_all,
1407 svn_wc_status_func4_t status_func,
1409 svn_cancel_func_t cancel_func,
1411 apr_pool_t *scratch_pool)
1413 const char *dir_repos_root_url;
1414 const char *dir_repos_relpath;
1415 const char *dir_repos_uuid;
1416 const struct svn_wc__db_info_t *dir_info;
1417 apr_array_header_t *collected_ignore_patterns = NULL;
1418 const char *parent_abspath = svn_dirent_dirname(local_abspath,
1422 SVN_ERR(cancel_func(cancel_baton));
1424 if (dirent->kind == svn_node_none)
1427 SVN_ERR(svn_wc__db_read_single_info(&dir_info,
1428 wb->db, parent_abspath,
1429 !wb->check_working_copy,
1430 scratch_pool, scratch_pool));
1432 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1433 &dir_repos_uuid, dir_info,
1435 wb->db, parent_abspath,
1436 scratch_pool, scratch_pool));
1438 /* An unversioned node with a tree conflict will see an INFO != NULL here,
1439 * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
1440 * effect and INFO->CONFLICTED counts.
1441 * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
1443 SVN_ERR(one_child_status(wb,
1451 FALSE, /* unversioned_tree_conflicted */
1452 &collected_ignore_patterns,
1456 TRUE, /* no_ignore. This is an explicit target. */
1463 return SVN_NO_ERROR;
1470 /* A faux status callback function for stashing STATUS item in an hash
1471 (which is the BATON), keyed on PATH. This implements the
1472 svn_wc_status_func4_t interface. */
1473 static svn_error_t *
1474 hash_stash(void *baton,
1476 const svn_wc_status3_t *status,
1477 apr_pool_t *scratch_pool)
1479 apr_hash_t *stat_hash = baton;
1480 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1481 void *new_status = svn_wc_dup_status3(status, hash_pool);
1482 const svn_wc__internal_status_t *old_status = (const void*)status;
1484 /* Copy the internal/private data. */
1485 svn_wc__internal_status_t *is = new_status;
1486 is->has_descendants = old_status->has_descendants;
1488 assert(! svn_hash_gets(stat_hash, path));
1489 svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), new_status);
1491 return SVN_NO_ERROR;
1495 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1496 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1497 yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
1498 create a new status struct using the hash's pool.
1500 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1501 of date (ood) information we want to set in BATON. This is necessary
1502 because this function tweaks the status of out-of-date directories
1503 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1504 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1505 contains the ood info we want to bubble up to ancestor directories so these
1506 accurately reflect the fact they have an ood descendant.
1508 Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
1509 status structure's "network" fields.
1511 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1514 If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
1515 optionally the revision path was deleted, in all other cases it must
1516 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1517 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1518 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1519 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
1520 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1521 ood_last_cmt_rev value - see comment below.
1523 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1524 static svn_error_t *
1525 tweak_statushash(void *baton,
1526 void *this_dir_baton,
1527 svn_boolean_t is_dir_baton,
1529 svn_boolean_t check_working_copy,
1530 const char *local_abspath,
1531 enum svn_wc_status_kind repos_node_status,
1532 enum svn_wc_status_kind repos_text_status,
1533 enum svn_wc_status_kind repos_prop_status,
1534 svn_revnum_t deleted_rev,
1535 const svn_lock_t *repos_lock,
1536 apr_pool_t *scratch_pool)
1538 svn_wc_status3_t *statstruct;
1540 apr_hash_t *statushash;
1543 statushash = ((struct dir_baton *) baton)->statii;
1545 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1546 pool = apr_hash_pool_get(statushash);
1548 /* Is PATH already a hash-key? */
1549 statstruct = svn_hash_gets(statushash, local_abspath);
1551 /* If not, make it so. */
1554 svn_wc__internal_status_t *i_stat;
1555 /* If this item isn't being added, then we're most likely
1556 dealing with a non-recursive (or at least partially
1557 non-recursive) working copy. Due to bugs in how the client
1558 reports the state of non-recursive working copies, the
1559 repository can send back responses about paths that don't
1560 even exist locally. Our best course here is just to ignore
1561 those responses. After all, if the client had reported
1562 correctly in the first, that path would either be mentioned
1563 as an 'add' or not mentioned at all, depending on how we
1564 eventually fix the bugs in non-recursivity. See issue
1565 #2122 for details. */
1566 if (repos_node_status != svn_wc_status_added)
1567 return SVN_NO_ERROR;
1569 /* Use the public API to get a statstruct, and put it into the hash. */
1570 SVN_ERR(internal_status(&i_stat, db, local_abspath,
1571 check_working_copy, pool, scratch_pool));
1572 statstruct = &i_stat->s;
1573 statstruct->repos_lock = repos_lock;
1574 svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
1577 /* Merge a repos "delete" + "add" into a single "replace". */
1578 if ((repos_node_status == svn_wc_status_added)
1579 && (statstruct->repos_node_status == svn_wc_status_deleted))
1580 repos_node_status = svn_wc_status_replaced;
1582 /* Tweak the structure's repos fields. */
1583 if (repos_node_status)
1584 statstruct->repos_node_status = repos_node_status;
1585 if (repos_text_status)
1586 statstruct->repos_text_status = repos_text_status;
1587 if (repos_prop_status)
1588 statstruct->repos_prop_status = repos_prop_status;
1590 /* Copy out-of-date info. */
1593 struct dir_baton *b = this_dir_baton;
1595 if (!statstruct->repos_relpath && b->repos_relpath)
1597 if (statstruct->repos_node_status == svn_wc_status_deleted)
1599 /* When deleting PATH, BATON is for PATH's parent,
1600 so we must construct PATH's real statstruct->url. */
1601 statstruct->repos_relpath =
1602 svn_relpath_join(b->repos_relpath,
1603 svn_dirent_basename(local_abspath,
1608 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1610 statstruct->repos_root_url =
1611 b->edit_baton->anchor_status->s.repos_root_url;
1612 statstruct->repos_uuid =
1613 b->edit_baton->anchor_status->s.repos_uuid;
1616 /* The last committed date, and author for deleted items
1618 if (statstruct->repos_node_status == svn_wc_status_deleted)
1620 statstruct->ood_kind = statstruct->kind;
1622 /* Pre 1.5 servers don't provide the revision a path was deleted.
1623 So we punt and use the last committed revision of the path's
1624 parent, which has some chance of being correct. At worse it
1625 is a higher revision than the path was deleted, but this is
1626 better than nothing... */
1627 if (deleted_rev == SVN_INVALID_REVNUM)
1628 statstruct->ood_changed_rev =
1629 ((struct dir_baton *) baton)->ood_changed_rev;
1631 statstruct->ood_changed_rev = deleted_rev;
1635 statstruct->ood_kind = b->ood_kind;
1636 statstruct->ood_changed_rev = b->ood_changed_rev;
1637 statstruct->ood_changed_date = b->ood_changed_date;
1638 if (b->ood_changed_author)
1639 statstruct->ood_changed_author =
1640 apr_pstrdup(pool, b->ood_changed_author);
1646 struct file_baton *b = baton;
1647 statstruct->ood_changed_rev = b->ood_changed_rev;
1648 statstruct->ood_changed_date = b->ood_changed_date;
1649 if (!statstruct->repos_relpath && b->repos_relpath)
1651 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1652 statstruct->repos_root_url =
1653 b->edit_baton->anchor_status->s.repos_root_url;
1654 statstruct->repos_uuid =
1655 b->edit_baton->anchor_status->s.repos_uuid;
1657 statstruct->ood_kind = b->ood_kind;
1658 if (b->ood_changed_author)
1659 statstruct->ood_changed_author =
1660 apr_pstrdup(pool, b->ood_changed_author);
1662 return SVN_NO_ERROR;
1665 /* Returns the URL for DB */
1667 find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
1669 /* If we have no name, we're the root, return the anchor URL. */
1671 return db->edit_baton->anchor_status->s.repos_relpath;
1674 const char *repos_relpath;
1675 struct dir_baton *pb = db->parent_baton;
1676 const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
1678 /* Note that status->repos_relpath could be NULL in the case of a missing
1679 * directory, which means we need to recurse up another level to get
1680 * a useful relpath. */
1681 if (status && status->repos_relpath)
1682 return status->repos_relpath;
1684 repos_relpath = find_dir_repos_relpath(pb, pool);
1685 return svn_relpath_join(repos_relpath, db->name, pool);
1691 /* Create a new dir_baton for subdir PATH. */
1692 static svn_error_t *
1693 make_dir_baton(void **dir_baton,
1695 struct edit_baton *edit_baton,
1696 struct dir_baton *parent_baton,
1697 apr_pool_t *result_pool)
1699 struct dir_baton *pb = parent_baton;
1700 struct edit_baton *eb = edit_baton;
1701 struct dir_baton *d;
1702 const char *local_abspath;
1703 const svn_wc__internal_status_t *status_in_parent;
1704 apr_pool_t *dir_pool;
1707 dir_pool = svn_pool_create(parent_baton->pool);
1709 dir_pool = svn_pool_create(result_pool);
1711 d = apr_pcalloc(dir_pool, sizeof(*d));
1713 SVN_ERR_ASSERT(path || (! pb));
1715 /* Construct the absolute path of this directory. */
1717 local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
1719 local_abspath = eb->anchor_abspath;
1721 /* Finish populating the baton members. */
1723 d->local_abspath = local_abspath;
1724 d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
1725 d->edit_baton = edit_baton;
1726 d->parent_baton = parent_baton;
1727 d->statii = apr_hash_make(dir_pool);
1728 d->ood_changed_rev = SVN_INVALID_REVNUM;
1729 d->ood_changed_date = 0;
1730 d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
1731 d->ood_kind = svn_node_dir;
1732 d->ood_changed_author = NULL;
1738 else if (pb->depth == svn_depth_immediates)
1739 d->depth = svn_depth_empty;
1740 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1742 else if (pb->depth == svn_depth_unknown)
1743 /* This is only tentative, it can be overridden from d's entry
1745 d->depth = svn_depth_unknown;
1747 d->depth = svn_depth_infinity;
1751 d->depth = eb->default_depth;
1754 /* Get the status for this path's children. Of course, we only want
1755 to do this if the path is versioned as a directory. */
1757 status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
1759 status_in_parent = eb->anchor_status;
1761 if (status_in_parent
1762 && (status_in_parent->has_descendants)
1764 && (d->depth == svn_depth_unknown
1765 || d->depth == svn_depth_infinity
1766 || d->depth == svn_depth_files
1767 || d->depth == svn_depth_immediates)
1770 const svn_wc_status3_t *this_dir_status;
1771 const apr_array_header_t *ignores = eb->ignores;
1773 SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
1774 status_in_parent->s.repos_root_url,
1775 NULL /*parent_repos_relpath*/,
1776 status_in_parent->s.repos_uuid,
1778 NULL /* dirent */, ignores,
1779 d->depth == svn_depth_files
1781 : svn_depth_immediates,
1783 hash_stash, d->statii,
1784 eb->cancel_func, eb->cancel_baton,
1787 /* If we found a depth here, it should govern. */
1788 this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
1789 if (this_dir_status && this_dir_status->versioned
1790 && (d->depth == svn_depth_unknown
1791 || d->depth > status_in_parent->s.depth))
1793 d->depth = this_dir_status->depth;
1798 return SVN_NO_ERROR;
1802 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1803 NAME is just one component, not a path. */
1804 static struct file_baton *
1805 make_file_baton(struct dir_baton *parent_dir_baton,
1809 struct dir_baton *pb = parent_dir_baton;
1810 struct edit_baton *eb = pb->edit_baton;
1811 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1813 /* Finish populating the baton members. */
1814 f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1815 f->name = svn_dirent_basename(f->local_abspath, NULL);
1819 f->ood_changed_rev = SVN_INVALID_REVNUM;
1820 f->ood_changed_date = 0;
1821 f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
1823 f->ood_kind = svn_node_file;
1824 f->ood_changed_author = NULL;
1830 * Return a boolean answer to the question "Is @a status something that
1831 * should be reported?". @a no_ignore and @a get_all are the same as
1832 * svn_wc_get_status_editor4().
1834 * This implementation should match the filter in assemble_status()
1836 static svn_boolean_t
1837 is_sendable_status(const svn_wc__internal_status_t *i_status,
1838 svn_boolean_t no_ignore,
1839 svn_boolean_t get_all)
1841 const svn_wc_status3_t *status = &i_status->s;
1842 /* If the repository status was touched at all, it's interesting. */
1843 if (status->repos_node_status != svn_wc_status_none)
1846 /* If there is a lock in the repository, send it. */
1847 if (status->repos_lock)
1850 if (status->conflicted)
1853 /* If the item is ignored, and we don't want ignores, skip it. */
1854 if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
1857 /* If we want everything, we obviously want this single-item subset
1862 /* If the item is unversioned, display it. */
1863 if (status->node_status == svn_wc_status_unversioned)
1866 /* If the text, property or tree state is interesting, send it. */
1867 if ((status->node_status != svn_wc_status_none)
1868 && (status->node_status != svn_wc_status_normal))
1871 /* If it's switched, send it. */
1872 if (status->switched)
1875 /* If there is a lock token, send it. */
1876 if (status->versioned && status->lock)
1879 /* If the entry is associated with a changelist, send it. */
1880 if (status->changelist)
1883 if (status->moved_to_abspath)
1886 /* Otherwise, don't send it. */
1891 /* Baton for mark_status. */
1894 svn_wc_status_func4_t real_status_func; /* real status function */
1895 void *real_status_baton; /* real status baton */
1898 /* A status callback function which wraps the *real* status
1899 function/baton. It simply sets the "repos_node_status" field of the
1900 STATUS to svn_wc_status_deleted and passes it off to the real
1901 status func/baton. Implements svn_wc_status_func4_t */
1902 static svn_error_t *
1903 mark_deleted(void *baton,
1904 const char *local_abspath,
1905 const svn_wc_status3_t *status,
1906 apr_pool_t *scratch_pool)
1908 struct status_baton *sb = baton;
1909 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
1910 new_status->repos_node_status = svn_wc_status_deleted;
1911 return sb->real_status_func(sb->real_status_baton, local_abspath,
1912 new_status, scratch_pool);
1916 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1917 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1918 directory itself. Descend into subdirectories according to DEPTH.
1919 Also, if DIR_WAS_DELETED is set, each status that is reported
1920 through this function will have its repos_text_status field showing
1921 a deletion. Use POOL for all allocations. */
1922 static svn_error_t *
1923 handle_statii(struct edit_baton *eb,
1924 const char *dir_repos_root_url,
1925 const char *dir_repos_relpath,
1926 const char *dir_repos_uuid,
1928 svn_boolean_t dir_was_deleted,
1932 const apr_array_header_t *ignores = eb->ignores;
1933 apr_hash_index_t *hi;
1934 apr_pool_t *iterpool = svn_pool_create(pool);
1935 svn_wc_status_func4_t status_func = eb->status_func;
1936 void *status_baton = eb->status_baton;
1937 struct status_baton sb;
1939 if (dir_was_deleted)
1941 sb.real_status_func = eb->status_func;
1942 sb.real_status_baton = eb->status_baton;
1943 status_func = mark_deleted;
1947 /* Loop over all the statii still in our hash, handling each one. */
1948 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1950 const char *local_abspath = apr_hash_this_key(hi);
1951 svn_wc__internal_status_t *status = apr_hash_this_val(hi);
1953 /* Clear the subpool. */
1954 svn_pool_clear(iterpool);
1956 /* Now, handle the status. We don't recurse for svn_depth_immediates
1957 because we already have the subdirectories' statii. */
1958 if (status->has_descendants
1959 && (depth == svn_depth_unknown
1960 || depth == svn_depth_infinity))
1962 SVN_ERR(get_dir_status(&eb->wb,
1963 local_abspath, TRUE,
1964 dir_repos_root_url, dir_repos_relpath,
1968 ignores, depth, eb->get_all, eb->no_ignore,
1969 status_func, status_baton,
1970 eb->cancel_func, eb->cancel_baton,
1973 if (dir_was_deleted)
1974 status->s.repos_node_status = svn_wc_status_deleted;
1975 if (is_sendable_status(status, eb->no_ignore, eb->get_all))
1976 SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, &status->s,
1980 /* Destroy the subpool. */
1981 svn_pool_destroy(iterpool);
1983 return SVN_NO_ERROR;
1987 /*----------------------------------------------------------------------*/
1989 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1991 /* An svn_delta_editor_t function. */
1992 static svn_error_t *
1993 set_target_revision(void *edit_baton,
1994 svn_revnum_t target_revision,
1997 struct edit_baton *eb = edit_baton;
1998 *(eb->target_revision) = target_revision;
1999 return SVN_NO_ERROR;
2003 /* An svn_delta_editor_t function. */
2004 static svn_error_t *
2005 open_root(void *edit_baton,
2006 svn_revnum_t base_revision,
2010 struct edit_baton *eb = edit_baton;
2011 eb->root_opened = TRUE;
2012 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
2016 /* An svn_delta_editor_t function. */
2017 static svn_error_t *
2018 delete_entry(const char *path,
2019 svn_revnum_t revision,
2023 struct dir_baton *db = parent_baton;
2024 struct edit_baton *eb = db->edit_baton;
2025 const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
2027 /* Note: when something is deleted, it's okay to tweak the
2028 statushash immediately. No need to wait until close_file or
2029 close_dir, because there's no risk of having to honor the 'added'
2030 flag. We already know this item exists in the working copy. */
2031 SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, eb->wb.check_working_copy,
2033 svn_wc_status_deleted, 0, 0, revision, NULL, pool));
2035 /* Mark the parent dir -- it lost an entry (unless that parent dir
2036 is the root node and we're not supposed to report on the root
2038 if (db->parent_baton && (! *eb->target_basename))
2039 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,
2040 eb->db, eb->wb.check_working_copy,
2042 svn_wc_status_modified, svn_wc_status_modified,
2043 0, SVN_INVALID_REVNUM, NULL, pool));
2045 return SVN_NO_ERROR;
2049 /* An svn_delta_editor_t function. */
2050 static svn_error_t *
2051 add_directory(const char *path,
2053 const char *copyfrom_path,
2054 svn_revnum_t copyfrom_revision,
2058 struct dir_baton *pb = parent_baton;
2059 struct edit_baton *eb = pb->edit_baton;
2060 struct dir_baton *new_db;
2062 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
2064 /* Make this dir as added. */
2065 new_db = *child_baton;
2066 new_db->added = TRUE;
2068 /* Mark the parent as changed; it gained an entry. */
2069 pb->text_changed = TRUE;
2071 return SVN_NO_ERROR;
2075 /* An svn_delta_editor_t function. */
2076 static svn_error_t *
2077 open_directory(const char *path,
2079 svn_revnum_t base_revision,
2083 struct dir_baton *pb = parent_baton;
2084 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
2088 /* An svn_delta_editor_t function. */
2089 static svn_error_t *
2090 change_dir_prop(void *dir_baton,
2092 const svn_string_t *value,
2095 struct dir_baton *db = dir_baton;
2096 if (svn_wc_is_normal_prop(name))
2097 db->prop_changed = TRUE;
2099 /* Note any changes to the repository. */
2102 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2103 db->ood_changed_rev = SVN_STR_TO_REV(value->data);
2104 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2105 db->ood_changed_author = apr_pstrdup(db->pool, value->data);
2106 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2109 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
2110 db->ood_changed_date = tm;
2114 return SVN_NO_ERROR;
2119 /* An svn_delta_editor_t function. */
2120 static svn_error_t *
2121 close_directory(void *dir_baton,
2124 struct dir_baton *db = dir_baton;
2125 struct dir_baton *pb = db->parent_baton;
2126 struct edit_baton *eb = db->edit_baton;
2127 apr_pool_t *scratch_pool = db->pool;
2129 /* If nothing has changed and directory has no out of
2130 date descendants, return. */
2131 if (db->added || db->prop_changed || db->text_changed
2132 || db->ood_changed_rev != SVN_INVALID_REVNUM)
2134 enum svn_wc_status_kind repos_node_status;
2135 enum svn_wc_status_kind repos_text_status;
2136 enum svn_wc_status_kind repos_prop_status;
2138 /* If this is a new directory, add it to the statushash. */
2141 repos_node_status = svn_wc_status_added;
2142 repos_text_status = svn_wc_status_none;
2143 repos_prop_status = db->prop_changed ? svn_wc_status_added
2144 : svn_wc_status_none;
2148 repos_node_status = (db->text_changed || db->prop_changed)
2149 ? svn_wc_status_modified
2150 : svn_wc_status_none;
2151 repos_text_status = db->text_changed ? svn_wc_status_modified
2152 : svn_wc_status_none;
2153 repos_prop_status = db->prop_changed ? svn_wc_status_modified
2154 : svn_wc_status_none;
2157 /* Maybe add this directory to its parent's status hash. Note
2158 that tweak_statushash won't do anything if repos_text_status
2159 is not svn_wc_status_added. */
2162 /* ### When we add directory locking, we need to find a
2163 ### directory lock here. */
2164 SVN_ERR(tweak_statushash(pb, db, TRUE,
2165 eb->db, eb->wb.check_working_copy,
2167 repos_node_status, repos_text_status,
2168 repos_prop_status, SVN_INVALID_REVNUM, NULL,
2173 /* We're editing the root dir of the WC. As its repos
2174 status info isn't otherwise set, set it directly to
2175 trigger invocation of the status callback below. */
2176 eb->anchor_status->s.repos_node_status = repos_node_status;
2177 eb->anchor_status->s.repos_prop_status = repos_prop_status;
2178 eb->anchor_status->s.repos_text_status = repos_text_status;
2180 /* If the root dir is out of date set the ood info directly too. */
2181 if (db->ood_changed_rev != eb->anchor_status->s.revision)
2183 eb->anchor_status->s.ood_changed_rev = db->ood_changed_rev;
2184 eb->anchor_status->s.ood_changed_date = db->ood_changed_date;
2185 eb->anchor_status->s.ood_kind = db->ood_kind;
2186 eb->anchor_status->s.ood_changed_author =
2187 apr_pstrdup(pool, db->ood_changed_author);
2192 /* Handle this directory's statuses, and then note in the parent
2193 that this has been done. */
2194 if (pb && ! db->excluded)
2196 svn_boolean_t was_deleted = FALSE;
2197 svn_wc__internal_status_t *dir_status;
2199 /* See if the directory was deleted or replaced. */
2200 dir_status = svn_hash_gets(pb->statii, db->local_abspath);
2202 ((dir_status->s.repos_node_status == svn_wc_status_deleted)
2203 || (dir_status->s.repos_node_status == svn_wc_status_replaced)))
2206 /* Now do the status reporting. */
2207 SVN_ERR(handle_statii(eb,
2208 dir_status ? dir_status->s.repos_root_url : NULL,
2209 dir_status ? dir_status->s.repos_relpath : NULL,
2210 dir_status ? dir_status->s.repos_uuid : NULL,
2211 db->statii, was_deleted, db->depth, scratch_pool));
2212 if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
2214 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2215 &dir_status->s, scratch_pool));
2216 svn_hash_sets(pb->statii, db->local_abspath, NULL);
2220 /* If this is the top-most directory, and the operation had a
2221 target, we should only report the target. */
2222 if (*eb->target_basename)
2224 const svn_wc__internal_status_t *tgt_status;
2226 tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
2229 if (tgt_status->has_descendants)
2231 SVN_ERR(get_dir_status(&eb->wb,
2232 eb->target_abspath, TRUE,
2233 NULL, NULL, NULL, NULL,
2237 eb->get_all, eb->no_ignore,
2238 eb->status_func, eb->status_baton,
2239 eb->cancel_func, eb->cancel_baton,
2242 if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
2243 SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
2244 &tgt_status->s, scratch_pool));
2249 /* Otherwise, we report on all our children and ourself.
2250 Note that our directory couldn't have been deleted,
2251 because it is the root of the edit drive. */
2252 SVN_ERR(handle_statii(eb,
2253 eb->anchor_status->s.repos_root_url,
2254 eb->anchor_status->s.repos_relpath,
2255 eb->anchor_status->s.repos_uuid,
2256 db->statii, FALSE, eb->default_depth,
2258 if (is_sendable_status(eb->anchor_status, eb->no_ignore,
2260 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2261 &eb->anchor_status->s, scratch_pool));
2262 eb->anchor_status = NULL;
2266 svn_pool_clear(scratch_pool); /* Clear baton and its pool */
2268 return SVN_NO_ERROR;
2273 /* An svn_delta_editor_t function. */
2274 static svn_error_t *
2275 add_file(const char *path,
2277 const char *copyfrom_path,
2278 svn_revnum_t copyfrom_revision,
2282 struct dir_baton *pb = parent_baton;
2283 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2285 /* Mark parent dir as changed */
2286 pb->text_changed = TRUE;
2288 /* Make this file as added. */
2289 new_fb->added = TRUE;
2291 *file_baton = new_fb;
2292 return SVN_NO_ERROR;
2296 /* An svn_delta_editor_t function. */
2297 static svn_error_t *
2298 open_file(const char *path,
2300 svn_revnum_t base_revision,
2304 struct dir_baton *pb = parent_baton;
2305 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2307 *file_baton = new_fb;
2308 return SVN_NO_ERROR;
2312 /* An svn_delta_editor_t function. */
2313 static svn_error_t *
2314 apply_textdelta(void *file_baton,
2315 const char *base_checksum,
2317 svn_txdelta_window_handler_t *handler,
2318 void **handler_baton)
2320 struct file_baton *fb = file_baton;
2322 /* Mark file as having textual mods. */
2323 fb->text_changed = TRUE;
2325 /* Send back a NULL window handler -- we don't need the actual diffs. */
2326 *handler_baton = NULL;
2327 *handler = svn_delta_noop_window_handler;
2329 return SVN_NO_ERROR;
2333 /* An svn_delta_editor_t function. */
2334 static svn_error_t *
2335 change_file_prop(void *file_baton,
2337 const svn_string_t *value,
2340 struct file_baton *fb = file_baton;
2341 if (svn_wc_is_normal_prop(name))
2342 fb->prop_changed = TRUE;
2344 /* Note any changes to the repository. */
2347 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2348 fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
2349 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2350 fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
2352 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2355 SVN_ERR(svn_time_from_cstring(&tm, value->data,
2356 fb->dir_baton->pool));
2357 fb->ood_changed_date = tm;
2361 return SVN_NO_ERROR;
2365 /* An svn_delta_editor_t function. */
2366 static svn_error_t *
2367 close_file(void *file_baton,
2368 const char *text_checksum, /* ignored, as we receive no data */
2371 struct file_baton *fb = file_baton;
2372 enum svn_wc_status_kind repos_node_status;
2373 enum svn_wc_status_kind repos_text_status;
2374 enum svn_wc_status_kind repos_prop_status;
2375 const svn_lock_t *repos_lock = NULL;
2377 /* If nothing has changed, return. */
2378 if (! (fb->added || fb->prop_changed || fb->text_changed))
2379 return SVN_NO_ERROR;
2381 /* If this is a new file, add it to the statushash. */
2384 repos_node_status = svn_wc_status_added;
2385 repos_text_status = fb->text_changed ? svn_wc_status_modified
2386 : 0 /* don't tweak */;
2387 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2388 : 0 /* don't tweak */;
2390 if (fb->edit_baton->wb.repos_locks)
2392 const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
2395 /* repos_lock still uses the deprecated filesystem absolute path
2397 const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
2400 repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
2401 svn_fspath__join("/", repos_relpath,
2407 repos_node_status = (fb->text_changed || fb->prop_changed)
2408 ? svn_wc_status_modified
2409 : 0 /* don't tweak */;
2410 repos_text_status = fb->text_changed ? svn_wc_status_modified
2411 : 0 /* don't tweak */;
2412 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2413 : 0 /* don't tweak */;
2416 return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
2417 fb->edit_baton->wb.check_working_copy,
2418 fb->local_abspath, repos_node_status,
2419 repos_text_status, repos_prop_status,
2420 SVN_INVALID_REVNUM, repos_lock, pool);
2423 /* An svn_delta_editor_t function. */
2424 static svn_error_t *
2425 close_edit(void *edit_baton,
2428 struct edit_baton *eb = edit_baton;
2430 /* If we get here and the root was not opened as part of the edit,
2431 we need to transmit statuses for everything. Otherwise, we
2433 if (eb->root_opened)
2434 return SVN_NO_ERROR;
2436 SVN_ERR(svn_wc__internal_walk_status(eb->db,
2449 return SVN_NO_ERROR;
2454 /*** Public API ***/
2457 svn_wc__get_status_editor(const svn_delta_editor_t **editor,
2459 void **set_locks_baton,
2460 svn_revnum_t *edit_revision,
2461 svn_wc_context_t *wc_ctx,
2462 const char *anchor_abspath,
2463 const char *target_basename,
2465 svn_boolean_t get_all,
2466 svn_boolean_t check_working_copy,
2467 svn_boolean_t no_ignore,
2468 svn_boolean_t depth_as_sticky,
2469 svn_boolean_t server_performs_filtering,
2470 const apr_array_header_t *ignore_patterns,
2471 svn_wc_status_func4_t status_func,
2473 svn_cancel_func_t cancel_func,
2475 apr_pool_t *result_pool,
2476 apr_pool_t *scratch_pool)
2478 struct edit_baton *eb;
2479 svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
2481 struct svn_wc__shim_fetch_baton_t *sfb;
2482 const svn_delta_editor_t *inner_editor;
2483 svn_delta_shim_callbacks_t *shim_callbacks =
2484 svn_delta_shim_callbacks_default(result_pool);
2486 /* Construct an edit baton. */
2487 eb = apr_pcalloc(result_pool, sizeof(*eb));
2488 eb->default_depth = depth;
2489 eb->target_revision = edit_revision;
2490 eb->db = wc_ctx->db;
2491 eb->get_all = get_all;
2492 eb->no_ignore = no_ignore;
2493 eb->status_func = status_func;
2494 eb->status_baton = status_baton;
2495 eb->cancel_func = cancel_func;
2496 eb->cancel_baton = cancel_baton;
2497 eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
2498 eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
2501 eb->target_basename = apr_pstrdup(result_pool, target_basename);
2502 eb->root_opened = FALSE;
2504 eb->wb.db = wc_ctx->db;
2505 eb->wb.target_abspath = eb->target_abspath;
2506 eb->wb.ignore_text_mods = !check_working_copy;
2507 eb->wb.check_working_copy = check_working_copy;
2508 eb->wb.repos_locks = NULL;
2509 eb->wb.repos_root = NULL;
2511 SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
2512 wc_ctx->db, eb->target_abspath,
2513 result_pool, scratch_pool));
2515 /* Use the caller-provided ignore patterns if provided; the build-time
2516 configured defaults otherwise. */
2517 if (ignore_patterns)
2519 eb->ignores = ignore_patterns;
2523 apr_array_header_t *ignores;
2525 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
2526 eb->ignores = ignores;
2529 /* The edit baton's status structure maps to PATH, and the editor
2530 have to be aware of whether that is the anchor or the target. */
2531 SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
2532 check_working_copy, result_pool, scratch_pool));
2534 /* Construct an editor. */
2535 tree_editor->set_target_revision = set_target_revision;
2536 tree_editor->open_root = open_root;
2537 tree_editor->delete_entry = delete_entry;
2538 tree_editor->add_directory = add_directory;
2539 tree_editor->open_directory = open_directory;
2540 tree_editor->change_dir_prop = change_dir_prop;
2541 tree_editor->close_directory = close_directory;
2542 tree_editor->add_file = add_file;
2543 tree_editor->open_file = open_file;
2544 tree_editor->apply_textdelta = apply_textdelta;
2545 tree_editor->change_file_prop = change_file_prop;
2546 tree_editor->close_file = close_file;
2547 tree_editor->close_edit = close_edit;
2549 inner_editor = tree_editor;
2552 if (!server_performs_filtering
2553 && !depth_as_sticky)
2554 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2563 /* Conjoin a cancellation editor with our status editor. */
2564 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2565 inner_editor, inner_baton,
2569 if (set_locks_baton)
2570 *set_locks_baton = eb;
2572 sfb = apr_palloc(result_pool, sizeof(*sfb));
2573 sfb->db = wc_ctx->db;
2574 sfb->base_abspath = eb->anchor_abspath;
2575 sfb->fetch_base = FALSE;
2577 shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2578 shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2579 shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2580 shim_callbacks->fetch_baton = sfb;
2582 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2583 NULL, NULL, shim_callbacks,
2584 result_pool, scratch_pool));
2586 return SVN_NO_ERROR;
2589 /* Like svn_io_stat_dirent, but works case sensitive inside working
2590 copies. Before 1.8 we handled this with a selection filter inside
2592 static svn_error_t *
2593 stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
2595 const char *local_abspath,
2596 apr_pool_t *result_pool,
2597 apr_pool_t *scratch_pool)
2599 svn_boolean_t is_wcroot;
2601 /* The wcroot is "" inside the wc; handle it as not in the wc, as
2602 the case of the root is indifferent to us. */
2604 /* Note that for performance this is really just a few hashtable lookups,
2605 as we just used local_abspath for a db call in both our callers */
2606 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
2609 return svn_error_trace(
2610 svn_io_stat_dirent2(dirent, local_abspath,
2611 ! is_wcroot /* verify_truename */,
2612 TRUE /* ignore_enoent */,
2613 result_pool, scratch_pool));
2617 svn_wc__internal_walk_status(svn_wc__db_t *db,
2618 const char *local_abspath,
2620 svn_boolean_t get_all,
2621 svn_boolean_t no_ignore,
2622 svn_boolean_t ignore_text_mods,
2623 const apr_array_header_t *ignore_patterns,
2624 svn_wc_status_func4_t status_func,
2626 svn_cancel_func_t cancel_func,
2628 apr_pool_t *scratch_pool)
2630 struct walk_status_baton wb;
2631 const svn_io_dirent2_t *dirent;
2632 const struct svn_wc__db_info_t *info;
2636 wb.target_abspath = local_abspath;
2637 wb.ignore_text_mods = ignore_text_mods;
2638 wb.check_working_copy = TRUE;
2639 wb.repos_root = NULL;
2640 wb.repos_locks = NULL;
2642 /* Use the caller-provided ignore patterns if provided; the build-time
2643 configured defaults otherwise. */
2644 if (!ignore_patterns)
2646 apr_array_header_t *ignores;
2648 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
2649 ignore_patterns = ignores;
2652 err = svn_wc__db_read_single_info(&info, db, local_abspath,
2653 FALSE /* base_tree_only */,
2654 scratch_pool, scratch_pool);
2658 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2660 svn_error_clear(err);
2664 return svn_error_trace(err);
2666 wb.externals = apr_hash_make(scratch_pool);
2668 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2669 scratch_pool, scratch_pool));
2673 SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
2675 scratch_pool, scratch_pool));
2677 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2678 scratch_pool, scratch_pool));
2682 && info->has_descendants /* is dir, or was dir and has tc descendants */
2683 && info->status != svn_wc__db_status_not_present
2684 && info->status != svn_wc__db_status_excluded
2685 && info->status != svn_wc__db_status_server_excluded)
2687 SVN_ERR(get_dir_status(&wb,
2689 FALSE /* skip_root */,
2697 status_func, status_baton,
2698 cancel_func, cancel_baton,
2703 /* It may be a file or an unversioned item. And this is an explicit
2704 * target, so no ignoring. An unversioned item (file or dir) shows a
2705 * status like '?', and can yield a tree conflicted path. */
2706 err = get_child_status(&wb,
2712 status_func, status_baton,
2713 cancel_func, cancel_baton,
2716 if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2718 /* The parent is also not versioned, but it is not nice to show
2719 an error about a path a user didn't intend to touch. */
2720 svn_error_clear(err);
2721 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
2722 _("The node '%s' was not found."),
2723 svn_dirent_local_style(local_abspath,
2729 return SVN_NO_ERROR;
2733 svn_wc_walk_status(svn_wc_context_t *wc_ctx,
2734 const char *local_abspath,
2736 svn_boolean_t get_all,
2737 svn_boolean_t no_ignore,
2738 svn_boolean_t ignore_text_mods,
2739 const apr_array_header_t *ignore_patterns,
2740 svn_wc_status_func4_t status_func,
2742 svn_cancel_func_t cancel_func,
2744 apr_pool_t *scratch_pool)
2746 return svn_error_trace(
2747 svn_wc__internal_walk_status(wc_ctx->db,
2763 svn_wc_status_set_repos_locks(void *edit_baton,
2765 const char *repos_root,
2768 struct edit_baton *eb = edit_baton;
2770 eb->wb.repos_locks = locks;
2771 eb->wb.repos_root = apr_pstrdup(pool, repos_root);
2773 return SVN_NO_ERROR;
2778 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2782 svn_config_t *cfg = config
2783 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
2787 /* Check the Subversion run-time configuration for global ignores.
2788 If no configuration value exists, we fall back to our defaults. */
2789 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2790 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2791 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2792 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2794 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2795 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2796 return SVN_NO_ERROR;
2801 static svn_error_t *
2802 internal_status(svn_wc__internal_status_t **status,
2804 const char *local_abspath,
2805 svn_boolean_t check_working_copy,
2806 apr_pool_t *result_pool,
2807 apr_pool_t *scratch_pool)
2809 const svn_io_dirent2_t *dirent = NULL;
2810 const char *parent_repos_relpath;
2811 const char *parent_repos_root_url;
2812 const char *parent_repos_uuid;
2813 const struct svn_wc__db_info_t *info;
2814 svn_boolean_t is_root = FALSE;
2817 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2819 err = svn_wc__db_read_single_info(&info, db, local_abspath,
2820 !check_working_copy,
2821 scratch_pool, scratch_pool);
2825 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
2826 return svn_error_trace(err);
2828 svn_error_clear(err);
2831 if (check_working_copy)
2832 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2833 scratch_pool, scratch_pool));
2835 else if (check_working_copy)
2836 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2837 scratch_pool, scratch_pool));
2840 || info->kind == svn_node_unknown
2841 || info->status == svn_wc__db_status_not_present
2842 || info->status == svn_wc__db_status_server_excluded
2843 || info->status == svn_wc__db_status_excluded)
2844 return svn_error_trace(assemble_unversioned(status,
2847 info ? info->conflicted : FALSE,
2848 FALSE /* is_ignored */,
2849 result_pool, scratch_pool));
2851 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
2854 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
2856 /* Even though passing parent_repos_* is not required, assemble_status needs
2857 these values to determine if a node is switched */
2860 const char *const parent_abspath = svn_dirent_dirname(local_abspath,
2862 if (check_working_copy)
2863 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL,
2864 &parent_repos_relpath,
2865 &parent_repos_root_url,
2867 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2868 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2869 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2871 result_pool, scratch_pool));
2873 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL,
2874 &parent_repos_relpath,
2875 &parent_repos_root_url,
2877 NULL, NULL, NULL, NULL, NULL,
2878 NULL, NULL, NULL, NULL, NULL,
2880 result_pool, scratch_pool));
2884 parent_repos_root_url = NULL;
2885 parent_repos_relpath = NULL;
2886 parent_repos_uuid = NULL;
2889 return svn_error_trace(assemble_status(status, db, local_abspath,
2890 parent_repos_root_url,
2891 parent_repos_relpath,
2896 FALSE, check_working_copy,
2897 NULL /* repos_lock */,
2898 result_pool, scratch_pool));
2903 svn_wc_status3(svn_wc_status3_t **status,
2904 svn_wc_context_t *wc_ctx,
2905 const char *local_abspath,
2906 apr_pool_t *result_pool,
2907 apr_pool_t *scratch_pool)
2909 svn_wc__internal_status_t *stat;
2910 SVN_ERR(internal_status(&stat, wc_ctx->db, local_abspath,
2911 TRUE /* check_working_copy */,
2912 result_pool, scratch_pool));
2914 return SVN_NO_ERROR;
2918 svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
2921 /* Allocate slightly more room */
2922 svn_wc__internal_status_t *new_istat = apr_palloc(pool, sizeof(*new_istat));
2923 svn_wc_status3_t *new_stat = &new_istat->s;
2925 /* Shallow copy all members. */
2926 *new_stat = *orig_stat;
2928 /* Now go back and dup the deep items into this pool. */
2929 if (orig_stat->repos_lock)
2930 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2932 if (orig_stat->changed_author)
2933 new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
2935 if (orig_stat->ood_changed_author)
2936 new_stat->ood_changed_author
2937 = apr_pstrdup(pool, orig_stat->ood_changed_author);
2939 if (orig_stat->lock)
2940 new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
2942 if (orig_stat->changelist)
2943 new_stat->changelist
2944 = apr_pstrdup(pool, orig_stat->changelist);
2946 if (orig_stat->repos_root_url)
2947 new_stat->repos_root_url
2948 = apr_pstrdup(pool, orig_stat->repos_root_url);
2950 if (orig_stat->repos_relpath)
2951 new_stat->repos_relpath
2952 = apr_pstrdup(pool, orig_stat->repos_relpath);
2954 if (orig_stat->repos_uuid)
2955 new_stat->repos_uuid
2956 = apr_pstrdup(pool, orig_stat->repos_uuid);
2958 if (orig_stat->moved_from_abspath)
2959 new_stat->moved_from_abspath
2960 = apr_pstrdup(pool, orig_stat->moved_from_abspath);
2962 if (orig_stat->moved_to_abspath)
2963 new_stat->moved_to_abspath
2964 = apr_pstrdup(pool, orig_stat->moved_to_abspath);
2966 /* Return the new hotness. */
2971 svn_wc_get_ignores2(apr_array_header_t **patterns,
2972 svn_wc_context_t *wc_ctx,
2973 const char *local_abspath,
2975 apr_pool_t *result_pool,
2976 apr_pool_t *scratch_pool)
2978 apr_array_header_t *default_ignores;
2980 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
2981 return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
2984 result_pool, scratch_pool));