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;
72 svn_boolean_t op_root;
74 /* Make sure to update hash_stash() when adding values here */
75 } svn_wc__internal_status_t;
78 /*** Baton used for walking the local status */
79 struct walk_status_baton
81 /* The DB handle for managing the working copy state. */
84 /*** External handling ***/
85 /* Target of the status */
86 const char *target_abspath;
88 /* Should we ignore text modifications? */
89 svn_boolean_t ignore_text_mods;
91 /* Scan the working copy for local modifications and missing nodes. */
92 svn_boolean_t check_working_copy;
94 /* Externals info harvested during the status run. */
95 apr_hash_t *externals;
97 /*** Repository lock handling ***/
98 /* The repository root URL, if set. */
99 const char *repos_root;
101 /* Repository locks, if set. */
102 apr_hash_t *repos_locks;
105 /*** Editor batons ***/
109 /* For status, the "destination" of the edit. */
110 const char *anchor_abspath;
111 const char *target_abspath;
112 const char *target_basename;
114 /* The DB handle for managing the working copy state. */
117 /* The overall depth of this edit (a dir baton may override this).
119 * If this is svn_depth_unknown, the depths found in the working
120 * copy will govern the edit; or if the edit depth indicates a
121 * descent deeper than the found depths are capable of, the found
122 * depths also govern, of course (there's no point descending into
123 * something that's not there).
125 svn_depth_t default_depth;
127 /* Do we want all statuses (instead of just the interesting ones) ? */
128 svn_boolean_t get_all;
130 /* Ignore the svn:ignores. */
131 svn_boolean_t no_ignore;
133 /* The comparison revision in the repository. This is a reference
134 because this editor returns this rev to the driver directly, as
135 well as in each statushash entry. */
136 svn_revnum_t *target_revision;
138 /* Status function/baton. */
139 svn_wc_status_func4_t status_func;
142 /* Cancellation function/baton. */
143 svn_cancel_func_t cancel_func;
146 /* The configured set of default ignores. */
147 const apr_array_header_t *ignores;
149 /* Status item for the path represented by the anchor of the edit. */
150 svn_wc__internal_status_t *anchor_status;
152 /* Was open_root() called for this edit drive? */
153 svn_boolean_t root_opened;
155 /* The local status baton */
156 struct walk_status_baton wb;
162 /* The path to this directory. */
163 const char *local_abspath;
165 /* Basename of this directory. */
168 /* The global edit baton. */
169 struct edit_baton *edit_baton;
171 /* Baton for this directory's parent, or NULL if this is the root
173 struct dir_baton *parent_baton;
175 /* The ambient requested depth below this point in the edit. This
176 can differ from the parent baton's depth (with the edit baton
177 considered the ultimate parent baton). For example, if the
178 parent baton has svn_depth_immediates, then here we should have
179 svn_depth_empty, because there would be no further recursion, not
180 even to file children. */
183 /* Is this directory filtered out due to depth? (Note that if this
184 is TRUE, the depth field is undefined.) */
185 svn_boolean_t excluded;
187 /* 'svn status' shouldn't print status lines for things that are
188 added; we're only interest in asking if objects that the user
189 *already* has are up-to-date or not. Thus if this flag is set,
190 the next two will be ignored. :-) */
193 /* Gets set iff there's a change to this directory's properties, to
194 guide us when syncing adm files later. */
195 svn_boolean_t prop_changed;
197 /* This means (in terms of 'svn status') that some child was deleted
198 or added to the directory */
199 svn_boolean_t text_changed;
201 /* Working copy status structures for children of this directory.
202 This hash maps const char * abspaths to svn_wc_status3_t *
206 /* The pool in which this baton itself is allocated. */
209 /* The repository root relative path to this item in the repository. */
210 const char *repos_relpath;
212 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
213 svn_node_kind_t ood_kind;
214 svn_revnum_t ood_changed_rev;
215 apr_time_t ood_changed_date;
216 const char *ood_changed_author;
222 /* Absolute local path to this file */
223 const char *local_abspath;
225 /* The global edit baton. */
226 struct edit_baton *edit_baton;
228 /* Baton for this file's parent directory. */
229 struct dir_baton *dir_baton;
231 /* Pool specific to this file_baton. */
234 /* Basename of this file */
237 /* 'svn status' shouldn't print status lines for things that are
238 added; we're only interest in asking if objects that the user
239 *already* has are up-to-date or not. Thus if this flag is set,
240 the next two will be ignored. :-) */
243 /* This gets set if the file underwent a text change, which guides
244 the code that syncs up the adm dir and working copy. */
245 svn_boolean_t text_changed;
247 /* This gets set if the file underwent a prop change, which guides
248 the code that syncs up the adm dir and working copy. */
249 svn_boolean_t prop_changed;
251 /* The repository root relative path to this item in the repository. */
252 const char *repos_relpath;
254 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
255 svn_node_kind_t ood_kind;
256 svn_revnum_t ood_changed_rev;
257 apr_time_t ood_changed_date;
259 const char *ood_changed_author;
267 /* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
268 information in INFO if available, falling back on
269 PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
270 finally falling back on querying DB. */
272 get_repos_root_url_relpath(const char **repos_relpath,
273 const char **repos_root_url,
274 const char **repos_uuid,
275 const struct svn_wc__db_info_t *info,
276 const char *parent_repos_relpath,
277 const char *parent_repos_root_url,
278 const char *parent_repos_uuid,
280 const char *local_abspath,
281 apr_pool_t *result_pool,
282 apr_pool_t *scratch_pool)
284 if (info->repos_relpath && info->repos_root_url)
286 *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
287 *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
288 *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
290 else if (parent_repos_relpath && parent_repos_root_url)
292 *repos_relpath = svn_relpath_join(parent_repos_relpath,
293 svn_dirent_basename(local_abspath,
296 *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
297 *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
301 SVN_ERR(svn_wc__db_read_repos_info(NULL,
302 repos_relpath, repos_root_url,
305 result_pool, scratch_pool));
312 internal_status(svn_wc__internal_status_t **status,
314 const char *local_abspath,
315 svn_boolean_t check_working_copy,
316 apr_pool_t *result_pool,
317 apr_pool_t *scratch_pool);
319 /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
320 RESULT_POOL and use SCRATCH_POOL for temporary allocations.
322 PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
323 and repository relative path of the parent of LOCAL_ABSPATH or NULL if
324 LOCAL_ABSPATH doesn't have a versioned parent directory.
326 DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
327 NULL if the node does not exist on disk.
329 If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
330 *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
331 allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
332 don't check for text mods, assume there are none and set and *STATUS
333 returned to reflect that assumption. If CHECK_WORKING_COPY is FALSE,
334 do not adjust the result for missing working copy files.
336 The status struct's repos_lock field will be set to REPOS_LOCK.
339 assemble_status(svn_wc__internal_status_t **status,
341 const char *local_abspath,
342 const char *parent_repos_root_url,
343 const char *parent_repos_relpath,
344 const char *parent_repos_uuid,
345 const struct svn_wc__db_info_t *info,
346 const svn_io_dirent2_t *dirent,
347 svn_boolean_t get_all,
348 svn_boolean_t ignore_text_mods,
349 svn_boolean_t check_working_copy,
350 const svn_lock_t *repos_lock,
351 apr_pool_t *result_pool,
352 apr_pool_t *scratch_pool)
354 svn_wc__internal_status_t *inner_stat;
355 svn_wc_status3_t *stat;
356 svn_boolean_t switched_p = FALSE;
357 svn_boolean_t copied = FALSE;
358 svn_boolean_t conflicted;
359 const char *moved_from_abspath = NULL;
361 /* Defaults for two main variables. */
362 enum svn_wc_status_kind node_status = svn_wc_status_normal;
363 enum svn_wc_status_kind text_status = svn_wc_status_normal;
364 enum svn_wc_status_kind prop_status = svn_wc_status_none;
367 if (!info->repos_relpath || !parent_repos_relpath)
371 /* A node is switched if it doesn't have the implied repos_relpath */
372 const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
373 info->repos_relpath);
374 switched_p = !name || (strcmp(name,
375 svn_dirent_basename(local_abspath, NULL))
379 if (info->status == svn_wc__db_status_incomplete || info->incomplete)
381 /* Highest precedence. */
382 node_status = svn_wc_status_incomplete;
384 else if (info->status == svn_wc__db_status_deleted)
386 node_status = svn_wc_status_deleted;
388 if (!info->have_base || info->have_more_work || info->copied)
390 else if (!info->have_more_work && info->have_base)
394 const char *work_del_abspath;
396 /* Find out details of our deletion. */
397 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
398 &work_del_abspath, NULL,
400 scratch_pool, scratch_pool));
401 if (work_del_abspath)
402 copied = TRUE; /* Working deletion */
405 else if (check_working_copy)
407 /* Examine whether our target is missing or obstructed. To detect
408 * obstructions, we have to look at the on-disk status in DIRENT. */
409 svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
413 if (!dirent || dirent->kind != expected_kind)
415 /* A present or added node should be on disk, so it is
416 reported missing or obstructed. */
417 if (!dirent || dirent->kind == svn_node_none)
418 node_status = svn_wc_status_missing;
420 node_status = svn_wc_status_obstructed;
424 /* Does the node have props? */
425 if (info->status != svn_wc__db_status_deleted)
428 prop_status = svn_wc_status_modified;
429 else if (info->had_props)
430 prop_status = svn_wc_status_normal;
433 /* If NODE_STATUS is still normal, after the above checks, then
434 we should proceed to refine the status.
436 If it was changed, then the subdir is incomplete or missing/obstructed.
438 if (info->kind != svn_node_dir
439 && node_status == svn_wc_status_normal)
441 svn_boolean_t text_modified_p = FALSE;
443 /* Implement predecence rules: */
445 /* 1. Set the two main variables to "discovered" values first (M, C).
446 Together, these two stati are of lowest precedence, and C has
447 precedence over M. */
449 /* If the entry is a file, check for textual modifications */
450 if ((info->kind == svn_node_file
451 || info->kind == svn_node_symlink)
453 && (info->special == (dirent && dirent->special))
454 #endif /* HAVE_SYMLINK */
457 /* If the on-disk dirent exactly matches the expected state
458 skip all operations in svn_wc__internal_text_modified_p()
459 to avoid an extra filestat for every file, which can be
460 expensive on network drives as a filestat usually can't
462 if (!info->has_checksum)
463 text_modified_p = TRUE; /* Local addition -> Modified */
464 else if (ignore_text_mods
466 && info->recorded_size != SVN_INVALID_FILESIZE
467 && info->recorded_time != 0
468 && info->recorded_size == dirent->filesize
469 && info->recorded_time == dirent->mtime))
470 text_modified_p = FALSE;
474 err = svn_wc__internal_file_modified_p(&text_modified_p,
476 FALSE, scratch_pool);
480 if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
481 return svn_error_trace(err);
483 /* An access denied is very common on Windows when another
484 application has the file open. Previously we ignored
485 this error in svn_wc__text_modified_internal_p, where it
486 should have really errored. */
487 svn_error_clear(err);
488 text_modified_p = TRUE;
493 else if (info->special != (dirent && dirent->special))
494 node_status = svn_wc_status_obstructed;
495 #endif /* HAVE_SYMLINK */
498 text_status = svn_wc_status_modified;
501 conflicted = info->conflicted;
504 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
506 /* ### Check if the conflict was resolved by removing the marker files.
507 ### This should really be moved to the users of this API */
508 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
510 db, local_abspath, scratch_pool));
512 if (!text_conflicted && !prop_conflicted && !tree_conflicted)
516 if (node_status == svn_wc_status_normal)
518 /* 2. Possibly overwrite the text_status variable with "scheduled"
519 states from the entry (A, D, R). As a group, these states are
520 of medium precedence. They also override any C or M that may
521 be in the prop_status field at this point, although they do not
522 override a C text status.*/
523 if (info->status == svn_wc__db_status_added)
525 copied = info->copied;
527 { /* Keep status normal */ }
528 else if (!info->have_base && !info->have_more_work)
530 /* Simple addition or copy, no replacement */
531 node_status = svn_wc_status_added;
535 svn_wc__db_status_t below_working;
536 svn_boolean_t have_base, have_work;
538 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
543 /* If the node is not present or deleted (read: not present
544 in working), then the node is not a replacement */
545 if (below_working != svn_wc__db_status_not_present
546 && below_working != svn_wc__db_status_deleted)
548 node_status = svn_wc_status_replaced;
551 node_status = svn_wc_status_added;
554 /* Get moved-from info (only for potential op-roots of a move). */
555 if (info->moved_here && info->op_root)
558 err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
560 result_pool, scratch_pool);
564 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
565 return svn_error_trace(err);
567 svn_error_clear(err);
568 /* We are no longer moved... So most likely we are somehow
569 changing the db for things like resolving conflicts. */
571 moved_from_abspath = NULL;
578 if (node_status == svn_wc_status_normal)
579 node_status = text_status;
581 if (node_status == svn_wc_status_normal
582 && prop_status != svn_wc_status_none)
583 node_status = prop_status;
585 /* 5. Easy out: unless we're fetching -every- node, don't bother
586 to allocate a struct for an uninteresting node.
588 This filter should match the filter in is_sendable_status() */
590 if (((node_status == svn_wc_status_none)
591 || (node_status == svn_wc_status_normal)
592 || (node_status == svn_wc_status_deleted && !info->op_root))
598 && (! info->changelist)
600 && (! info->moved_to))
606 /* 6. Build and return a status structure. */
608 inner_stat = apr_pcalloc(result_pool, sizeof(*inner_stat));
609 stat = &inner_stat->s;
610 inner_stat->has_descendants = info->has_descendants;
611 inner_stat->op_root = info->op_root;
616 stat->kind = svn_node_dir;
619 case svn_node_symlink:
620 stat->kind = svn_node_file;
622 case svn_node_unknown:
624 stat->kind = svn_node_unknown;
626 stat->depth = info->depth;
630 stat->filesize = (dirent->kind == svn_node_file)
632 : SVN_INVALID_FILESIZE;
633 stat->actual_kind = dirent->special ? svn_node_symlink
638 stat->filesize = SVN_INVALID_FILESIZE;
639 stat->actual_kind = ignore_text_mods ? svn_node_unknown
643 stat->node_status = node_status;
644 stat->text_status = text_status;
645 stat->prop_status = prop_status;
646 stat->repos_node_status = svn_wc_status_none; /* default */
647 stat->repos_text_status = svn_wc_status_none; /* default */
648 stat->repos_prop_status = svn_wc_status_none; /* default */
649 stat->switched = switched_p;
650 stat->copied = copied;
651 stat->repos_lock = repos_lock;
652 stat->revision = info->revnum;
653 stat->changed_rev = info->changed_rev;
654 if (info->changed_author)
655 stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
656 stat->changed_date = info->changed_date;
658 stat->ood_kind = svn_node_none;
659 stat->ood_changed_rev = SVN_INVALID_REVNUM;
660 stat->ood_changed_date = 0;
661 stat->ood_changed_author = NULL;
663 SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
664 &stat->repos_root_url,
665 &stat->repos_uuid, info,
666 parent_repos_relpath,
667 parent_repos_root_url,
670 result_pool, scratch_pool));
674 svn_lock_t *lck = svn_lock_create(result_pool);
675 lck->path = stat->repos_relpath;
676 lck->token = info->lock->token;
677 lck->owner = info->lock->owner;
678 lck->comment = info->lock->comment;
679 lck->creation_date = info->lock->date;
685 stat->locked = info->locked;
686 stat->conflicted = conflicted;
687 stat->versioned = TRUE;
688 if (info->changelist)
689 stat->changelist = apr_pstrdup(result_pool, info->changelist);
691 stat->moved_from_abspath = moved_from_abspath;
693 /* ### TODO: Handle multiple moved_to values properly */
695 stat->moved_to_abspath = apr_pstrdup(result_pool,
696 info->moved_to->moved_to_abspath);
698 stat->file_external = info->file_external;
700 *status = inner_stat;
705 /* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
706 available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
707 temporary allocations.
709 If IS_IGNORED is non-zero and this is a non-versioned entity, set
710 the node_status to svn_wc_status_none. Otherwise set the
711 node_status to svn_wc_status_unversioned.
714 assemble_unversioned(svn_wc__internal_status_t **status,
716 const char *local_abspath,
717 const svn_io_dirent2_t *dirent,
718 svn_boolean_t tree_conflicted,
719 svn_boolean_t is_ignored,
720 apr_pool_t *result_pool,
721 apr_pool_t *scratch_pool)
723 svn_wc__internal_status_t *inner_status;
724 svn_wc_status3_t *stat;
726 /* return a fairly blank structure. */
727 inner_status = apr_pcalloc(result_pool, sizeof(*inner_status));
728 stat = &inner_status->s;
730 /*stat->versioned = FALSE;*/
731 stat->kind = svn_node_unknown; /* not versioned */
732 stat->depth = svn_depth_unknown;
735 stat->actual_kind = dirent->special ? svn_node_symlink
737 stat->filesize = (dirent->kind == svn_node_file)
739 : SVN_INVALID_FILESIZE;
743 stat->actual_kind = svn_node_none;
744 stat->filesize = SVN_INVALID_FILESIZE;
747 stat->node_status = svn_wc_status_none;
748 stat->text_status = svn_wc_status_none;
749 stat->prop_status = svn_wc_status_none;
750 stat->repos_node_status = svn_wc_status_none;
751 stat->repos_text_status = svn_wc_status_none;
752 stat->repos_prop_status = svn_wc_status_none;
754 /* If this path has no entry, but IS present on disk, it's
755 unversioned. If this file is being explicitly ignored (due
756 to matching an ignore-pattern), the node_status is set to
757 svn_wc_status_ignored. Otherwise the node_status is set to
758 svn_wc_status_unversioned. */
759 if (dirent && dirent->kind != svn_node_none)
762 stat->node_status = svn_wc_status_ignored;
764 stat->node_status = svn_wc_status_unversioned;
766 else if (tree_conflicted)
768 /* If this path has no entry, is NOT present on disk, and IS a
769 tree conflict victim, report it as conflicted. */
770 stat->node_status = svn_wc_status_conflicted;
773 stat->revision = SVN_INVALID_REVNUM;
774 stat->changed_rev = SVN_INVALID_REVNUM;
775 stat->ood_changed_rev = SVN_INVALID_REVNUM;
776 stat->ood_kind = svn_node_none;
778 /* For the case of an incoming delete to a locally deleted path during
779 an update, we get a tree conflict. */
780 stat->conflicted = tree_conflicted;
781 stat->changelist = NULL;
783 *status = inner_status;
788 /* Given an ENTRY object representing PATH, build a status structure
789 and pass it off to the STATUS_FUNC/STATUS_BATON. All other
790 arguments are the same as those passed to assemble_status(). */
792 send_status_structure(const struct walk_status_baton *wb,
793 const char *local_abspath,
794 const char *parent_repos_root_url,
795 const char *parent_repos_relpath,
796 const char *parent_repos_uuid,
797 const struct svn_wc__db_info_t *info,
798 const svn_io_dirent2_t *dirent,
799 svn_boolean_t get_all,
800 svn_wc_status_func4_t status_func,
802 apr_pool_t *scratch_pool)
804 svn_wc__internal_status_t *statstruct;
805 const svn_lock_t *repos_lock = NULL;
807 /* Check for a repository lock. */
810 const char *repos_relpath, *repos_root_url, *repos_uuid;
812 SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
814 info, parent_repos_relpath,
815 parent_repos_root_url,
817 wb->db, local_abspath,
818 scratch_pool, scratch_pool));
821 /* repos_lock still uses the deprecated filesystem absolute path
823 repos_lock = svn_hash_gets(wb->repos_locks,
824 svn_fspath__join("/", repos_relpath,
829 SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
830 parent_repos_root_url, parent_repos_relpath,
832 info, dirent, get_all,
833 wb->ignore_text_mods, wb->check_working_copy,
834 repos_lock, scratch_pool, scratch_pool));
836 if (statstruct && status_func)
837 return svn_error_trace((*status_func)(status_baton, local_abspath,
845 /* Store in *PATTERNS a list of ignores collected from svn:ignore properties
846 on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
847 repository ancestors (as cached in the working copy), including the default
848 ignores passed in as IGNORES.
850 Upon return, *PATTERNS will contain zero or more (const char *)
851 patterns from the value of the SVN_PROP_IGNORE property set on
852 the working directory path.
854 IGNORES is a list of patterns to include; typically this will
855 be the default ignores as, for example, specified in a config file.
857 DB, LOCAL_ABSPATH is used to access the working copy.
859 Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
861 None of the arguments may be NULL.
864 collect_ignore_patterns(apr_array_header_t **patterns,
866 const char *local_abspath,
867 const apr_array_header_t *ignores,
868 apr_pool_t *result_pool,
869 apr_pool_t *scratch_pool)
873 apr_array_header_t *inherited_props;
876 /* ### assert we are passed a directory? */
878 *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
880 /* Copy default ignores into the local PATTERNS array. */
881 for (i = 0; i < ignores->nelts; i++)
883 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
884 APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
888 err = svn_wc__db_read_inherited_props(&inherited_props, &props,
890 SVN_PROP_INHERITABLE_IGNORES,
891 scratch_pool, scratch_pool);
895 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
896 return svn_error_trace(err);
898 svn_error_clear(err);
904 const svn_string_t *value;
906 value = svn_hash_gets(props, SVN_PROP_IGNORE);
908 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
911 value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
913 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
917 for (i = 0; i < inherited_props->nelts; i++)
919 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
920 inherited_props, i, svn_prop_inherited_item_t *);
921 const svn_string_t *value;
923 value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
926 svn_cstring_split_append(*patterns, value->data,
927 "\n\r", FALSE, result_pool);
934 /* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
935 LOCAL_ABSPATH is the drop location for, or an intermediate directory
936 of the drop location for, an externals definition. Use SCRATCH_POOL
939 is_external_path(apr_hash_t *externals,
940 const char *local_abspath,
941 apr_pool_t *scratch_pool)
943 apr_hash_index_t *hi;
945 /* First try: does the path exist as a key in the hash? */
946 if (svn_hash_gets(externals, local_abspath))
949 /* Failing that, we need to check if any external is a child of
951 for (hi = apr_hash_first(scratch_pool, externals);
953 hi = apr_hash_next(hi))
955 const char *external_abspath = apr_hash_this_key(hi);
957 if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
965 /* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
966 for it through STATUS_FUNC/STATUS_BATON unless this path is being
967 ignored. This function should never be called on a versioned entry.
969 LOCAL_ABSPATH is the path to the unversioned file whose status is being
970 requested. PATH_KIND is the node kind of NAME as determined by the
971 caller. PATH_SPECIAL is the special status of the path, also determined
973 PATTERNS points to a list of filename patterns which are marked as ignored.
974 None of these parameter may be NULL.
976 If NO_IGNORE is TRUE, the item will be added regardless of
977 whether it is ignored; otherwise we will only add the item if it
978 does not match any of the patterns in PATTERN or INHERITED_IGNORES.
980 Allocate everything in POOL.
983 send_unversioned_item(const struct walk_status_baton *wb,
984 const char *local_abspath,
985 const svn_io_dirent2_t *dirent,
986 svn_boolean_t tree_conflicted,
987 const apr_array_header_t *patterns,
988 svn_boolean_t no_ignore,
989 svn_wc_status_func4_t status_func,
991 apr_pool_t *scratch_pool)
993 svn_boolean_t is_ignored;
994 svn_boolean_t is_external;
995 svn_wc__internal_status_t *status;
996 const char *base_name = svn_dirent_basename(local_abspath, NULL);
998 is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
999 SVN_ERR(assemble_unversioned(&status,
1000 wb->db, local_abspath,
1001 dirent, tree_conflicted,
1003 scratch_pool, scratch_pool));
1005 is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
1007 status->s.node_status = svn_wc_status_external;
1009 /* We can have a tree conflict on an unversioned path, i.e. an incoming
1010 * delete on a locally deleted path during an update. Don't ever ignore
1012 if (status->s.conflicted)
1015 /* If we aren't ignoring it, or if it's an externals path, pass this
1016 entry to the status func. */
1020 return svn_error_trace((*status_func)(status_baton, local_abspath,
1021 &status->s, scratch_pool));
1023 return SVN_NO_ERROR;
1026 static svn_error_t *
1027 get_dir_status(const struct walk_status_baton *wb,
1028 const char *local_abspath,
1029 svn_boolean_t skip_this_dir,
1030 const char *parent_repos_root_url,
1031 const char *parent_repos_relpath,
1032 const char *parent_repos_uuid,
1033 const struct svn_wc__db_info_t *dir_info,
1034 const svn_io_dirent2_t *dirent,
1035 const apr_array_header_t *ignore_patterns,
1037 svn_boolean_t get_all,
1038 svn_boolean_t no_ignore,
1039 svn_wc_status_func4_t status_func,
1041 svn_cancel_func_t cancel_func,
1043 apr_pool_t *scratch_pool);
1045 /* Send out a status structure according to the information gathered on one
1046 * child node. (Basically this function is the guts of the loop in
1047 * get_dir_status() and of get_child_status().)
1049 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
1050 * dirname of LOCAL_ABSPATH.
1052 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
1053 * be an unversioned file or dir, or a versioned file. For versioned
1054 * directories use get_dir_status() instead.
1056 * INFO may be NULL for an unversioned node. If such node has a tree conflict,
1057 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
1058 * UNVERSIONED_TREE_CONFLICTED is ignored.
1060 * DIRENT should reflect LOCAL_ABSPATH's dirent information.
1062 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
1063 * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
1065 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
1066 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
1067 * containing all ignore patterns, as returned by collect_ignore_patterns() on
1068 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
1069 * non-NULL, it is assumed it already holds those results.
1070 * This speeds up repeated calls with the same PARENT_ABSPATH.
1072 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
1073 * allocations are made in SCRATCH_POOL.
1075 * The remaining parameters correspond to get_dir_status(). */
1076 static svn_error_t *
1077 one_child_status(const struct walk_status_baton *wb,
1078 const char *local_abspath,
1079 const char *parent_abspath,
1080 const struct svn_wc__db_info_t *info,
1081 const svn_io_dirent2_t *dirent,
1082 const char *dir_repos_root_url,
1083 const char *dir_repos_relpath,
1084 const char *dir_repos_uuid,
1085 svn_boolean_t unversioned_tree_conflicted,
1086 apr_array_header_t **collected_ignore_patterns,
1087 const apr_array_header_t *ignore_patterns,
1089 svn_boolean_t get_all,
1090 svn_boolean_t no_ignore,
1091 svn_wc_status_func4_t status_func,
1093 svn_cancel_func_t cancel_func,
1095 apr_pool_t *result_pool,
1096 apr_pool_t *scratch_pool)
1098 svn_boolean_t conflicted = info ? info->conflicted
1099 : unversioned_tree_conflicted;
1102 && info->status != svn_wc__db_status_not_present
1103 && info->status != svn_wc__db_status_excluded
1104 && info->status != svn_wc__db_status_server_excluded
1105 && !(info->kind == svn_node_unknown
1106 && info->status == svn_wc__db_status_normal))
1108 if (depth == svn_depth_files
1109 && info->kind == svn_node_dir)
1111 return SVN_NO_ERROR;
1114 SVN_ERR(send_status_structure(wb, local_abspath,
1118 info, dirent, get_all,
1119 status_func, status_baton,
1122 /* Descend in subdirectories. */
1123 if (depth == svn_depth_infinity
1124 && info->has_descendants /* is dir, or was dir and tc descendants */)
1126 SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
1127 dir_repos_root_url, dir_repos_relpath,
1128 dir_repos_uuid, info,
1129 dirent, ignore_patterns,
1130 svn_depth_infinity, get_all,
1132 status_func, status_baton,
1133 cancel_func, cancel_baton,
1137 return SVN_NO_ERROR;
1140 /* If conflicted, fall right through to unversioned.
1141 * With depth_files, show all conflicts, even if their report is only
1142 * about directories. A tree conflict may actually report two different
1143 * kinds, so it's not so easy to define what depth=files means. We could go
1144 * look up the kinds in the conflict ... just show all. */
1147 /* We have a node, but its not visible in the WC. It can be a marker
1148 node (not present, (server) excluded), *or* it can be the explictly
1149 passed target of the status walk operation that doesn't exist.
1151 We only report the node when the caller explicitly as
1153 if (dirent == NULL && strcmp(wb->target_abspath, local_abspath) != 0)
1154 return SVN_NO_ERROR; /* Marker node */
1156 if (depth == svn_depth_files && dirent && dirent->kind == svn_node_dir)
1157 return SVN_NO_ERROR;
1159 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
1161 return SVN_NO_ERROR;
1164 /* The node exists on disk but there is no versioned information about it,
1165 * or it doesn't exist but is a tree conflicted path or should be
1166 * reported not-present. */
1168 /* Why pass ignore patterns on a tree conflicted node, even if it should
1169 * always show up in clients' status reports anyway? Because the calling
1170 * client decides whether to ignore, and thus this flag needs to be
1171 * determined. For example, in 'svn status', plain unversioned nodes show
1172 * as '? C', where ignored ones show as 'I C'. */
1174 if (ignore_patterns && ! *collected_ignore_patterns)
1175 SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
1176 wb->db, parent_abspath, ignore_patterns,
1177 result_pool, scratch_pool));
1179 SVN_ERR(send_unversioned_item(wb,
1183 *collected_ignore_patterns,
1185 status_func, status_baton,
1188 return SVN_NO_ERROR;
1191 /* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
1192 for all its child nodes (according to DEPTH) through STATUS_FUNC /
1195 If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
1196 All subdirs reached by recursion will be reported regardless of this
1199 PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
1200 URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
1201 retrieving them again. Otherwise they must be NULL.
1203 DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
1204 it again. Otherwise it must be NULL.
1206 DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
1207 so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
1209 Other arguments are the same as those passed to
1210 svn_wc_get_status_editor5(). */
1211 static svn_error_t *
1212 get_dir_status(const struct walk_status_baton *wb,
1213 const char *local_abspath,
1214 svn_boolean_t skip_this_dir,
1215 const char *parent_repos_root_url,
1216 const char *parent_repos_relpath,
1217 const char *parent_repos_uuid,
1218 const struct svn_wc__db_info_t *dir_info,
1219 const svn_io_dirent2_t *dirent,
1220 const apr_array_header_t *ignore_patterns,
1222 svn_boolean_t get_all,
1223 svn_boolean_t no_ignore,
1224 svn_wc_status_func4_t status_func,
1226 svn_cancel_func_t cancel_func,
1228 apr_pool_t *scratch_pool)
1230 const char *dir_repos_root_url;
1231 const char *dir_repos_relpath;
1232 const char *dir_repos_uuid;
1233 apr_hash_t *dirents, *nodes, *conflicts, *all_children;
1234 apr_array_header_t *sorted_children;
1235 apr_array_header_t *collected_ignore_patterns = NULL;
1236 apr_pool_t *iterpool;
1241 SVN_ERR(cancel_func(cancel_baton));
1243 if (depth == svn_depth_unknown)
1244 depth = svn_depth_infinity;
1246 iterpool = svn_pool_create(scratch_pool);
1248 if (wb->check_working_copy)
1250 err = svn_io_get_dirents3(&dirents, local_abspath,
1251 wb->ignore_text_mods /* only_check_type*/,
1252 scratch_pool, iterpool);
1254 && (APR_STATUS_IS_ENOENT(err->apr_err)
1255 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
1257 svn_error_clear(err);
1258 dirents = apr_hash_make(scratch_pool);
1264 dirents = apr_hash_make(scratch_pool);
1267 SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath,
1268 !wb->check_working_copy,
1269 scratch_pool, iterpool));
1271 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1272 &dir_repos_uuid, dir_info,
1273 parent_repos_relpath,
1274 parent_repos_root_url, parent_repos_uuid,
1275 wb->db, local_abspath,
1276 scratch_pool, iterpool));
1278 /* Create a hash containing all children. The source hashes
1279 don't all map the same types, but only the keys of the result
1280 hash are subsequently used. */
1281 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1282 wb->db, local_abspath,
1283 !wb->check_working_copy,
1284 scratch_pool, iterpool));
1286 all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
1287 if (apr_hash_count(conflicts) > 0)
1288 all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
1290 /* Handle "this-dir" first. */
1291 if (! skip_this_dir)
1293 /* This code is not conditional on HAVE_SYMLINK as some systems that do
1294 not allow creating symlinks (!HAVE_SYMLINK) can still encounter
1295 symlinks (or in case of Windows also 'Junctions') created by other
1298 Without this block a working copy in the root of a junction is
1299 reported as an obstruction, because the junction itself is reported as
1302 Systems that have no symlink support at all, would always see
1303 dirent->special as FALSE, so even there enabling this code shouldn't
1306 if (dirent->special)
1308 svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
1310 /* We're being pointed to "this-dir" via a symlink.
1311 * Get the real node kind and pretend the path is not a symlink.
1312 * This prevents send_status_structure() from treating this-dir
1313 * as a directory obstructed by a file. */
1314 SVN_ERR(svn_io_check_resolved_path(local_abspath,
1315 &this_dirent->kind, iterpool));
1316 this_dirent->special = FALSE;
1317 SVN_ERR(send_status_structure(wb, local_abspath,
1318 parent_repos_root_url,
1319 parent_repos_relpath,
1321 dir_info, this_dirent, get_all,
1322 status_func, status_baton,
1326 SVN_ERR(send_status_structure(wb, local_abspath,
1327 parent_repos_root_url,
1328 parent_repos_relpath,
1330 dir_info, dirent, get_all,
1331 status_func, status_baton,
1335 /* If the requested depth is empty, we only need status on this-dir. */
1336 if (depth == svn_depth_empty)
1337 return SVN_NO_ERROR;
1339 /* Walk all the children of this directory. */
1340 sorted_children = svn_sort__hash(all_children,
1341 svn_sort_compare_items_lexically,
1343 for (i = 0; i < sorted_children->nelts; i++)
1347 svn_sort__item_t item;
1348 const char *child_abspath;
1349 svn_io_dirent2_t *child_dirent;
1350 const struct svn_wc__db_info_t *child_info;
1352 svn_pool_clear(iterpool);
1354 item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
1358 child_abspath = svn_dirent_join(local_abspath, key, iterpool);
1359 child_dirent = apr_hash_get(dirents, key, klen);
1360 child_info = apr_hash_get(nodes, key, klen);
1362 SVN_ERR(one_child_status(wb,
1370 apr_hash_get(conflicts, key, klen) != NULL,
1371 &collected_ignore_patterns,
1384 /* Destroy our subpools. */
1385 svn_pool_destroy(iterpool);
1387 return SVN_NO_ERROR;
1390 /* Send an svn_wc_status3_t * structure for the versioned file, or for the
1391 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
1392 * explicit target). Does not recurse.
1394 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
1395 * unversioned nodes. An unversioned and tree-conflicted node however should
1396 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
1398 * DIRENT should reflect LOCAL_ABSPATH.
1400 * All allocations made in SCRATCH_POOL.
1402 * The remaining parameters correspond to get_dir_status(). */
1403 static svn_error_t *
1404 get_child_status(const struct walk_status_baton *wb,
1405 const char *local_abspath,
1406 const struct svn_wc__db_info_t *info,
1407 const svn_io_dirent2_t *dirent,
1408 const apr_array_header_t *ignore_patterns,
1409 svn_boolean_t get_all,
1410 svn_wc_status_func4_t status_func,
1412 svn_cancel_func_t cancel_func,
1414 apr_pool_t *scratch_pool)
1416 const char *dir_repos_root_url;
1417 const char *dir_repos_relpath;
1418 const char *dir_repos_uuid;
1419 const struct svn_wc__db_info_t *dir_info;
1420 apr_array_header_t *collected_ignore_patterns = NULL;
1421 const char *parent_abspath = svn_dirent_dirname(local_abspath,
1425 SVN_ERR(cancel_func(cancel_baton));
1427 if (dirent->kind == svn_node_none)
1430 SVN_ERR(svn_wc__db_read_single_info(&dir_info,
1431 wb->db, parent_abspath,
1432 !wb->check_working_copy,
1433 scratch_pool, scratch_pool));
1435 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1436 &dir_repos_uuid, dir_info,
1438 wb->db, parent_abspath,
1439 scratch_pool, scratch_pool));
1441 /* An unversioned node with a tree conflict will see an INFO != NULL here,
1442 * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
1443 * effect and INFO->CONFLICTED counts.
1444 * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
1446 SVN_ERR(one_child_status(wb,
1454 FALSE, /* unversioned_tree_conflicted */
1455 &collected_ignore_patterns,
1459 TRUE, /* no_ignore. This is an explicit target. */
1466 return SVN_NO_ERROR;
1473 /* A faux status callback function for stashing STATUS item in an hash
1474 (which is the BATON), keyed on PATH. This implements the
1475 svn_wc_status_func4_t interface. */
1476 static svn_error_t *
1477 hash_stash(void *baton,
1479 const svn_wc_status3_t *status,
1480 apr_pool_t *scratch_pool)
1482 apr_hash_t *stat_hash = baton;
1483 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1484 void *new_status = svn_wc_dup_status3(status, hash_pool);
1485 const svn_wc__internal_status_t *old_status = (const void*)status;
1487 /* Copy the internal/private data. */
1488 svn_wc__internal_status_t *is = new_status;
1489 is->has_descendants = old_status->has_descendants;
1490 is->op_root = old_status->op_root;
1492 assert(! svn_hash_gets(stat_hash, path));
1493 svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), new_status);
1495 return SVN_NO_ERROR;
1499 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1500 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1501 yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
1502 create a new status struct using the hash's pool.
1504 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1505 of date (ood) information we want to set in BATON. This is necessary
1506 because this function tweaks the status of out-of-date directories
1507 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1508 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1509 contains the ood info we want to bubble up to ancestor directories so these
1510 accurately reflect the fact they have an ood descendant.
1512 Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
1513 status structure's "network" fields.
1515 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1518 If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
1519 optionally the revision path was deleted, in all other cases it must
1520 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1521 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1522 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1523 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
1524 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1525 ood_last_cmt_rev value - see comment below.
1527 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1528 static svn_error_t *
1529 tweak_statushash(void *baton,
1530 void *this_dir_baton,
1531 svn_boolean_t is_dir_baton,
1533 svn_boolean_t check_working_copy,
1534 const char *local_abspath,
1535 enum svn_wc_status_kind repos_node_status,
1536 enum svn_wc_status_kind repos_text_status,
1537 enum svn_wc_status_kind repos_prop_status,
1538 svn_revnum_t deleted_rev,
1539 const svn_lock_t *repos_lock,
1540 apr_pool_t *scratch_pool)
1542 svn_wc_status3_t *statstruct;
1544 apr_hash_t *statushash;
1547 statushash = ((struct dir_baton *) baton)->statii;
1549 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1550 pool = apr_hash_pool_get(statushash);
1552 /* Is PATH already a hash-key? */
1553 statstruct = svn_hash_gets(statushash, local_abspath);
1555 /* If not, make it so. */
1558 svn_wc__internal_status_t *i_stat;
1559 /* If this item isn't being added, then we're most likely
1560 dealing with a non-recursive (or at least partially
1561 non-recursive) working copy. Due to bugs in how the client
1562 reports the state of non-recursive working copies, the
1563 repository can send back responses about paths that don't
1564 even exist locally. Our best course here is just to ignore
1565 those responses. After all, if the client had reported
1566 correctly in the first, that path would either be mentioned
1567 as an 'add' or not mentioned at all, depending on how we
1568 eventually fix the bugs in non-recursivity. See issue
1569 #2122 for details. */
1570 if (repos_node_status != svn_wc_status_added)
1571 return SVN_NO_ERROR;
1573 /* Use the public API to get a statstruct, and put it into the hash. */
1574 SVN_ERR(internal_status(&i_stat, db, local_abspath,
1575 check_working_copy, pool, scratch_pool));
1576 statstruct = &i_stat->s;
1577 statstruct->repos_lock = repos_lock;
1578 svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
1581 /* Merge a repos "delete" + "add" into a single "replace". */
1582 if ((repos_node_status == svn_wc_status_added)
1583 && (statstruct->repos_node_status == svn_wc_status_deleted))
1584 repos_node_status = svn_wc_status_replaced;
1586 /* Tweak the structure's repos fields. */
1587 if (repos_node_status)
1588 statstruct->repos_node_status = repos_node_status;
1589 if (repos_text_status)
1590 statstruct->repos_text_status = repos_text_status;
1591 if (repos_prop_status)
1592 statstruct->repos_prop_status = repos_prop_status;
1594 /* Copy out-of-date info. */
1597 struct dir_baton *b = this_dir_baton;
1599 if (!statstruct->repos_relpath && b->repos_relpath)
1601 if (statstruct->repos_node_status == svn_wc_status_deleted)
1603 /* When deleting PATH, BATON is for PATH's parent,
1604 so we must construct PATH's real statstruct->url. */
1605 statstruct->repos_relpath =
1606 svn_relpath_join(b->repos_relpath,
1607 svn_dirent_basename(local_abspath,
1612 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1614 statstruct->repos_root_url =
1615 b->edit_baton->anchor_status->s.repos_root_url;
1616 statstruct->repos_uuid =
1617 b->edit_baton->anchor_status->s.repos_uuid;
1620 /* The last committed date, and author for deleted items
1622 if (statstruct->repos_node_status == svn_wc_status_deleted)
1624 statstruct->ood_kind = statstruct->kind;
1626 /* Pre 1.5 servers don't provide the revision a path was deleted.
1627 So we punt and use the last committed revision of the path's
1628 parent, which has some chance of being correct. At worse it
1629 is a higher revision than the path was deleted, but this is
1630 better than nothing... */
1631 if (deleted_rev == SVN_INVALID_REVNUM)
1632 statstruct->ood_changed_rev =
1633 ((struct dir_baton *) baton)->ood_changed_rev;
1635 statstruct->ood_changed_rev = deleted_rev;
1639 statstruct->ood_kind = b->ood_kind;
1640 statstruct->ood_changed_rev = b->ood_changed_rev;
1641 statstruct->ood_changed_date = b->ood_changed_date;
1642 if (b->ood_changed_author)
1643 statstruct->ood_changed_author =
1644 apr_pstrdup(pool, b->ood_changed_author);
1650 struct file_baton *b = baton;
1651 statstruct->ood_changed_rev = b->ood_changed_rev;
1652 statstruct->ood_changed_date = b->ood_changed_date;
1653 if (!statstruct->repos_relpath && b->repos_relpath)
1655 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1656 statstruct->repos_root_url =
1657 b->edit_baton->anchor_status->s.repos_root_url;
1658 statstruct->repos_uuid =
1659 b->edit_baton->anchor_status->s.repos_uuid;
1661 statstruct->ood_kind = b->ood_kind;
1662 if (b->ood_changed_author)
1663 statstruct->ood_changed_author =
1664 apr_pstrdup(pool, b->ood_changed_author);
1666 return SVN_NO_ERROR;
1669 /* Returns the URL for DB */
1671 find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
1673 /* If we have no name, we're the root, return the anchor URL. */
1675 return db->edit_baton->anchor_status->s.repos_relpath;
1678 const char *repos_relpath;
1679 struct dir_baton *pb = db->parent_baton;
1680 const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
1682 /* Note that status->repos_relpath could be NULL in the case of a missing
1683 * directory, which means we need to recurse up another level to get
1684 * a useful relpath. */
1685 if (status && status->repos_relpath)
1686 return status->repos_relpath;
1688 repos_relpath = find_dir_repos_relpath(pb, pool);
1689 return svn_relpath_join(repos_relpath, db->name, pool);
1695 /* Create a new dir_baton for subdir PATH. */
1696 static svn_error_t *
1697 make_dir_baton(void **dir_baton,
1699 struct edit_baton *edit_baton,
1700 struct dir_baton *parent_baton,
1701 apr_pool_t *result_pool)
1703 struct dir_baton *pb = parent_baton;
1704 struct edit_baton *eb = edit_baton;
1705 struct dir_baton *d;
1706 const char *local_abspath;
1707 const svn_wc__internal_status_t *status_in_parent;
1708 apr_pool_t *dir_pool;
1711 dir_pool = svn_pool_create(parent_baton->pool);
1713 dir_pool = svn_pool_create(result_pool);
1715 d = apr_pcalloc(dir_pool, sizeof(*d));
1717 SVN_ERR_ASSERT(path || (! pb));
1719 /* Construct the absolute path of this directory. */
1721 local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
1723 local_abspath = eb->anchor_abspath;
1725 /* Finish populating the baton members. */
1727 d->local_abspath = local_abspath;
1728 d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
1729 d->edit_baton = edit_baton;
1730 d->parent_baton = parent_baton;
1731 d->statii = apr_hash_make(dir_pool);
1732 d->ood_changed_rev = SVN_INVALID_REVNUM;
1733 d->ood_changed_date = 0;
1734 d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
1735 d->ood_kind = svn_node_dir;
1736 d->ood_changed_author = NULL;
1742 else if (pb->depth == svn_depth_immediates)
1743 d->depth = svn_depth_empty;
1744 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1746 else if (pb->depth == svn_depth_unknown)
1747 /* This is only tentative, it can be overridden from d's entry
1749 d->depth = svn_depth_unknown;
1751 d->depth = svn_depth_infinity;
1755 d->depth = eb->default_depth;
1758 /* Get the status for this path's children. Of course, we only want
1759 to do this if the path is versioned as a directory. */
1761 status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
1763 status_in_parent = eb->anchor_status;
1765 if (status_in_parent
1766 && (status_in_parent->has_descendants)
1768 && (d->depth == svn_depth_unknown
1769 || d->depth == svn_depth_infinity
1770 || d->depth == svn_depth_files
1771 || d->depth == svn_depth_immediates)
1774 const svn_wc_status3_t *this_dir_status;
1775 const apr_array_header_t *ignores = eb->ignores;
1777 SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
1778 status_in_parent->s.repos_root_url,
1779 NULL /*parent_repos_relpath*/,
1780 status_in_parent->s.repos_uuid,
1782 NULL /* dirent */, ignores,
1783 d->depth == svn_depth_files
1785 : svn_depth_immediates,
1787 hash_stash, d->statii,
1788 eb->cancel_func, eb->cancel_baton,
1791 /* If we found a depth here, it should govern. */
1792 this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
1793 if (this_dir_status && this_dir_status->versioned
1794 && (d->depth == svn_depth_unknown
1795 || d->depth > status_in_parent->s.depth))
1797 d->depth = this_dir_status->depth;
1802 return SVN_NO_ERROR;
1806 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1807 NAME is just one component, not a path. */
1808 static struct file_baton *
1809 make_file_baton(struct dir_baton *parent_dir_baton,
1813 struct dir_baton *pb = parent_dir_baton;
1814 struct edit_baton *eb = pb->edit_baton;
1815 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1817 /* Finish populating the baton members. */
1818 f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1819 f->name = svn_dirent_basename(f->local_abspath, NULL);
1823 f->ood_changed_rev = SVN_INVALID_REVNUM;
1824 f->ood_changed_date = 0;
1825 f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
1827 f->ood_kind = svn_node_file;
1828 f->ood_changed_author = NULL;
1834 * Return a boolean answer to the question "Is @a status something that
1835 * should be reported?". @a no_ignore and @a get_all are the same as
1836 * svn_wc_get_status_editor4().
1838 * This implementation should match the filter in assemble_status()
1840 static svn_boolean_t
1841 is_sendable_status(const svn_wc__internal_status_t *i_status,
1842 svn_boolean_t no_ignore,
1843 svn_boolean_t get_all)
1845 const svn_wc_status3_t *status = &i_status->s;
1846 /* If the repository status was touched at all, it's interesting. */
1847 if (status->repos_node_status != svn_wc_status_none)
1850 /* If there is a lock in the repository, send it. */
1851 if (status->repos_lock)
1854 if (status->conflicted)
1857 /* If the item is ignored, and we don't want ignores, skip it. */
1858 if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
1861 /* If we want everything, we obviously want this single-item subset
1866 /* If the item is unversioned, display it. */
1867 if (status->node_status == svn_wc_status_unversioned)
1870 /* If the text, property or tree state is interesting, send it. */
1871 if ((status->node_status != svn_wc_status_none)
1872 && (status->node_status != svn_wc_status_normal)
1873 && !(status->node_status == svn_wc_status_deleted
1874 && !i_status->op_root))
1877 /* If it's switched, send it. */
1878 if (status->switched)
1881 /* If there is a lock token, send it. */
1882 if (status->versioned && status->lock)
1885 /* If the entry is associated with a changelist, send it. */
1886 if (status->changelist)
1889 if (status->moved_to_abspath)
1892 /* Otherwise, don't send it. */
1897 /* Baton for mark_status. */
1900 svn_wc_status_func4_t real_status_func; /* real status function */
1901 void *real_status_baton; /* real status baton */
1904 /* A status callback function which wraps the *real* status
1905 function/baton. It simply sets the "repos_node_status" field of the
1906 STATUS to svn_wc_status_deleted and passes it off to the real
1907 status func/baton. Implements svn_wc_status_func4_t */
1908 static svn_error_t *
1909 mark_deleted(void *baton,
1910 const char *local_abspath,
1911 const svn_wc_status3_t *status,
1912 apr_pool_t *scratch_pool)
1914 struct status_baton *sb = baton;
1915 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
1916 new_status->repos_node_status = svn_wc_status_deleted;
1917 return sb->real_status_func(sb->real_status_baton, local_abspath,
1918 new_status, scratch_pool);
1922 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1923 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1924 directory itself. Descend into subdirectories according to DEPTH.
1925 Also, if DIR_WAS_DELETED is set, each status that is reported
1926 through this function will have its repos_text_status field showing
1927 a deletion. Use POOL for all allocations. */
1928 static svn_error_t *
1929 handle_statii(struct edit_baton *eb,
1930 const char *dir_repos_root_url,
1931 const char *dir_repos_relpath,
1932 const char *dir_repos_uuid,
1934 svn_boolean_t dir_was_deleted,
1938 const apr_array_header_t *ignores = eb->ignores;
1939 apr_hash_index_t *hi;
1940 apr_pool_t *iterpool = svn_pool_create(pool);
1941 svn_wc_status_func4_t status_func = eb->status_func;
1942 void *status_baton = eb->status_baton;
1943 struct status_baton sb;
1945 if (dir_was_deleted)
1947 sb.real_status_func = eb->status_func;
1948 sb.real_status_baton = eb->status_baton;
1949 status_func = mark_deleted;
1953 /* Loop over all the statii still in our hash, handling each one. */
1954 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1956 const char *local_abspath = apr_hash_this_key(hi);
1957 svn_wc__internal_status_t *status = apr_hash_this_val(hi);
1959 /* Clear the subpool. */
1960 svn_pool_clear(iterpool);
1962 /* Now, handle the status. We don't recurse for svn_depth_immediates
1963 because we already have the subdirectories' statii. */
1964 if (status->has_descendants
1965 && (depth == svn_depth_unknown
1966 || depth == svn_depth_infinity))
1968 SVN_ERR(get_dir_status(&eb->wb,
1969 local_abspath, TRUE,
1970 dir_repos_root_url, dir_repos_relpath,
1974 ignores, depth, eb->get_all, eb->no_ignore,
1975 status_func, status_baton,
1976 eb->cancel_func, eb->cancel_baton,
1979 if (dir_was_deleted)
1980 status->s.repos_node_status = svn_wc_status_deleted;
1981 if (is_sendable_status(status, eb->no_ignore, eb->get_all))
1982 SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, &status->s,
1986 /* Destroy the subpool. */
1987 svn_pool_destroy(iterpool);
1989 return SVN_NO_ERROR;
1993 /*----------------------------------------------------------------------*/
1995 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1997 /* An svn_delta_editor_t function. */
1998 static svn_error_t *
1999 set_target_revision(void *edit_baton,
2000 svn_revnum_t target_revision,
2003 struct edit_baton *eb = edit_baton;
2004 *(eb->target_revision) = target_revision;
2005 return SVN_NO_ERROR;
2009 /* An svn_delta_editor_t function. */
2010 static svn_error_t *
2011 open_root(void *edit_baton,
2012 svn_revnum_t base_revision,
2016 struct edit_baton *eb = edit_baton;
2017 eb->root_opened = TRUE;
2018 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
2022 /* An svn_delta_editor_t function. */
2023 static svn_error_t *
2024 delete_entry(const char *path,
2025 svn_revnum_t revision,
2029 struct dir_baton *db = parent_baton;
2030 struct edit_baton *eb = db->edit_baton;
2031 const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
2033 /* Note: when something is deleted, it's okay to tweak the
2034 statushash immediately. No need to wait until close_file or
2035 close_dir, because there's no risk of having to honor the 'added'
2036 flag. We already know this item exists in the working copy. */
2037 SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, eb->wb.check_working_copy,
2039 svn_wc_status_deleted, 0, 0, revision, NULL, pool));
2041 /* Mark the parent dir -- it lost an entry (unless that parent dir
2042 is the root node and we're not supposed to report on the root
2044 if (db->parent_baton && (! *eb->target_basename))
2045 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,
2046 eb->db, eb->wb.check_working_copy,
2048 svn_wc_status_modified, svn_wc_status_modified,
2049 0, SVN_INVALID_REVNUM, NULL, pool));
2051 return SVN_NO_ERROR;
2055 /* An svn_delta_editor_t function. */
2056 static svn_error_t *
2057 add_directory(const char *path,
2059 const char *copyfrom_path,
2060 svn_revnum_t copyfrom_revision,
2064 struct dir_baton *pb = parent_baton;
2065 struct edit_baton *eb = pb->edit_baton;
2066 struct dir_baton *new_db;
2068 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
2070 /* Make this dir as added. */
2071 new_db = *child_baton;
2072 new_db->added = TRUE;
2074 /* Mark the parent as changed; it gained an entry. */
2075 pb->text_changed = TRUE;
2077 return SVN_NO_ERROR;
2081 /* An svn_delta_editor_t function. */
2082 static svn_error_t *
2083 open_directory(const char *path,
2085 svn_revnum_t base_revision,
2089 struct dir_baton *pb = parent_baton;
2090 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
2094 /* An svn_delta_editor_t function. */
2095 static svn_error_t *
2096 change_dir_prop(void *dir_baton,
2098 const svn_string_t *value,
2101 struct dir_baton *db = dir_baton;
2102 if (svn_wc_is_normal_prop(name))
2103 db->prop_changed = TRUE;
2105 /* Note any changes to the repository. */
2108 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2109 db->ood_changed_rev = SVN_STR_TO_REV(value->data);
2110 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2111 db->ood_changed_author = apr_pstrdup(db->pool, value->data);
2112 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2115 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
2116 db->ood_changed_date = tm;
2120 return SVN_NO_ERROR;
2125 /* An svn_delta_editor_t function. */
2126 static svn_error_t *
2127 close_directory(void *dir_baton,
2130 struct dir_baton *db = dir_baton;
2131 struct dir_baton *pb = db->parent_baton;
2132 struct edit_baton *eb = db->edit_baton;
2133 apr_pool_t *scratch_pool = db->pool;
2135 /* If nothing has changed and directory has no out of
2136 date descendants, return. */
2137 if (db->added || db->prop_changed || db->text_changed
2138 || db->ood_changed_rev != SVN_INVALID_REVNUM)
2140 enum svn_wc_status_kind repos_node_status;
2141 enum svn_wc_status_kind repos_text_status;
2142 enum svn_wc_status_kind repos_prop_status;
2144 /* If this is a new directory, add it to the statushash. */
2147 repos_node_status = svn_wc_status_added;
2148 repos_text_status = svn_wc_status_none;
2149 repos_prop_status = db->prop_changed ? svn_wc_status_added
2150 : svn_wc_status_none;
2154 repos_node_status = (db->text_changed || db->prop_changed)
2155 ? svn_wc_status_modified
2156 : svn_wc_status_none;
2157 repos_text_status = db->text_changed ? svn_wc_status_modified
2158 : svn_wc_status_none;
2159 repos_prop_status = db->prop_changed ? svn_wc_status_modified
2160 : svn_wc_status_none;
2163 /* Maybe add this directory to its parent's status hash. Note
2164 that tweak_statushash won't do anything if repos_text_status
2165 is not svn_wc_status_added. */
2168 /* ### When we add directory locking, we need to find a
2169 ### directory lock here. */
2170 SVN_ERR(tweak_statushash(pb, db, TRUE,
2171 eb->db, eb->wb.check_working_copy,
2173 repos_node_status, repos_text_status,
2174 repos_prop_status, SVN_INVALID_REVNUM, NULL,
2179 /* We're editing the root dir of the WC. As its repos
2180 status info isn't otherwise set, set it directly to
2181 trigger invocation of the status callback below. */
2182 eb->anchor_status->s.repos_node_status = repos_node_status;
2183 eb->anchor_status->s.repos_prop_status = repos_prop_status;
2184 eb->anchor_status->s.repos_text_status = repos_text_status;
2186 /* If the root dir is out of date set the ood info directly too. */
2187 if (db->ood_changed_rev != eb->anchor_status->s.revision)
2189 eb->anchor_status->s.ood_changed_rev = db->ood_changed_rev;
2190 eb->anchor_status->s.ood_changed_date = db->ood_changed_date;
2191 eb->anchor_status->s.ood_kind = db->ood_kind;
2192 eb->anchor_status->s.ood_changed_author =
2193 apr_pstrdup(pool, db->ood_changed_author);
2198 /* Handle this directory's statuses, and then note in the parent
2199 that this has been done. */
2200 if (pb && ! db->excluded)
2202 svn_boolean_t was_deleted = FALSE;
2203 svn_wc__internal_status_t *dir_status;
2205 /* See if the directory was deleted or replaced. */
2206 dir_status = svn_hash_gets(pb->statii, db->local_abspath);
2208 ((dir_status->s.repos_node_status == svn_wc_status_deleted)
2209 || (dir_status->s.repos_node_status == svn_wc_status_replaced)))
2212 /* Now do the status reporting. */
2213 SVN_ERR(handle_statii(eb,
2214 dir_status ? dir_status->s.repos_root_url : NULL,
2215 dir_status ? dir_status->s.repos_relpath : NULL,
2216 dir_status ? dir_status->s.repos_uuid : NULL,
2217 db->statii, was_deleted, db->depth, scratch_pool));
2218 if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
2220 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2221 &dir_status->s, scratch_pool));
2222 svn_hash_sets(pb->statii, db->local_abspath, NULL);
2226 /* If this is the top-most directory, and the operation had a
2227 target, we should only report the target. */
2228 if (*eb->target_basename)
2230 const svn_wc__internal_status_t *tgt_status;
2232 tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
2235 if (tgt_status->has_descendants)
2237 SVN_ERR(get_dir_status(&eb->wb,
2238 eb->target_abspath, TRUE,
2239 NULL, NULL, NULL, NULL,
2243 eb->get_all, eb->no_ignore,
2244 eb->status_func, eb->status_baton,
2245 eb->cancel_func, eb->cancel_baton,
2248 if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
2249 SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
2250 &tgt_status->s, scratch_pool));
2255 /* Otherwise, we report on all our children and ourself.
2256 Note that our directory couldn't have been deleted,
2257 because it is the root of the edit drive. */
2258 SVN_ERR(handle_statii(eb,
2259 eb->anchor_status->s.repos_root_url,
2260 eb->anchor_status->s.repos_relpath,
2261 eb->anchor_status->s.repos_uuid,
2262 db->statii, FALSE, eb->default_depth,
2264 if (is_sendable_status(eb->anchor_status, eb->no_ignore,
2266 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2267 &eb->anchor_status->s, scratch_pool));
2268 eb->anchor_status = NULL;
2272 svn_pool_clear(scratch_pool); /* Clear baton and its pool */
2274 return SVN_NO_ERROR;
2279 /* An svn_delta_editor_t function. */
2280 static svn_error_t *
2281 add_file(const char *path,
2283 const char *copyfrom_path,
2284 svn_revnum_t copyfrom_revision,
2288 struct dir_baton *pb = parent_baton;
2289 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2291 /* Mark parent dir as changed */
2292 pb->text_changed = TRUE;
2294 /* Make this file as added. */
2295 new_fb->added = TRUE;
2297 *file_baton = new_fb;
2298 return SVN_NO_ERROR;
2302 /* An svn_delta_editor_t function. */
2303 static svn_error_t *
2304 open_file(const char *path,
2306 svn_revnum_t base_revision,
2310 struct dir_baton *pb = parent_baton;
2311 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2313 *file_baton = new_fb;
2314 return SVN_NO_ERROR;
2318 /* An svn_delta_editor_t function. */
2319 static svn_error_t *
2320 apply_textdelta(void *file_baton,
2321 const char *base_checksum,
2323 svn_txdelta_window_handler_t *handler,
2324 void **handler_baton)
2326 struct file_baton *fb = file_baton;
2328 /* Mark file as having textual mods. */
2329 fb->text_changed = TRUE;
2331 /* Send back a NULL window handler -- we don't need the actual diffs. */
2332 *handler_baton = NULL;
2333 *handler = svn_delta_noop_window_handler;
2335 return SVN_NO_ERROR;
2339 /* An svn_delta_editor_t function. */
2340 static svn_error_t *
2341 change_file_prop(void *file_baton,
2343 const svn_string_t *value,
2346 struct file_baton *fb = file_baton;
2347 if (svn_wc_is_normal_prop(name))
2348 fb->prop_changed = TRUE;
2350 /* Note any changes to the repository. */
2353 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2354 fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
2355 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2356 fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
2358 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2361 SVN_ERR(svn_time_from_cstring(&tm, value->data,
2362 fb->dir_baton->pool));
2363 fb->ood_changed_date = tm;
2367 return SVN_NO_ERROR;
2371 /* An svn_delta_editor_t function. */
2372 static svn_error_t *
2373 close_file(void *file_baton,
2374 const char *text_checksum, /* ignored, as we receive no data */
2377 struct file_baton *fb = file_baton;
2378 enum svn_wc_status_kind repos_node_status;
2379 enum svn_wc_status_kind repos_text_status;
2380 enum svn_wc_status_kind repos_prop_status;
2381 const svn_lock_t *repos_lock = NULL;
2383 /* If nothing has changed, return. */
2384 if (! (fb->added || fb->prop_changed || fb->text_changed))
2385 return SVN_NO_ERROR;
2387 /* If this is a new file, add it to the statushash. */
2390 repos_node_status = svn_wc_status_added;
2391 repos_text_status = fb->text_changed ? svn_wc_status_modified
2392 : 0 /* don't tweak */;
2393 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2394 : 0 /* don't tweak */;
2396 if (fb->edit_baton->wb.repos_locks)
2398 const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
2401 /* repos_lock still uses the deprecated filesystem absolute path
2403 const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
2406 repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
2407 svn_fspath__join("/", repos_relpath,
2413 repos_node_status = (fb->text_changed || fb->prop_changed)
2414 ? svn_wc_status_modified
2415 : 0 /* don't tweak */;
2416 repos_text_status = fb->text_changed ? svn_wc_status_modified
2417 : 0 /* don't tweak */;
2418 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2419 : 0 /* don't tweak */;
2422 return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
2423 fb->edit_baton->wb.check_working_copy,
2424 fb->local_abspath, repos_node_status,
2425 repos_text_status, repos_prop_status,
2426 SVN_INVALID_REVNUM, repos_lock, pool);
2429 /* An svn_delta_editor_t function. */
2430 static svn_error_t *
2431 close_edit(void *edit_baton,
2434 struct edit_baton *eb = edit_baton;
2436 /* If we get here and the root was not opened as part of the edit,
2437 we need to transmit statuses for everything. Otherwise, we
2439 if (eb->root_opened)
2440 return SVN_NO_ERROR;
2442 SVN_ERR(svn_wc__internal_walk_status(eb->db,
2455 return SVN_NO_ERROR;
2460 /*** Public API ***/
2463 svn_wc__get_status_editor(const svn_delta_editor_t **editor,
2465 void **set_locks_baton,
2466 svn_revnum_t *edit_revision,
2467 svn_wc_context_t *wc_ctx,
2468 const char *anchor_abspath,
2469 const char *target_basename,
2471 svn_boolean_t get_all,
2472 svn_boolean_t check_working_copy,
2473 svn_boolean_t no_ignore,
2474 svn_boolean_t depth_as_sticky,
2475 svn_boolean_t server_performs_filtering,
2476 const apr_array_header_t *ignore_patterns,
2477 svn_wc_status_func4_t status_func,
2479 svn_cancel_func_t cancel_func,
2481 apr_pool_t *result_pool,
2482 apr_pool_t *scratch_pool)
2484 struct edit_baton *eb;
2485 svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
2487 struct svn_wc__shim_fetch_baton_t *sfb;
2488 const svn_delta_editor_t *inner_editor;
2489 svn_delta_shim_callbacks_t *shim_callbacks =
2490 svn_delta_shim_callbacks_default(result_pool);
2492 /* Construct an edit baton. */
2493 eb = apr_pcalloc(result_pool, sizeof(*eb));
2494 eb->default_depth = depth;
2495 eb->target_revision = edit_revision;
2496 eb->db = wc_ctx->db;
2497 eb->get_all = get_all;
2498 eb->no_ignore = no_ignore;
2499 eb->status_func = status_func;
2500 eb->status_baton = status_baton;
2501 eb->cancel_func = cancel_func;
2502 eb->cancel_baton = cancel_baton;
2503 eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
2504 eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
2507 eb->target_basename = apr_pstrdup(result_pool, target_basename);
2508 eb->root_opened = FALSE;
2510 eb->wb.db = wc_ctx->db;
2511 eb->wb.target_abspath = eb->target_abspath;
2512 eb->wb.ignore_text_mods = !check_working_copy;
2513 eb->wb.check_working_copy = check_working_copy;
2514 eb->wb.repos_locks = NULL;
2515 eb->wb.repos_root = NULL;
2517 SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
2518 wc_ctx->db, eb->target_abspath,
2519 result_pool, scratch_pool));
2521 /* Use the caller-provided ignore patterns if provided; the build-time
2522 configured defaults otherwise. */
2523 if (ignore_patterns)
2525 eb->ignores = ignore_patterns;
2529 apr_array_header_t *ignores;
2531 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
2532 eb->ignores = ignores;
2535 /* The edit baton's status structure maps to PATH, and the editor
2536 have to be aware of whether that is the anchor or the target. */
2537 SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
2538 check_working_copy, result_pool, scratch_pool));
2540 /* Construct an editor. */
2541 tree_editor->set_target_revision = set_target_revision;
2542 tree_editor->open_root = open_root;
2543 tree_editor->delete_entry = delete_entry;
2544 tree_editor->add_directory = add_directory;
2545 tree_editor->open_directory = open_directory;
2546 tree_editor->change_dir_prop = change_dir_prop;
2547 tree_editor->close_directory = close_directory;
2548 tree_editor->add_file = add_file;
2549 tree_editor->open_file = open_file;
2550 tree_editor->apply_textdelta = apply_textdelta;
2551 tree_editor->change_file_prop = change_file_prop;
2552 tree_editor->close_file = close_file;
2553 tree_editor->close_edit = close_edit;
2555 inner_editor = tree_editor;
2558 if (!server_performs_filtering
2559 && !depth_as_sticky)
2560 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2569 /* Conjoin a cancellation editor with our status editor. */
2570 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2571 inner_editor, inner_baton,
2575 if (set_locks_baton)
2576 *set_locks_baton = eb;
2578 sfb = apr_palloc(result_pool, sizeof(*sfb));
2579 sfb->db = wc_ctx->db;
2580 sfb->base_abspath = eb->anchor_abspath;
2581 sfb->fetch_base = FALSE;
2583 shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2584 shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2585 shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2586 shim_callbacks->fetch_baton = sfb;
2588 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2589 NULL, NULL, shim_callbacks,
2590 result_pool, scratch_pool));
2592 return SVN_NO_ERROR;
2595 /* Like svn_io_stat_dirent, but works case sensitive inside working
2596 copies. Before 1.8 we handled this with a selection filter inside
2598 static svn_error_t *
2599 stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
2601 const char *local_abspath,
2602 apr_pool_t *result_pool,
2603 apr_pool_t *scratch_pool)
2605 svn_boolean_t is_wcroot;
2607 /* The wcroot is "" inside the wc; handle it as not in the wc, as
2608 the case of the root is indifferent to us. */
2610 /* Note that for performance this is really just a few hashtable lookups,
2611 as we just used local_abspath for a db call in both our callers */
2612 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
2615 return svn_error_trace(
2616 svn_io_stat_dirent2(dirent, local_abspath,
2617 ! is_wcroot /* verify_truename */,
2618 TRUE /* ignore_enoent */,
2619 result_pool, scratch_pool));
2623 svn_wc__internal_walk_status(svn_wc__db_t *db,
2624 const char *local_abspath,
2626 svn_boolean_t get_all,
2627 svn_boolean_t no_ignore,
2628 svn_boolean_t ignore_text_mods,
2629 const apr_array_header_t *ignore_patterns,
2630 svn_wc_status_func4_t status_func,
2632 svn_cancel_func_t cancel_func,
2634 apr_pool_t *scratch_pool)
2636 struct walk_status_baton wb;
2637 const svn_io_dirent2_t *dirent;
2638 const struct svn_wc__db_info_t *info;
2642 wb.target_abspath = local_abspath;
2643 wb.ignore_text_mods = ignore_text_mods;
2644 wb.check_working_copy = TRUE;
2645 wb.repos_root = NULL;
2646 wb.repos_locks = NULL;
2648 /* Use the caller-provided ignore patterns if provided; the build-time
2649 configured defaults otherwise. */
2650 if (!ignore_patterns)
2652 apr_array_header_t *ignores;
2654 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
2655 ignore_patterns = ignores;
2658 err = svn_wc__db_read_single_info(&info, db, local_abspath,
2659 FALSE /* base_tree_only */,
2660 scratch_pool, scratch_pool);
2664 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2666 svn_error_clear(err);
2670 return svn_error_trace(err);
2672 wb.externals = apr_hash_make(scratch_pool);
2674 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2675 scratch_pool, scratch_pool));
2679 SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
2681 scratch_pool, scratch_pool));
2683 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2684 scratch_pool, scratch_pool));
2688 && info->has_descendants /* is dir, or was dir and has tc descendants */
2689 && info->status != svn_wc__db_status_not_present
2690 && info->status != svn_wc__db_status_excluded
2691 && info->status != svn_wc__db_status_server_excluded)
2693 SVN_ERR(get_dir_status(&wb,
2695 FALSE /* skip_root */,
2703 status_func, status_baton,
2704 cancel_func, cancel_baton,
2709 /* It may be a file or an unversioned item. And this is an explicit
2710 * target, so no ignoring. An unversioned item (file or dir) shows a
2711 * status like '?', and can yield a tree conflicted path. */
2712 err = get_child_status(&wb,
2718 status_func, status_baton,
2719 cancel_func, cancel_baton,
2722 if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2724 /* The parent is also not versioned, but it is not nice to show
2725 an error about a path a user didn't intend to touch. */
2726 svn_error_clear(err);
2727 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
2728 _("The node '%s' was not found."),
2729 svn_dirent_local_style(local_abspath,
2735 return SVN_NO_ERROR;
2739 svn_wc_walk_status(svn_wc_context_t *wc_ctx,
2740 const char *local_abspath,
2742 svn_boolean_t get_all,
2743 svn_boolean_t no_ignore,
2744 svn_boolean_t ignore_text_mods,
2745 const apr_array_header_t *ignore_patterns,
2746 svn_wc_status_func4_t status_func,
2748 svn_cancel_func_t cancel_func,
2750 apr_pool_t *scratch_pool)
2752 return svn_error_trace(
2753 svn_wc__internal_walk_status(wc_ctx->db,
2769 svn_wc_status_set_repos_locks(void *edit_baton,
2771 const char *repos_root,
2774 struct edit_baton *eb = edit_baton;
2776 eb->wb.repos_locks = locks;
2777 eb->wb.repos_root = apr_pstrdup(pool, repos_root);
2779 return SVN_NO_ERROR;
2784 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2788 svn_config_t *cfg = config
2789 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
2793 /* Check the Subversion run-time configuration for global ignores.
2794 If no configuration value exists, we fall back to our defaults. */
2795 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2796 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2797 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2798 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2800 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2801 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2802 return SVN_NO_ERROR;
2807 static svn_error_t *
2808 internal_status(svn_wc__internal_status_t **status,
2810 const char *local_abspath,
2811 svn_boolean_t check_working_copy,
2812 apr_pool_t *result_pool,
2813 apr_pool_t *scratch_pool)
2815 const svn_io_dirent2_t *dirent = NULL;
2816 const char *parent_repos_relpath;
2817 const char *parent_repos_root_url;
2818 const char *parent_repos_uuid;
2819 const struct svn_wc__db_info_t *info;
2820 svn_boolean_t is_root = FALSE;
2823 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2825 err = svn_wc__db_read_single_info(&info, db, local_abspath,
2826 !check_working_copy,
2827 scratch_pool, scratch_pool);
2831 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
2832 return svn_error_trace(err);
2834 svn_error_clear(err);
2837 if (check_working_copy)
2838 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2839 scratch_pool, scratch_pool));
2841 else if (check_working_copy)
2842 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2843 scratch_pool, scratch_pool));
2846 || info->kind == svn_node_unknown
2847 || info->status == svn_wc__db_status_not_present
2848 || info->status == svn_wc__db_status_server_excluded
2849 || info->status == svn_wc__db_status_excluded)
2850 return svn_error_trace(assemble_unversioned(status,
2853 info ? info->conflicted : FALSE,
2854 FALSE /* is_ignored */,
2855 result_pool, scratch_pool));
2857 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
2860 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
2862 /* Even though passing parent_repos_* is not required, assemble_status needs
2863 these values to determine if a node is switched */
2866 const char *const parent_abspath = svn_dirent_dirname(local_abspath,
2868 if (check_working_copy)
2869 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL,
2870 &parent_repos_relpath,
2871 &parent_repos_root_url,
2873 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2874 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2875 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2877 result_pool, scratch_pool));
2879 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL,
2880 &parent_repos_relpath,
2881 &parent_repos_root_url,
2883 NULL, NULL, NULL, NULL, NULL,
2884 NULL, NULL, NULL, NULL, NULL,
2886 result_pool, scratch_pool));
2890 parent_repos_root_url = NULL;
2891 parent_repos_relpath = NULL;
2892 parent_repos_uuid = NULL;
2895 return svn_error_trace(assemble_status(status, db, local_abspath,
2896 parent_repos_root_url,
2897 parent_repos_relpath,
2902 FALSE, check_working_copy,
2903 NULL /* repos_lock */,
2904 result_pool, scratch_pool));
2909 svn_wc_status3(svn_wc_status3_t **status,
2910 svn_wc_context_t *wc_ctx,
2911 const char *local_abspath,
2912 apr_pool_t *result_pool,
2913 apr_pool_t *scratch_pool)
2915 svn_wc__internal_status_t *stat;
2916 SVN_ERR(internal_status(&stat, wc_ctx->db, local_abspath,
2917 TRUE /* check_working_copy */,
2918 result_pool, scratch_pool));
2920 return SVN_NO_ERROR;
2924 svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
2927 /* Allocate slightly more room */
2928 svn_wc__internal_status_t *new_istat = apr_palloc(pool, sizeof(*new_istat));
2929 svn_wc_status3_t *new_stat = &new_istat->s;
2931 /* Shallow copy all members. */
2932 *new_stat = *orig_stat;
2934 /* Now go back and dup the deep items into this pool. */
2935 if (orig_stat->repos_lock)
2936 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2938 if (orig_stat->changed_author)
2939 new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
2941 if (orig_stat->ood_changed_author)
2942 new_stat->ood_changed_author
2943 = apr_pstrdup(pool, orig_stat->ood_changed_author);
2945 if (orig_stat->lock)
2946 new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
2948 if (orig_stat->changelist)
2949 new_stat->changelist
2950 = apr_pstrdup(pool, orig_stat->changelist);
2952 if (orig_stat->repos_root_url)
2953 new_stat->repos_root_url
2954 = apr_pstrdup(pool, orig_stat->repos_root_url);
2956 if (orig_stat->repos_relpath)
2957 new_stat->repos_relpath
2958 = apr_pstrdup(pool, orig_stat->repos_relpath);
2960 if (orig_stat->repos_uuid)
2961 new_stat->repos_uuid
2962 = apr_pstrdup(pool, orig_stat->repos_uuid);
2964 if (orig_stat->moved_from_abspath)
2965 new_stat->moved_from_abspath
2966 = apr_pstrdup(pool, orig_stat->moved_from_abspath);
2968 if (orig_stat->moved_to_abspath)
2969 new_stat->moved_to_abspath
2970 = apr_pstrdup(pool, orig_stat->moved_to_abspath);
2972 /* Return the new hotness. */
2977 svn_wc_get_ignores2(apr_array_header_t **patterns,
2978 svn_wc_context_t *wc_ctx,
2979 const char *local_abspath,
2981 apr_pool_t *result_pool,
2982 apr_pool_t *scratch_pool)
2984 apr_array_header_t *default_ignores;
2986 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
2987 return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
2990 result_pool, scratch_pool));