2 * status.c: construct a status structure from an entry structure
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
29 #include <apr_pools.h>
30 #include <apr_file_io.h>
33 #include "svn_pools.h"
34 #include "svn_types.h"
35 #include "svn_delta.h"
36 #include "svn_string.h"
37 #include "svn_error.h"
38 #include "svn_dirent_uri.h"
41 #include "svn_config.h"
44 #include "svn_sorts.h"
46 #include "svn_private_config.h"
51 #include "translate.h"
52 #include "tree_conflicts.h"
54 #include "private/svn_wc_private.h"
55 #include "private/svn_fspath.h"
56 #include "private/svn_editor.h"
60 /*** Baton used for walking the local status */
61 struct walk_status_baton
63 /* The DB handle for managing the working copy state. */
66 /*** External handling ***/
67 /* Target of the status */
68 const char *target_abspath;
70 /* Should we ignore text modifications? */
71 svn_boolean_t ignore_text_mods;
73 /* Externals info harvested during the status run. */
74 apr_hash_t *externals;
76 /*** Repository lock handling ***/
77 /* The repository root URL, if set. */
78 const char *repos_root;
80 /* Repository locks, if set. */
81 apr_hash_t *repos_locks;
84 /*** Editor batons ***/
88 /* For status, the "destination" of the edit. */
89 const char *anchor_abspath;
90 const char *target_abspath;
91 const char *target_basename;
93 /* The DB handle for managing the working copy state. */
95 svn_wc_context_t *wc_ctx;
97 /* The overall depth of this edit (a dir baton may override this).
99 * If this is svn_depth_unknown, the depths found in the working
100 * copy will govern the edit; or if the edit depth indicates a
101 * descent deeper than the found depths are capable of, the found
102 * depths also govern, of course (there's no point descending into
103 * something that's not there).
105 svn_depth_t default_depth;
107 /* Do we want all statuses (instead of just the interesting ones) ? */
108 svn_boolean_t get_all;
110 /* Ignore the svn:ignores. */
111 svn_boolean_t no_ignore;
113 /* The comparison revision in the repository. This is a reference
114 because this editor returns this rev to the driver directly, as
115 well as in each statushash entry. */
116 svn_revnum_t *target_revision;
118 /* Status function/baton. */
119 svn_wc_status_func4_t status_func;
122 /* Cancellation function/baton. */
123 svn_cancel_func_t cancel_func;
126 /* The configured set of default ignores. */
127 const apr_array_header_t *ignores;
129 /* Status item for the path represented by the anchor of the edit. */
130 svn_wc_status3_t *anchor_status;
132 /* Was open_root() called for this edit drive? */
133 svn_boolean_t root_opened;
135 /* The local status baton */
136 struct walk_status_baton wb;
142 /* The path to this directory. */
143 const char *local_abspath;
145 /* Basename of this directory. */
148 /* The global edit baton. */
149 struct edit_baton *edit_baton;
151 /* Baton for this directory's parent, or NULL if this is the root
153 struct dir_baton *parent_baton;
155 /* The ambient requested depth below this point in the edit. This
156 can differ from the parent baton's depth (with the edit baton
157 considered the ultimate parent baton). For example, if the
158 parent baton has svn_depth_immediates, then here we should have
159 svn_depth_empty, because there would be no further recursion, not
160 even to file children. */
163 /* Is this directory filtered out due to depth? (Note that if this
164 is TRUE, the depth field is undefined.) */
165 svn_boolean_t excluded;
167 /* 'svn status' shouldn't print status lines for things that are
168 added; we're only interest in asking if objects that the user
169 *already* has are up-to-date or not. Thus if this flag is set,
170 the next two will be ignored. :-) */
173 /* Gets set iff there's a change to this directory's properties, to
174 guide us when syncing adm files later. */
175 svn_boolean_t prop_changed;
177 /* This means (in terms of 'svn status') that some child was deleted
178 or added to the directory */
179 svn_boolean_t text_changed;
181 /* Working copy status structures for children of this directory.
182 This hash maps const char * abspaths to svn_wc_status3_t *
186 /* The pool in which this baton itself is allocated. */
189 /* The repository root relative path to this item in the repository. */
190 const char *repos_relpath;
192 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
193 svn_node_kind_t ood_kind;
194 svn_revnum_t ood_changed_rev;
195 apr_time_t ood_changed_date;
196 const char *ood_changed_author;
202 /* Absolute local path to this file */
203 const char *local_abspath;
205 /* The global edit baton. */
206 struct edit_baton *edit_baton;
208 /* Baton for this file's parent directory. */
209 struct dir_baton *dir_baton;
211 /* Pool specific to this file_baton. */
214 /* Basename of this file */
217 /* 'svn status' shouldn't print status lines for things that are
218 added; we're only interest in asking if objects that the user
219 *already* has are up-to-date or not. Thus if this flag is set,
220 the next two will be ignored. :-) */
223 /* This gets set if the file underwent a text change, which guides
224 the code that syncs up the adm dir and working copy. */
225 svn_boolean_t text_changed;
227 /* This gets set if the file underwent a prop change, which guides
228 the code that syncs up the adm dir and working copy. */
229 svn_boolean_t prop_changed;
231 /* The repository root relative path to this item in the repository. */
232 const char *repos_relpath;
234 /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
235 svn_node_kind_t ood_kind;
236 svn_revnum_t ood_changed_rev;
237 apr_time_t ood_changed_date;
239 const char *ood_changed_author;
245 /* Fill in *INFO with the information it would contain if it were
246 obtained from svn_wc__db_read_children_info. */
248 read_info(const struct svn_wc__db_info_t **info,
249 const char *local_abspath,
251 apr_pool_t *result_pool,
252 apr_pool_t *scratch_pool)
254 struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb));
255 const svn_checksum_t *checksum;
256 const char *original_repos_relpath;
258 SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind,
259 &mtb->revnum, &mtb->repos_relpath,
260 &mtb->repos_root_url, &mtb->repos_uuid,
261 &mtb->changed_rev, &mtb->changed_date,
262 &mtb->changed_author, &mtb->depth,
263 &checksum, NULL, &original_repos_relpath, NULL,
264 NULL, NULL, &mtb->lock, &mtb->recorded_size,
265 &mtb->recorded_time, &mtb->changelist,
266 &mtb->conflicted, &mtb->op_root,
267 &mtb->had_props, &mtb->props_mod,
268 &mtb->have_base, &mtb->have_more_work, NULL,
270 result_pool, scratch_pool));
272 SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool));
274 /* Maybe we have to get some shadowed lock from BASE to make our test suite
275 happy... (It might be completely unrelated, but...) */
277 && (mtb->status == svn_wc__db_status_added
278 || mtb->status == svn_wc__db_status_deleted
279 || mtb->kind == svn_node_file))
281 svn_boolean_t update_root;
282 svn_wc__db_lock_t **lock_arg = NULL;
284 if (mtb->status == svn_wc__db_status_added
285 || mtb->status == svn_wc__db_status_deleted)
286 lock_arg = &mtb->lock;
288 SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL,
289 NULL, NULL, NULL, NULL, NULL, NULL,
290 lock_arg, NULL, NULL, &update_root,
292 result_pool, scratch_pool));
294 mtb->file_external = (update_root && mtb->kind == svn_node_file);
296 if (mtb->status == svn_wc__db_status_deleted)
298 const char *moved_to_abspath;
299 const char *moved_to_op_root_abspath;
301 /* NOTE: we can't use op-root-ness as a condition here since a base
302 * node can be the root of a move and still not be an explicit
303 * op-root (having a working node with op_depth == pathelements).
305 * Both these (almost identical) situations showcase this:
311 * In both, 'bb' is moved from 'a/b', but 'a/b' has no op_depth>0
312 * node at all, as its parent 'a' is locally deleted. */
314 SVN_ERR(svn_wc__db_scan_deletion(NULL,
317 &moved_to_op_root_abspath,
319 scratch_pool, scratch_pool));
320 if (moved_to_abspath != NULL
321 && moved_to_op_root_abspath != NULL
322 && strcmp(moved_to_abspath, moved_to_op_root_abspath) == 0)
324 mtb->moved_to_abspath = apr_pstrdup(result_pool,
327 /* ### ^^^ THIS SUCKS. For at least two reasons:
328 * 1) We scan the node deletion and that's technically not necessary.
329 * We'd be fine to know if this is an actual root of a move.
330 * 2) From the elaborately calculated results, we backwards-guess
331 * whether this is a root.
332 * It works ok, and this code only gets called when a node is an
333 * explicit target of a 'status'. But it would be better to do this
335 * We could return moved-to via svn_wc__db_base_get_info() (called
336 * just above), but as moved-to is only intended to be returned for
337 * roots of a move, that doesn't fit too well. */
341 /* ### svn_wc__db_read_info() could easily return the moved-here flag. But
342 * for now... (The per-dir query for recursive status is far more optimal.)
343 * Note that this actually scans around to get the full path, for a bool.
344 * This bool then gets returned, later is evaluated, and if true leads to
345 * the same paths being scanned again. We'd want to obtain this bool here as
346 * cheaply as svn_wc__db_read_children_info() does. */
347 if (mtb->status == svn_wc__db_status_added)
349 svn_wc__db_status_t status;
351 SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL,
352 NULL, NULL, NULL, NULL,
354 result_pool, scratch_pool));
356 mtb->moved_here = (status == svn_wc__db_status_moved_here);
357 mtb->incomplete = (status == svn_wc__db_status_incomplete);
360 mtb->has_checksum = (checksum != NULL);
361 mtb->copied = (original_repos_relpath != NULL);
364 if (mtb->kind == svn_node_file
365 && (mtb->had_props || mtb->props_mod))
367 apr_hash_t *properties;
370 SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath,
371 scratch_pool, scratch_pool));
373 SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath,
374 scratch_pool, scratch_pool));
376 mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL));
384 /* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
385 information in INFO if available, falling back on
386 PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
387 finally falling back on querying DB. */
389 get_repos_root_url_relpath(const char **repos_relpath,
390 const char **repos_root_url,
391 const char **repos_uuid,
392 const struct svn_wc__db_info_t *info,
393 const char *parent_repos_relpath,
394 const char *parent_repos_root_url,
395 const char *parent_repos_uuid,
397 const char *local_abspath,
398 apr_pool_t *result_pool,
399 apr_pool_t *scratch_pool)
401 if (info->repos_relpath && info->repos_root_url)
403 *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
404 *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
405 *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
407 else if (parent_repos_relpath && parent_repos_root_url)
409 *repos_relpath = svn_relpath_join(parent_repos_relpath,
410 svn_dirent_basename(local_abspath,
413 *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
414 *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
416 else if (info->status == svn_wc__db_status_added)
418 SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
419 repos_relpath, repos_root_url,
420 repos_uuid, NULL, NULL, NULL, NULL,
422 result_pool, scratch_pool));
424 else if (info->have_base)
426 SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url,
429 result_pool, scratch_pool));
433 *repos_relpath = NULL;
434 *repos_root_url = NULL;
441 internal_status(svn_wc_status3_t **status,
443 const char *local_abspath,
444 apr_pool_t *result_pool,
445 apr_pool_t *scratch_pool);
447 /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
448 RESULT_POOL and use SCRATCH_POOL for temporary allocations.
450 PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
451 and repository relative path of the parent of LOCAL_ABSPATH or NULL if
452 LOCAL_ABSPATH doesn't have a versioned parent directory.
454 DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
455 NULL if the node does not exist on disk.
457 If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
458 *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
459 allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
460 don't check for text mods, assume there are none and set and *STATUS
461 returned to reflect that assumption.
463 The status struct's repos_lock field will be set to REPOS_LOCK.
466 assemble_status(svn_wc_status3_t **status,
468 const char *local_abspath,
469 const char *parent_repos_root_url,
470 const char *parent_repos_relpath,
471 const char *parent_repos_uuid,
472 const struct svn_wc__db_info_t *info,
473 const svn_io_dirent2_t *dirent,
474 svn_boolean_t get_all,
475 svn_boolean_t ignore_text_mods,
476 const svn_lock_t *repos_lock,
477 apr_pool_t *result_pool,
478 apr_pool_t *scratch_pool)
480 svn_wc_status3_t *stat;
481 svn_boolean_t switched_p = FALSE;
482 svn_boolean_t copied = FALSE;
483 svn_boolean_t conflicted;
484 const char *moved_from_abspath = NULL;
485 svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file))
487 : SVN_INVALID_FILESIZE;
489 /* Defaults for two main variables. */
490 enum svn_wc_status_kind node_status = svn_wc_status_normal;
491 enum svn_wc_status_kind text_status = svn_wc_status_normal;
492 enum svn_wc_status_kind prop_status = svn_wc_status_none;
496 SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool));
498 if (!info->repos_relpath || !parent_repos_relpath)
502 /* A node is switched if it doesn't have the implied repos_relpath */
503 const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
504 info->repos_relpath);
505 switched_p = !name || (strcmp(name,
506 svn_dirent_basename(local_abspath, NULL))
510 if (info->status == svn_wc__db_status_incomplete || info->incomplete)
512 /* Highest precedence. */
513 node_status = svn_wc_status_incomplete;
515 else if (info->status == svn_wc__db_status_deleted)
517 node_status = svn_wc_status_deleted;
519 if (!info->have_base || info->have_more_work || info->copied)
521 else if (!info->have_more_work && info->have_base)
525 const char *work_del_abspath;
527 /* Find out details of our deletion. */
528 SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
529 &work_del_abspath, NULL,
531 scratch_pool, scratch_pool));
532 if (work_del_abspath)
533 copied = TRUE; /* Working deletion */
538 /* Examine whether our target is missing or obstructed. To detect
539 * obstructions, we have to look at the on-disk status in DIRENT. */
540 svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
544 if (!dirent || dirent->kind != expected_kind)
546 /* A present or added node should be on disk, so it is
547 reported missing or obstructed. */
548 if (!dirent || dirent->kind == svn_node_none)
549 node_status = svn_wc_status_missing;
551 node_status = svn_wc_status_obstructed;
555 /* Does the node have props? */
556 if (info->status != svn_wc__db_status_deleted)
559 prop_status = svn_wc_status_modified;
560 else if (info->had_props)
561 prop_status = svn_wc_status_normal;
564 /* If NODE_STATUS is still normal, after the above checks, then
565 we should proceed to refine the status.
567 If it was changed, then the subdir is incomplete or missing/obstructed.
569 if (info->kind != svn_node_dir
570 && node_status == svn_wc_status_normal)
572 svn_boolean_t text_modified_p = FALSE;
574 /* Implement predecence rules: */
576 /* 1. Set the two main variables to "discovered" values first (M, C).
577 Together, these two stati are of lowest precedence, and C has
578 precedence over M. */
580 /* If the entry is a file, check for textual modifications */
581 if ((info->kind == svn_node_file
582 || info->kind == svn_node_symlink)
584 && (info->special == (dirent && dirent->special))
585 #endif /* HAVE_SYMLINK */
588 /* If the on-disk dirent exactly matches the expected state
589 skip all operations in svn_wc__internal_text_modified_p()
590 to avoid an extra filestat for every file, which can be
591 expensive on network drives as a filestat usually can't
593 if (!info->has_checksum)
594 text_modified_p = TRUE; /* Local addition -> Modified */
595 else if (ignore_text_mods
597 && info->recorded_size != SVN_INVALID_FILESIZE
598 && info->recorded_time != 0
599 && info->recorded_size == dirent->filesize
600 && info->recorded_time == dirent->mtime))
601 text_modified_p = FALSE;
605 err = svn_wc__internal_file_modified_p(&text_modified_p,
607 FALSE, scratch_pool);
611 if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
612 return svn_error_trace(err);
614 /* An access denied is very common on Windows when another
615 application has the file open. Previously we ignored
616 this error in svn_wc__text_modified_internal_p, where it
617 should have really errored. */
618 svn_error_clear(err);
619 text_modified_p = TRUE;
624 else if (info->special != (dirent && dirent->special))
625 node_status = svn_wc_status_obstructed;
626 #endif /* HAVE_SYMLINK */
629 text_status = svn_wc_status_modified;
632 conflicted = info->conflicted;
635 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
637 /* ### Check if the conflict was resolved by removing the marker files.
638 ### This should really be moved to the users of this API */
639 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
641 db, local_abspath, scratch_pool));
643 if (!text_conflicted && !prop_conflicted && !tree_conflicted)
647 if (node_status == svn_wc_status_normal)
649 /* 2. Possibly overwrite the text_status variable with "scheduled"
650 states from the entry (A, D, R). As a group, these states are
651 of medium precedence. They also override any C or M that may
652 be in the prop_status field at this point, although they do not
653 override a C text status.*/
654 if (info->status == svn_wc__db_status_added)
656 copied = info->copied;
658 { /* Keep status normal */ }
659 else if (!info->have_base && !info->have_more_work)
661 /* Simple addition or copy, no replacement */
662 node_status = svn_wc_status_added;
666 svn_wc__db_status_t below_working;
667 svn_boolean_t have_base, have_work;
669 SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
674 /* If the node is not present or deleted (read: not present
675 in working), then the node is not a replacement */
676 if (below_working != svn_wc__db_status_not_present
677 && below_working != svn_wc__db_status_deleted)
679 node_status = svn_wc_status_replaced;
682 node_status = svn_wc_status_added;
685 /* Get moved-from info (only for potential op-roots of a move). */
686 if (info->moved_here && info->op_root)
689 err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
691 result_pool, scratch_pool);
695 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
696 return svn_error_trace(err);
698 svn_error_clear(err);
699 /* We are no longer moved... So most likely we are somehow
700 changing the db for things like resolving conflicts. */
702 moved_from_abspath = NULL;
709 if (node_status == svn_wc_status_normal)
710 node_status = text_status;
712 if (node_status == svn_wc_status_normal
713 && prop_status != svn_wc_status_none)
714 node_status = prop_status;
716 /* 5. Easy out: unless we're fetching -every- entry, don't bother
717 to allocate a struct for an uninteresting entry. */
720 if (((node_status == svn_wc_status_none)
721 || (node_status == svn_wc_status_normal))
727 && (! info->changelist)
734 /* 6. Build and return a status structure. */
736 stat = apr_pcalloc(result_pool, sizeof(**status));
741 stat->kind = svn_node_dir;
744 case svn_node_symlink:
745 stat->kind = svn_node_file;
747 case svn_node_unknown:
749 stat->kind = svn_node_unknown;
751 stat->depth = info->depth;
752 stat->filesize = filesize;
753 stat->node_status = node_status;
754 stat->text_status = text_status;
755 stat->prop_status = prop_status;
756 stat->repos_node_status = svn_wc_status_none; /* default */
757 stat->repos_text_status = svn_wc_status_none; /* default */
758 stat->repos_prop_status = svn_wc_status_none; /* default */
759 stat->switched = switched_p;
760 stat->copied = copied;
761 stat->repos_lock = repos_lock;
762 stat->revision = info->revnum;
763 stat->changed_rev = info->changed_rev;
764 if (info->changed_author)
765 stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
766 stat->changed_date = info->changed_date;
768 stat->ood_kind = svn_node_none;
769 stat->ood_changed_rev = SVN_INVALID_REVNUM;
770 stat->ood_changed_date = 0;
771 stat->ood_changed_author = NULL;
773 SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
774 &stat->repos_root_url,
775 &stat->repos_uuid, info,
776 parent_repos_relpath,
777 parent_repos_root_url,
780 result_pool, scratch_pool));
784 svn_lock_t *lck = svn_lock_create(result_pool);
785 lck->path = stat->repos_relpath;
786 lck->token = info->lock->token;
787 lck->owner = info->lock->owner;
788 lck->comment = info->lock->comment;
789 lck->creation_date = info->lock->date;
795 stat->locked = info->locked;
796 stat->conflicted = conflicted;
797 stat->versioned = TRUE;
798 if (info->changelist)
799 stat->changelist = apr_pstrdup(result_pool, info->changelist);
801 stat->moved_from_abspath = moved_from_abspath;
802 if (info->moved_to_abspath)
803 stat->moved_to_abspath = apr_pstrdup(result_pool, info->moved_to_abspath);
805 stat->file_external = info->file_external;
812 /* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
813 available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
814 temporary allocations.
816 If IS_IGNORED is non-zero and this is a non-versioned entity, set
817 the node_status to svn_wc_status_none. Otherwise set the
818 node_status to svn_wc_status_unversioned.
821 assemble_unversioned(svn_wc_status3_t **status,
823 const char *local_abspath,
824 const svn_io_dirent2_t *dirent,
825 svn_boolean_t tree_conflicted,
826 svn_boolean_t is_ignored,
827 apr_pool_t *result_pool,
828 apr_pool_t *scratch_pool)
830 svn_wc_status3_t *stat;
832 /* return a fairly blank structure. */
833 stat = apr_pcalloc(result_pool, sizeof(*stat));
835 /*stat->versioned = FALSE;*/
836 stat->kind = svn_node_unknown; /* not versioned */
837 stat->depth = svn_depth_unknown;
838 stat->filesize = (dirent && dirent->kind == svn_node_file)
840 : SVN_INVALID_FILESIZE;
841 stat->node_status = svn_wc_status_none;
842 stat->text_status = svn_wc_status_none;
843 stat->prop_status = svn_wc_status_none;
844 stat->repos_node_status = svn_wc_status_none;
845 stat->repos_text_status = svn_wc_status_none;
846 stat->repos_prop_status = svn_wc_status_none;
848 /* If this path has no entry, but IS present on disk, it's
849 unversioned. If this file is being explicitly ignored (due
850 to matching an ignore-pattern), the node_status is set to
851 svn_wc_status_ignored. Otherwise the node_status is set to
852 svn_wc_status_unversioned. */
853 if (dirent && dirent->kind != svn_node_none)
856 stat->node_status = svn_wc_status_ignored;
858 stat->node_status = svn_wc_status_unversioned;
860 else if (tree_conflicted)
862 /* If this path has no entry, is NOT present on disk, and IS a
863 tree conflict victim, report it as conflicted. */
864 stat->node_status = svn_wc_status_conflicted;
867 stat->revision = SVN_INVALID_REVNUM;
868 stat->changed_rev = SVN_INVALID_REVNUM;
869 stat->ood_changed_rev = SVN_INVALID_REVNUM;
870 stat->ood_kind = svn_node_none;
872 /* For the case of an incoming delete to a locally deleted path during
873 an update, we get a tree conflict. */
874 stat->conflicted = tree_conflicted;
875 stat->changelist = NULL;
882 /* Given an ENTRY object representing PATH, build a status structure
883 and pass it off to the STATUS_FUNC/STATUS_BATON. All other
884 arguments are the same as those passed to assemble_status(). */
886 send_status_structure(const struct walk_status_baton *wb,
887 const char *local_abspath,
888 const char *parent_repos_root_url,
889 const char *parent_repos_relpath,
890 const char *parent_repos_uuid,
891 const struct svn_wc__db_info_t *info,
892 const svn_io_dirent2_t *dirent,
893 svn_boolean_t get_all,
894 svn_wc_status_func4_t status_func,
896 apr_pool_t *scratch_pool)
898 svn_wc_status3_t *statstruct;
899 const svn_lock_t *repos_lock = NULL;
901 /* Check for a repository lock. */
904 const char *repos_relpath, *repos_root_url, *repos_uuid;
906 SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
908 info, parent_repos_relpath,
909 parent_repos_root_url,
911 wb->db, local_abspath,
912 scratch_pool, scratch_pool));
915 /* repos_lock still uses the deprecated filesystem absolute path
917 repos_lock = svn_hash_gets(wb->repos_locks,
918 svn_fspath__join("/", repos_relpath,
923 SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
924 parent_repos_root_url, parent_repos_relpath,
926 info, dirent, get_all, wb->ignore_text_mods,
927 repos_lock, scratch_pool, scratch_pool));
929 if (statstruct && status_func)
930 return svn_error_trace((*status_func)(status_baton, local_abspath,
931 statstruct, scratch_pool));
937 /* Store in *PATTERNS a list of ignores collected from svn:ignore properties
938 on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
939 repository ancestors (as cached in the working copy), including the default
940 ignores passed in as IGNORES.
942 Upon return, *PATTERNS will contain zero or more (const char *)
943 patterns from the value of the SVN_PROP_IGNORE property set on
944 the working directory path.
946 IGNORES is a list of patterns to include; typically this will
947 be the default ignores as, for example, specified in a config file.
949 DB, LOCAL_ABSPATH is used to access the working copy.
951 Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
953 None of the arguments may be NULL.
956 collect_ignore_patterns(apr_array_header_t **patterns,
958 const char *local_abspath,
959 const apr_array_header_t *ignores,
960 apr_pool_t *result_pool,
961 apr_pool_t *scratch_pool)
965 apr_array_header_t *inherited_props;
968 /* ### assert we are passed a directory? */
970 *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
972 /* Copy default ignores into the local PATTERNS array. */
973 for (i = 0; i < ignores->nelts; i++)
975 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
976 APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
980 err = svn_wc__db_read_inherited_props(&inherited_props, &props,
982 SVN_PROP_INHERITABLE_IGNORES,
983 scratch_pool, scratch_pool);
987 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
988 return svn_error_trace(err);
990 svn_error_clear(err);
996 const svn_string_t *value;
998 value = svn_hash_gets(props, SVN_PROP_IGNORE);
1000 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
1003 value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
1005 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
1009 for (i = 0; i < inherited_props->nelts; i++)
1011 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
1012 inherited_props, i, svn_prop_inherited_item_t *);
1013 const svn_string_t *value;
1015 value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
1018 svn_cstring_split_append(*patterns, value->data,
1019 "\n\r", FALSE, result_pool);
1022 return SVN_NO_ERROR;
1026 /* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
1027 LOCAL_ABSPATH is the drop location for, or an intermediate directory
1028 of the drop location for, an externals definition. Use SCRATCH_POOL
1030 static svn_boolean_t
1031 is_external_path(apr_hash_t *externals,
1032 const char *local_abspath,
1033 apr_pool_t *scratch_pool)
1035 apr_hash_index_t *hi;
1037 /* First try: does the path exist as a key in the hash? */
1038 if (svn_hash_gets(externals, local_abspath))
1041 /* Failing that, we need to check if any external is a child of
1043 for (hi = apr_hash_first(scratch_pool, externals);
1045 hi = apr_hash_next(hi))
1047 const char *external_abspath = svn__apr_hash_index_key(hi);
1049 if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
1057 /* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
1058 for it through STATUS_FUNC/STATUS_BATON unless this path is being
1059 ignored. This function should never be called on a versioned entry.
1061 LOCAL_ABSPATH is the path to the unversioned file whose status is being
1062 requested. PATH_KIND is the node kind of NAME as determined by the
1063 caller. PATH_SPECIAL is the special status of the path, also determined
1065 PATTERNS points to a list of filename patterns which are marked as ignored.
1066 None of these parameter may be NULL.
1068 If NO_IGNORE is TRUE, the item will be added regardless of
1069 whether it is ignored; otherwise we will only add the item if it
1070 does not match any of the patterns in PATTERN or INHERITED_IGNORES.
1072 Allocate everything in POOL.
1074 static svn_error_t *
1075 send_unversioned_item(const struct walk_status_baton *wb,
1076 const char *local_abspath,
1077 const svn_io_dirent2_t *dirent,
1078 svn_boolean_t tree_conflicted,
1079 const apr_array_header_t *patterns,
1080 svn_boolean_t no_ignore,
1081 svn_wc_status_func4_t status_func,
1083 apr_pool_t *scratch_pool)
1085 svn_boolean_t is_ignored;
1086 svn_boolean_t is_external;
1087 svn_wc_status3_t *status;
1088 const char *base_name = svn_dirent_basename(local_abspath, NULL);
1090 is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
1091 SVN_ERR(assemble_unversioned(&status,
1092 wb->db, local_abspath,
1093 dirent, tree_conflicted,
1095 scratch_pool, scratch_pool));
1097 is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
1099 status->node_status = svn_wc_status_external;
1101 /* We can have a tree conflict on an unversioned path, i.e. an incoming
1102 * delete on a locally deleted path during an update. Don't ever ignore
1104 if (status->conflicted)
1107 /* If we aren't ignoring it, or if it's an externals path, pass this
1108 entry to the status func. */
1112 return svn_error_trace((*status_func)(status_baton, local_abspath,
1113 status, scratch_pool));
1115 return SVN_NO_ERROR;
1118 static svn_error_t *
1119 get_dir_status(const struct walk_status_baton *wb,
1120 const char *local_abspath,
1121 svn_boolean_t skip_this_dir,
1122 const char *parent_repos_root_url,
1123 const char *parent_repos_relpath,
1124 const char *parent_repos_uuid,
1125 const struct svn_wc__db_info_t *dir_info,
1126 const svn_io_dirent2_t *dirent,
1127 const apr_array_header_t *ignore_patterns,
1129 svn_boolean_t get_all,
1130 svn_boolean_t no_ignore,
1131 svn_wc_status_func4_t status_func,
1133 svn_cancel_func_t cancel_func,
1135 apr_pool_t *scratch_pool);
1137 /* Send out a status structure according to the information gathered on one
1138 * child node. (Basically this function is the guts of the loop in
1139 * get_dir_status() and of get_child_status().)
1141 * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
1142 * dirname of LOCAL_ABSPATH.
1144 * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
1145 * be an unversioned file or dir, or a versioned file. For versioned
1146 * directories use get_dir_status() instead.
1148 * INFO may be NULL for an unversioned node. If such node has a tree conflict,
1149 * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
1150 * UNVERSIONED_TREE_CONFLICTED is ignored.
1152 * DIRENT should reflect LOCAL_ABSPATH's dirent information.
1154 * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
1155 * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
1157 * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
1158 * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
1159 * containing all ignore patterns, as returned by collect_ignore_patterns() on
1160 * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
1161 * non-NULL, it is assumed it already holds those results.
1162 * This speeds up repeated calls with the same PARENT_ABSPATH.
1164 * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
1165 * allocations are made in SCRATCH_POOL.
1167 * The remaining parameters correspond to get_dir_status(). */
1168 static svn_error_t *
1169 one_child_status(const struct walk_status_baton *wb,
1170 const char *local_abspath,
1171 const char *parent_abspath,
1172 const struct svn_wc__db_info_t *info,
1173 const svn_io_dirent2_t *dirent,
1174 const char *dir_repos_root_url,
1175 const char *dir_repos_relpath,
1176 const char *dir_repos_uuid,
1177 svn_boolean_t unversioned_tree_conflicted,
1178 apr_array_header_t **collected_ignore_patterns,
1179 const apr_array_header_t *ignore_patterns,
1181 svn_boolean_t get_all,
1182 svn_boolean_t no_ignore,
1183 svn_wc_status_func4_t status_func,
1185 svn_cancel_func_t cancel_func,
1187 apr_pool_t *result_pool,
1188 apr_pool_t *scratch_pool)
1190 svn_boolean_t conflicted = info ? info->conflicted
1191 : unversioned_tree_conflicted;
1194 && info->status != svn_wc__db_status_not_present
1195 && info->status != svn_wc__db_status_excluded
1196 && info->status != svn_wc__db_status_server_excluded
1197 && !(info->kind == svn_node_unknown
1198 && info->status == svn_wc__db_status_normal))
1200 if (depth == svn_depth_files
1201 && info->kind == svn_node_dir)
1203 return SVN_NO_ERROR;
1206 SVN_ERR(send_status_structure(wb, local_abspath,
1210 info, dirent, get_all,
1211 status_func, status_baton,
1214 /* Descend in subdirectories. */
1215 if (depth == svn_depth_infinity
1216 && info->kind == svn_node_dir)
1218 SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
1219 dir_repos_root_url, dir_repos_relpath,
1220 dir_repos_uuid, info,
1221 dirent, ignore_patterns,
1222 svn_depth_infinity, get_all,
1224 status_func, status_baton,
1225 cancel_func, cancel_baton,
1229 return SVN_NO_ERROR;
1232 /* If conflicted, fall right through to unversioned.
1233 * With depth_files, show all conflicts, even if their report is only
1234 * about directories. A tree conflict may actually report two different
1235 * kinds, so it's not so easy to define what depth=files means. We could go
1236 * look up the kinds in the conflict ... just show all. */
1239 /* Selected node, but not found */
1241 return SVN_NO_ERROR;
1243 if (depth == svn_depth_files && dirent->kind == svn_node_dir)
1244 return SVN_NO_ERROR;
1246 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
1248 return SVN_NO_ERROR;
1251 /* The node exists on disk but there is no versioned information about it,
1252 * or it doesn't exist but is a tree conflicted path or should be
1253 * reported not-present. */
1255 /* Why pass ignore patterns on a tree conflicted node, even if it should
1256 * always show up in clients' status reports anyway? Because the calling
1257 * client decides whether to ignore, and thus this flag needs to be
1258 * determined. For example, in 'svn status', plain unversioned nodes show
1259 * as '? C', where ignored ones show as 'I C'. */
1261 if (ignore_patterns && ! *collected_ignore_patterns)
1262 SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
1263 wb->db, parent_abspath, ignore_patterns,
1264 result_pool, scratch_pool));
1266 SVN_ERR(send_unversioned_item(wb,
1270 *collected_ignore_patterns,
1272 status_func, status_baton,
1275 return SVN_NO_ERROR;
1278 /* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
1279 for all its child nodes (according to DEPTH) through STATUS_FUNC /
1282 If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
1283 All subdirs reached by recursion will be reported regardless of this
1286 PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
1287 URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
1288 retrieving them again. Otherwise they must be NULL.
1290 DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
1291 it again. Otherwise it must be NULL.
1293 DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
1294 so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
1296 Other arguments are the same as those passed to
1297 svn_wc_get_status_editor5(). */
1298 static svn_error_t *
1299 get_dir_status(const struct walk_status_baton *wb,
1300 const char *local_abspath,
1301 svn_boolean_t skip_this_dir,
1302 const char *parent_repos_root_url,
1303 const char *parent_repos_relpath,
1304 const char *parent_repos_uuid,
1305 const struct svn_wc__db_info_t *dir_info,
1306 const svn_io_dirent2_t *dirent,
1307 const apr_array_header_t *ignore_patterns,
1309 svn_boolean_t get_all,
1310 svn_boolean_t no_ignore,
1311 svn_wc_status_func4_t status_func,
1313 svn_cancel_func_t cancel_func,
1315 apr_pool_t *scratch_pool)
1317 const char *dir_repos_root_url;
1318 const char *dir_repos_relpath;
1319 const char *dir_repos_uuid;
1320 apr_hash_t *dirents, *nodes, *conflicts, *all_children;
1321 apr_array_header_t *sorted_children;
1322 apr_array_header_t *collected_ignore_patterns = NULL;
1323 apr_pool_t *iterpool;
1328 SVN_ERR(cancel_func(cancel_baton));
1330 if (depth == svn_depth_unknown)
1331 depth = svn_depth_infinity;
1333 iterpool = svn_pool_create(scratch_pool);
1335 err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool,
1338 && (APR_STATUS_IS_ENOENT(err->apr_err)
1339 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
1341 svn_error_clear(err);
1342 dirents = apr_hash_make(scratch_pool);
1348 SVN_ERR(read_info(&dir_info, local_abspath, wb->db,
1349 scratch_pool, iterpool));
1351 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1352 &dir_repos_uuid, dir_info,
1353 parent_repos_relpath,
1354 parent_repos_root_url, parent_repos_uuid,
1355 wb->db, local_abspath,
1356 scratch_pool, iterpool));
1358 /* Create a hash containing all children. The source hashes
1359 don't all map the same types, but only the keys of the result
1360 hash are subsequently used. */
1361 SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1362 wb->db, local_abspath,
1363 scratch_pool, iterpool));
1365 all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
1366 if (apr_hash_count(conflicts) > 0)
1367 all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
1369 /* Handle "this-dir" first. */
1370 if (! skip_this_dir)
1372 /* This code is not conditional on HAVE_SYMLINK as some systems that do
1373 not allow creating symlinks (!HAVE_SYMLINK) can still encounter
1374 symlinks (or in case of Windows also 'Junctions') created by other
1377 Without this block a working copy in the root of a junction is
1378 reported as an obstruction, because the junction itself is reported as
1381 Systems that have no symlink support at all, would always see
1382 dirent->special as FALSE, so even there enabling this code shouldn't
1385 if (dirent->special)
1387 svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
1389 /* We're being pointed to "this-dir" via a symlink.
1390 * Get the real node kind and pretend the path is not a symlink.
1391 * This prevents send_status_structure() from treating this-dir
1392 * as a directory obstructed by a file. */
1393 SVN_ERR(svn_io_check_resolved_path(local_abspath,
1394 &this_dirent->kind, iterpool));
1395 this_dirent->special = FALSE;
1396 SVN_ERR(send_status_structure(wb, local_abspath,
1397 parent_repos_root_url,
1398 parent_repos_relpath,
1400 dir_info, this_dirent, get_all,
1401 status_func, status_baton,
1405 SVN_ERR(send_status_structure(wb, local_abspath,
1406 parent_repos_root_url,
1407 parent_repos_relpath,
1409 dir_info, dirent, get_all,
1410 status_func, status_baton,
1414 /* If the requested depth is empty, we only need status on this-dir. */
1415 if (depth == svn_depth_empty)
1416 return SVN_NO_ERROR;
1418 /* Walk all the children of this directory. */
1419 sorted_children = svn_sort__hash(all_children,
1420 svn_sort_compare_items_lexically,
1422 for (i = 0; i < sorted_children->nelts; i++)
1426 svn_sort__item_t item;
1427 const char *child_abspath;
1428 svn_io_dirent2_t *child_dirent;
1429 const struct svn_wc__db_info_t *child_info;
1431 svn_pool_clear(iterpool);
1433 item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
1437 child_abspath = svn_dirent_join(local_abspath, key, iterpool);
1438 child_dirent = apr_hash_get(dirents, key, klen);
1439 child_info = apr_hash_get(nodes, key, klen);
1441 SVN_ERR(one_child_status(wb,
1449 apr_hash_get(conflicts, key, klen) != NULL,
1450 &collected_ignore_patterns,
1463 /* Destroy our subpools. */
1464 svn_pool_destroy(iterpool);
1466 return SVN_NO_ERROR;
1469 /* Send an svn_wc_status3_t * structure for the versioned file, or for the
1470 * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
1471 * explicit target). Does not recurse.
1473 * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
1474 * unversioned nodes. An unversioned and tree-conflicted node however should
1475 * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
1477 * DIRENT should reflect LOCAL_ABSPATH.
1479 * All allocations made in SCRATCH_POOL.
1481 * The remaining parameters correspond to get_dir_status(). */
1482 static svn_error_t *
1483 get_child_status(const struct walk_status_baton *wb,
1484 const char *local_abspath,
1485 const struct svn_wc__db_info_t *info,
1486 const svn_io_dirent2_t *dirent,
1487 const apr_array_header_t *ignore_patterns,
1488 svn_boolean_t get_all,
1489 svn_wc_status_func4_t status_func,
1491 svn_cancel_func_t cancel_func,
1493 apr_pool_t *scratch_pool)
1495 const char *dir_repos_root_url;
1496 const char *dir_repos_relpath;
1497 const char *dir_repos_uuid;
1498 const struct svn_wc__db_info_t *dir_info;
1499 apr_array_header_t *collected_ignore_patterns = NULL;
1500 const char *parent_abspath = svn_dirent_dirname(local_abspath,
1504 SVN_ERR(cancel_func(cancel_baton));
1506 if (dirent->kind == svn_node_none)
1509 SVN_ERR(read_info(&dir_info, parent_abspath, wb->db,
1510 scratch_pool, scratch_pool));
1512 SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
1513 &dir_repos_uuid, dir_info,
1515 wb->db, parent_abspath,
1516 scratch_pool, scratch_pool));
1518 /* An unversioned node with a tree conflict will see an INFO != NULL here,
1519 * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
1520 * effect and INFO->CONFLICTED counts.
1521 * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
1523 SVN_ERR(one_child_status(wb,
1531 FALSE, /* unversioned_tree_conflicted */
1532 &collected_ignore_patterns,
1536 TRUE, /* no_ignore. This is an explicit target. */
1543 return SVN_NO_ERROR;
1550 /* A faux status callback function for stashing STATUS item in an hash
1551 (which is the BATON), keyed on PATH. This implements the
1552 svn_wc_status_func4_t interface. */
1553 static svn_error_t *
1554 hash_stash(void *baton,
1556 const svn_wc_status3_t *status,
1557 apr_pool_t *scratch_pool)
1559 apr_hash_t *stat_hash = baton;
1560 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1561 assert(! svn_hash_gets(stat_hash, path));
1562 svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path),
1563 svn_wc_dup_status3(status, hash_pool));
1565 return SVN_NO_ERROR;
1569 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1570 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1571 yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
1572 create a new status struct using the hash's pool.
1574 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1575 of date (ood) information we want to set in BATON. This is necessary
1576 because this function tweaks the status of out-of-date directories
1577 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1578 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1579 contains the ood info we want to bubble up to ancestor directories so these
1580 accurately reflect the fact they have an ood descendant.
1582 Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
1583 status structure's "network" fields.
1585 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1588 If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
1589 optionally the revision path was deleted, in all other cases it must
1590 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1591 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1592 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1593 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
1594 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1595 ood_last_cmt_rev value - see comment below.
1597 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1598 static svn_error_t *
1599 tweak_statushash(void *baton,
1600 void *this_dir_baton,
1601 svn_boolean_t is_dir_baton,
1603 const char *local_abspath,
1604 enum svn_wc_status_kind repos_node_status,
1605 enum svn_wc_status_kind repos_text_status,
1606 enum svn_wc_status_kind repos_prop_status,
1607 svn_revnum_t deleted_rev,
1608 const svn_lock_t *repos_lock,
1609 apr_pool_t *scratch_pool)
1611 svn_wc_status3_t *statstruct;
1613 apr_hash_t *statushash;
1616 statushash = ((struct dir_baton *) baton)->statii;
1618 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1619 pool = apr_hash_pool_get(statushash);
1621 /* Is PATH already a hash-key? */
1622 statstruct = svn_hash_gets(statushash, local_abspath);
1624 /* If not, make it so. */
1627 /* If this item isn't being added, then we're most likely
1628 dealing with a non-recursive (or at least partially
1629 non-recursive) working copy. Due to bugs in how the client
1630 reports the state of non-recursive working copies, the
1631 repository can send back responses about paths that don't
1632 even exist locally. Our best course here is just to ignore
1633 those responses. After all, if the client had reported
1634 correctly in the first, that path would either be mentioned
1635 as an 'add' or not mentioned at all, depending on how we
1636 eventually fix the bugs in non-recursivity. See issue
1637 #2122 for details. */
1638 if (repos_node_status != svn_wc_status_added)
1639 return SVN_NO_ERROR;
1641 /* Use the public API to get a statstruct, and put it into the hash. */
1642 SVN_ERR(internal_status(&statstruct, db, local_abspath, pool,
1644 statstruct->repos_lock = repos_lock;
1645 svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
1648 /* Merge a repos "delete" + "add" into a single "replace". */
1649 if ((repos_node_status == svn_wc_status_added)
1650 && (statstruct->repos_node_status == svn_wc_status_deleted))
1651 repos_node_status = svn_wc_status_replaced;
1653 /* Tweak the structure's repos fields. */
1654 if (repos_node_status)
1655 statstruct->repos_node_status = repos_node_status;
1656 if (repos_text_status)
1657 statstruct->repos_text_status = repos_text_status;
1658 if (repos_prop_status)
1659 statstruct->repos_prop_status = repos_prop_status;
1661 /* Copy out-of-date info. */
1664 struct dir_baton *b = this_dir_baton;
1666 if (!statstruct->repos_relpath && b->repos_relpath)
1668 if (statstruct->repos_node_status == svn_wc_status_deleted)
1670 /* When deleting PATH, BATON is for PATH's parent,
1671 so we must construct PATH's real statstruct->url. */
1672 statstruct->repos_relpath =
1673 svn_relpath_join(b->repos_relpath,
1674 svn_dirent_basename(local_abspath,
1679 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1681 statstruct->repos_root_url =
1682 b->edit_baton->anchor_status->repos_root_url;
1683 statstruct->repos_uuid =
1684 b->edit_baton->anchor_status->repos_uuid;
1687 /* The last committed date, and author for deleted items
1689 if (statstruct->repos_node_status == svn_wc_status_deleted)
1691 statstruct->ood_kind = statstruct->kind;
1693 /* Pre 1.5 servers don't provide the revision a path was deleted.
1694 So we punt and use the last committed revision of the path's
1695 parent, which has some chance of being correct. At worse it
1696 is a higher revision than the path was deleted, but this is
1697 better than nothing... */
1698 if (deleted_rev == SVN_INVALID_REVNUM)
1699 statstruct->ood_changed_rev =
1700 ((struct dir_baton *) baton)->ood_changed_rev;
1702 statstruct->ood_changed_rev = deleted_rev;
1706 statstruct->ood_kind = b->ood_kind;
1707 statstruct->ood_changed_rev = b->ood_changed_rev;
1708 statstruct->ood_changed_date = b->ood_changed_date;
1709 if (b->ood_changed_author)
1710 statstruct->ood_changed_author =
1711 apr_pstrdup(pool, b->ood_changed_author);
1717 struct file_baton *b = baton;
1718 statstruct->ood_changed_rev = b->ood_changed_rev;
1719 statstruct->ood_changed_date = b->ood_changed_date;
1720 if (!statstruct->repos_relpath && b->repos_relpath)
1722 statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
1723 statstruct->repos_root_url =
1724 b->edit_baton->anchor_status->repos_root_url;
1725 statstruct->repos_uuid =
1726 b->edit_baton->anchor_status->repos_uuid;
1728 statstruct->ood_kind = b->ood_kind;
1729 if (b->ood_changed_author)
1730 statstruct->ood_changed_author =
1731 apr_pstrdup(pool, b->ood_changed_author);
1733 return SVN_NO_ERROR;
1736 /* Returns the URL for DB */
1738 find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
1740 /* If we have no name, we're the root, return the anchor URL. */
1742 return db->edit_baton->anchor_status->repos_relpath;
1745 const char *repos_relpath;
1746 struct dir_baton *pb = db->parent_baton;
1747 const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
1749 /* Note that status->repos_relpath could be NULL in the case of a missing
1750 * directory, which means we need to recurse up another level to get
1751 * a useful relpath. */
1752 if (status && status->repos_relpath)
1753 return status->repos_relpath;
1755 repos_relpath = find_dir_repos_relpath(pb, pool);
1756 return svn_relpath_join(repos_relpath, db->name, pool);
1762 /* Create a new dir_baton for subdir PATH. */
1763 static svn_error_t *
1764 make_dir_baton(void **dir_baton,
1766 struct edit_baton *edit_baton,
1767 struct dir_baton *parent_baton,
1768 apr_pool_t *result_pool)
1770 struct dir_baton *pb = parent_baton;
1771 struct edit_baton *eb = edit_baton;
1772 struct dir_baton *d;
1773 const char *local_abspath;
1774 const svn_wc_status3_t *status_in_parent;
1775 apr_pool_t *dir_pool;
1778 dir_pool = svn_pool_create(parent_baton->pool);
1780 dir_pool = svn_pool_create(result_pool);
1782 d = apr_pcalloc(dir_pool, sizeof(*d));
1784 SVN_ERR_ASSERT(path || (! pb));
1786 /* Construct the absolute path of this directory. */
1788 local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
1790 local_abspath = eb->anchor_abspath;
1792 /* Finish populating the baton members. */
1794 d->local_abspath = local_abspath;
1795 d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
1796 d->edit_baton = edit_baton;
1797 d->parent_baton = parent_baton;
1798 d->statii = apr_hash_make(dir_pool);
1799 d->ood_changed_rev = SVN_INVALID_REVNUM;
1800 d->ood_changed_date = 0;
1801 d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
1802 d->ood_kind = svn_node_dir;
1803 d->ood_changed_author = NULL;
1809 else if (pb->depth == svn_depth_immediates)
1810 d->depth = svn_depth_empty;
1811 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1813 else if (pb->depth == svn_depth_unknown)
1814 /* This is only tentative, it can be overridden from d's entry
1816 d->depth = svn_depth_unknown;
1818 d->depth = svn_depth_infinity;
1822 d->depth = eb->default_depth;
1825 /* Get the status for this path's children. Of course, we only want
1826 to do this if the path is versioned as a directory. */
1828 status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
1830 status_in_parent = eb->anchor_status;
1832 if (status_in_parent
1833 && status_in_parent->versioned
1834 && (status_in_parent->kind == svn_node_dir)
1836 && (d->depth == svn_depth_unknown
1837 || d->depth == svn_depth_infinity
1838 || d->depth == svn_depth_files
1839 || d->depth == svn_depth_immediates)
1842 const svn_wc_status3_t *this_dir_status;
1843 const apr_array_header_t *ignores = eb->ignores;
1845 SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
1846 status_in_parent->repos_root_url,
1847 NULL /*parent_repos_relpath*/,
1848 status_in_parent->repos_uuid,
1850 NULL /* dirent */, ignores,
1851 d->depth == svn_depth_files
1853 : svn_depth_immediates,
1855 hash_stash, d->statii,
1856 eb->cancel_func, eb->cancel_baton,
1859 /* If we found a depth here, it should govern. */
1860 this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
1861 if (this_dir_status && this_dir_status->versioned
1862 && (d->depth == svn_depth_unknown
1863 || d->depth > status_in_parent->depth))
1865 d->depth = this_dir_status->depth;
1870 return SVN_NO_ERROR;
1874 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1875 NAME is just one component, not a path. */
1876 static struct file_baton *
1877 make_file_baton(struct dir_baton *parent_dir_baton,
1881 struct dir_baton *pb = parent_dir_baton;
1882 struct edit_baton *eb = pb->edit_baton;
1883 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1885 /* Finish populating the baton members. */
1886 f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
1887 f->name = svn_dirent_basename(f->local_abspath, NULL);
1891 f->ood_changed_rev = SVN_INVALID_REVNUM;
1892 f->ood_changed_date = 0;
1893 f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
1895 f->ood_kind = svn_node_file;
1896 f->ood_changed_author = NULL;
1902 * Return a boolean answer to the question "Is @a status something that
1903 * should be reported?". @a no_ignore and @a get_all are the same as
1904 * svn_wc_get_status_editor4().
1906 static svn_boolean_t
1907 is_sendable_status(const svn_wc_status3_t *status,
1908 svn_boolean_t no_ignore,
1909 svn_boolean_t get_all)
1911 /* If the repository status was touched at all, it's interesting. */
1912 if (status->repos_node_status != svn_wc_status_none)
1915 /* If there is a lock in the repository, send it. */
1916 if (status->repos_lock)
1919 if (status->conflicted)
1922 /* If the item is ignored, and we don't want ignores, skip it. */
1923 if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
1926 /* If we want everything, we obviously want this single-item subset
1931 /* If the item is unversioned, display it. */
1932 if (status->node_status == svn_wc_status_unversioned)
1935 /* If the text, property or tree state is interesting, send it. */
1936 if ((status->node_status != svn_wc_status_none
1937 && (status->node_status != svn_wc_status_normal)))
1940 /* If it's switched, send it. */
1941 if (status->switched)
1944 /* If there is a lock token, send it. */
1945 if (status->versioned && status->lock)
1948 /* If the entry is associated with a changelist, send it. */
1949 if (status->changelist)
1952 /* Otherwise, don't send it. */
1957 /* Baton for mark_status. */
1960 svn_wc_status_func4_t real_status_func; /* real status function */
1961 void *real_status_baton; /* real status baton */
1964 /* A status callback function which wraps the *real* status
1965 function/baton. It simply sets the "repos_node_status" field of the
1966 STATUS to svn_wc_status_deleted and passes it off to the real
1967 status func/baton. Implements svn_wc_status_func4_t */
1968 static svn_error_t *
1969 mark_deleted(void *baton,
1970 const char *local_abspath,
1971 const svn_wc_status3_t *status,
1972 apr_pool_t *scratch_pool)
1974 struct status_baton *sb = baton;
1975 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
1976 new_status->repos_node_status = svn_wc_status_deleted;
1977 return sb->real_status_func(sb->real_status_baton, local_abspath,
1978 new_status, scratch_pool);
1982 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1983 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1984 directory itself. Descend into subdirectories according to DEPTH.
1985 Also, if DIR_WAS_DELETED is set, each status that is reported
1986 through this function will have its repos_text_status field showing
1987 a deletion. Use POOL for all allocations. */
1988 static svn_error_t *
1989 handle_statii(struct edit_baton *eb,
1990 const char *dir_repos_root_url,
1991 const char *dir_repos_relpath,
1992 const char *dir_repos_uuid,
1994 svn_boolean_t dir_was_deleted,
1998 const apr_array_header_t *ignores = eb->ignores;
1999 apr_hash_index_t *hi;
2000 apr_pool_t *iterpool = svn_pool_create(pool);
2001 svn_wc_status_func4_t status_func = eb->status_func;
2002 void *status_baton = eb->status_baton;
2003 struct status_baton sb;
2005 if (dir_was_deleted)
2007 sb.real_status_func = eb->status_func;
2008 sb.real_status_baton = eb->status_baton;
2009 status_func = mark_deleted;
2013 /* Loop over all the statii still in our hash, handling each one. */
2014 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
2016 const char *local_abspath = svn__apr_hash_index_key(hi);
2017 svn_wc_status3_t *status = svn__apr_hash_index_val(hi);
2019 /* Clear the subpool. */
2020 svn_pool_clear(iterpool);
2022 /* Now, handle the status. We don't recurse for svn_depth_immediates
2023 because we already have the subdirectories' statii. */
2024 if (status->versioned && status->kind == svn_node_dir
2025 && (depth == svn_depth_unknown
2026 || depth == svn_depth_infinity))
2028 SVN_ERR(get_dir_status(&eb->wb,
2029 local_abspath, TRUE,
2030 dir_repos_root_url, dir_repos_relpath,
2034 ignores, depth, eb->get_all, eb->no_ignore,
2035 status_func, status_baton,
2036 eb->cancel_func, eb->cancel_baton,
2039 if (dir_was_deleted)
2040 status->repos_node_status = svn_wc_status_deleted;
2041 if (is_sendable_status(status, eb->no_ignore, eb->get_all))
2042 SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status,
2046 /* Destroy the subpool. */
2047 svn_pool_destroy(iterpool);
2049 return SVN_NO_ERROR;
2053 /*----------------------------------------------------------------------*/
2055 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
2057 /* An svn_delta_editor_t function. */
2058 static svn_error_t *
2059 set_target_revision(void *edit_baton,
2060 svn_revnum_t target_revision,
2063 struct edit_baton *eb = edit_baton;
2064 *(eb->target_revision) = target_revision;
2065 return SVN_NO_ERROR;
2069 /* An svn_delta_editor_t function. */
2070 static svn_error_t *
2071 open_root(void *edit_baton,
2072 svn_revnum_t base_revision,
2076 struct edit_baton *eb = edit_baton;
2077 eb->root_opened = TRUE;
2078 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
2082 /* An svn_delta_editor_t function. */
2083 static svn_error_t *
2084 delete_entry(const char *path,
2085 svn_revnum_t revision,
2089 struct dir_baton *db = parent_baton;
2090 struct edit_baton *eb = db->edit_baton;
2091 const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
2093 /* Note: when something is deleted, it's okay to tweak the
2094 statushash immediately. No need to wait until close_file or
2095 close_dir, because there's no risk of having to honor the 'added'
2096 flag. We already know this item exists in the working copy. */
2097 SVN_ERR(tweak_statushash(db, db, TRUE, eb->db,
2099 svn_wc_status_deleted, 0, 0, revision, NULL, pool));
2101 /* Mark the parent dir -- it lost an entry (unless that parent dir
2102 is the root node and we're not supposed to report on the root
2104 if (db->parent_baton && (! *eb->target_basename))
2105 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db,
2107 svn_wc_status_modified, svn_wc_status_modified,
2108 0, SVN_INVALID_REVNUM, NULL, pool));
2110 return SVN_NO_ERROR;
2114 /* An svn_delta_editor_t function. */
2115 static svn_error_t *
2116 add_directory(const char *path,
2118 const char *copyfrom_path,
2119 svn_revnum_t copyfrom_revision,
2123 struct dir_baton *pb = parent_baton;
2124 struct edit_baton *eb = pb->edit_baton;
2125 struct dir_baton *new_db;
2127 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
2129 /* Make this dir as added. */
2130 new_db = *child_baton;
2131 new_db->added = TRUE;
2133 /* Mark the parent as changed; it gained an entry. */
2134 pb->text_changed = TRUE;
2136 return SVN_NO_ERROR;
2140 /* An svn_delta_editor_t function. */
2141 static svn_error_t *
2142 open_directory(const char *path,
2144 svn_revnum_t base_revision,
2148 struct dir_baton *pb = parent_baton;
2149 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
2153 /* An svn_delta_editor_t function. */
2154 static svn_error_t *
2155 change_dir_prop(void *dir_baton,
2157 const svn_string_t *value,
2160 struct dir_baton *db = dir_baton;
2161 if (svn_wc_is_normal_prop(name))
2162 db->prop_changed = TRUE;
2164 /* Note any changes to the repository. */
2167 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2168 db->ood_changed_rev = SVN_STR_TO_REV(value->data);
2169 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2170 db->ood_changed_author = apr_pstrdup(db->pool, value->data);
2171 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2174 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
2175 db->ood_changed_date = tm;
2179 return SVN_NO_ERROR;
2184 /* An svn_delta_editor_t function. */
2185 static svn_error_t *
2186 close_directory(void *dir_baton,
2189 struct dir_baton *db = dir_baton;
2190 struct dir_baton *pb = db->parent_baton;
2191 struct edit_baton *eb = db->edit_baton;
2192 apr_pool_t *scratch_pool = db->pool;
2194 /* If nothing has changed and directory has no out of
2195 date descendants, return. */
2196 if (db->added || db->prop_changed || db->text_changed
2197 || db->ood_changed_rev != SVN_INVALID_REVNUM)
2199 enum svn_wc_status_kind repos_node_status;
2200 enum svn_wc_status_kind repos_text_status;
2201 enum svn_wc_status_kind repos_prop_status;
2203 /* If this is a new directory, add it to the statushash. */
2206 repos_node_status = svn_wc_status_added;
2207 repos_text_status = svn_wc_status_none;
2208 repos_prop_status = db->prop_changed ? svn_wc_status_added
2209 : svn_wc_status_none;
2213 repos_node_status = (db->text_changed || db->prop_changed)
2214 ? svn_wc_status_modified
2215 : svn_wc_status_none;
2216 repos_text_status = db->text_changed ? svn_wc_status_modified
2217 : svn_wc_status_none;
2218 repos_prop_status = db->prop_changed ? svn_wc_status_modified
2219 : svn_wc_status_none;
2222 /* Maybe add this directory to its parent's status hash. Note
2223 that tweak_statushash won't do anything if repos_text_status
2224 is not svn_wc_status_added. */
2227 /* ### When we add directory locking, we need to find a
2228 ### directory lock here. */
2229 SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath,
2230 repos_node_status, repos_text_status,
2231 repos_prop_status, SVN_INVALID_REVNUM, NULL,
2236 /* We're editing the root dir of the WC. As its repos
2237 status info isn't otherwise set, set it directly to
2238 trigger invocation of the status callback below. */
2239 eb->anchor_status->repos_node_status = repos_node_status;
2240 eb->anchor_status->repos_prop_status = repos_prop_status;
2241 eb->anchor_status->repos_text_status = repos_text_status;
2243 /* If the root dir is out of date set the ood info directly too. */
2244 if (db->ood_changed_rev != eb->anchor_status->revision)
2246 eb->anchor_status->ood_changed_rev = db->ood_changed_rev;
2247 eb->anchor_status->ood_changed_date = db->ood_changed_date;
2248 eb->anchor_status->ood_kind = db->ood_kind;
2249 eb->anchor_status->ood_changed_author =
2250 apr_pstrdup(pool, db->ood_changed_author);
2255 /* Handle this directory's statuses, and then note in the parent
2256 that this has been done. */
2257 if (pb && ! db->excluded)
2259 svn_boolean_t was_deleted = FALSE;
2260 const svn_wc_status3_t *dir_status;
2262 /* See if the directory was deleted or replaced. */
2263 dir_status = svn_hash_gets(pb->statii, db->local_abspath);
2265 ((dir_status->repos_node_status == svn_wc_status_deleted)
2266 || (dir_status->repos_node_status == svn_wc_status_replaced)))
2269 /* Now do the status reporting. */
2270 SVN_ERR(handle_statii(eb,
2271 dir_status ? dir_status->repos_root_url : NULL,
2272 dir_status ? dir_status->repos_relpath : NULL,
2273 dir_status ? dir_status->repos_uuid : NULL,
2274 db->statii, was_deleted, db->depth, scratch_pool));
2275 if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
2277 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2278 dir_status, scratch_pool));
2279 svn_hash_sets(pb->statii, db->local_abspath, NULL);
2283 /* If this is the top-most directory, and the operation had a
2284 target, we should only report the target. */
2285 if (*eb->target_basename)
2287 const svn_wc_status3_t *tgt_status;
2289 tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
2292 if (tgt_status->versioned
2293 && tgt_status->kind == svn_node_dir)
2295 SVN_ERR(get_dir_status(&eb->wb,
2296 eb->target_abspath, TRUE,
2297 NULL, NULL, NULL, NULL,
2301 eb->get_all, eb->no_ignore,
2302 eb->status_func, eb->status_baton,
2303 eb->cancel_func, eb->cancel_baton,
2306 if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
2307 SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
2308 tgt_status, scratch_pool));
2313 /* Otherwise, we report on all our children and ourself.
2314 Note that our directory couldn't have been deleted,
2315 because it is the root of the edit drive. */
2316 SVN_ERR(handle_statii(eb,
2317 eb->anchor_status->repos_root_url,
2318 eb->anchor_status->repos_relpath,
2319 eb->anchor_status->repos_uuid,
2320 db->statii, FALSE, eb->default_depth,
2322 if (is_sendable_status(eb->anchor_status, eb->no_ignore,
2324 SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
2325 eb->anchor_status, scratch_pool));
2326 eb->anchor_status = NULL;
2330 svn_pool_clear(scratch_pool); /* Clear baton and its pool */
2332 return SVN_NO_ERROR;
2337 /* An svn_delta_editor_t function. */
2338 static svn_error_t *
2339 add_file(const char *path,
2341 const char *copyfrom_path,
2342 svn_revnum_t copyfrom_revision,
2346 struct dir_baton *pb = parent_baton;
2347 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2349 /* Mark parent dir as changed */
2350 pb->text_changed = TRUE;
2352 /* Make this file as added. */
2353 new_fb->added = TRUE;
2355 *file_baton = new_fb;
2356 return SVN_NO_ERROR;
2360 /* An svn_delta_editor_t function. */
2361 static svn_error_t *
2362 open_file(const char *path,
2364 svn_revnum_t base_revision,
2368 struct dir_baton *pb = parent_baton;
2369 struct file_baton *new_fb = make_file_baton(pb, path, pool);
2371 *file_baton = new_fb;
2372 return SVN_NO_ERROR;
2376 /* An svn_delta_editor_t function. */
2377 static svn_error_t *
2378 apply_textdelta(void *file_baton,
2379 const char *base_checksum,
2381 svn_txdelta_window_handler_t *handler,
2382 void **handler_baton)
2384 struct file_baton *fb = file_baton;
2386 /* Mark file as having textual mods. */
2387 fb->text_changed = TRUE;
2389 /* Send back a NULL window handler -- we don't need the actual diffs. */
2390 *handler_baton = NULL;
2391 *handler = svn_delta_noop_window_handler;
2393 return SVN_NO_ERROR;
2397 /* An svn_delta_editor_t function. */
2398 static svn_error_t *
2399 change_file_prop(void *file_baton,
2401 const svn_string_t *value,
2404 struct file_baton *fb = file_baton;
2405 if (svn_wc_is_normal_prop(name))
2406 fb->prop_changed = TRUE;
2408 /* Note any changes to the repository. */
2411 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
2412 fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
2413 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
2414 fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
2416 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
2419 SVN_ERR(svn_time_from_cstring(&tm, value->data,
2420 fb->dir_baton->pool));
2421 fb->ood_changed_date = tm;
2425 return SVN_NO_ERROR;
2429 /* An svn_delta_editor_t function. */
2430 static svn_error_t *
2431 close_file(void *file_baton,
2432 const char *text_checksum, /* ignored, as we receive no data */
2435 struct file_baton *fb = file_baton;
2436 enum svn_wc_status_kind repos_node_status;
2437 enum svn_wc_status_kind repos_text_status;
2438 enum svn_wc_status_kind repos_prop_status;
2439 const svn_lock_t *repos_lock = NULL;
2441 /* If nothing has changed, return. */
2442 if (! (fb->added || fb->prop_changed || fb->text_changed))
2443 return SVN_NO_ERROR;
2445 /* If this is a new file, add it to the statushash. */
2448 repos_node_status = svn_wc_status_added;
2449 repos_text_status = fb->text_changed ? svn_wc_status_modified
2450 : 0 /* don't tweak */;
2451 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2452 : 0 /* don't tweak */;
2454 if (fb->edit_baton->wb.repos_locks)
2456 const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
2459 /* repos_lock still uses the deprecated filesystem absolute path
2461 const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
2464 repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
2465 svn_fspath__join("/", repos_relpath,
2471 repos_node_status = (fb->text_changed || fb->prop_changed)
2472 ? svn_wc_status_modified
2473 : 0 /* don't tweak */;
2474 repos_text_status = fb->text_changed ? svn_wc_status_modified
2475 : 0 /* don't tweak */;
2476 repos_prop_status = fb->prop_changed ? svn_wc_status_modified
2477 : 0 /* don't tweak */;
2480 return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
2481 fb->local_abspath, repos_node_status,
2482 repos_text_status, repos_prop_status,
2483 SVN_INVALID_REVNUM, repos_lock, pool);
2486 /* An svn_delta_editor_t function. */
2487 static svn_error_t *
2488 close_edit(void *edit_baton,
2491 struct edit_baton *eb = edit_baton;
2493 /* If we get here and the root was not opened as part of the edit,
2494 we need to transmit statuses for everything. Otherwise, we
2496 if (eb->root_opened)
2497 return SVN_NO_ERROR;
2499 SVN_ERR(svn_wc_walk_status(eb->wc_ctx,
2512 return SVN_NO_ERROR;
2517 /*** Public API ***/
2520 svn_wc__get_status_editor(const svn_delta_editor_t **editor,
2522 void **set_locks_baton,
2523 svn_revnum_t *edit_revision,
2524 svn_wc_context_t *wc_ctx,
2525 const char *anchor_abspath,
2526 const char *target_basename,
2528 svn_boolean_t get_all,
2529 svn_boolean_t no_ignore,
2530 svn_boolean_t depth_as_sticky,
2531 svn_boolean_t server_performs_filtering,
2532 const apr_array_header_t *ignore_patterns,
2533 svn_wc_status_func4_t status_func,
2535 svn_cancel_func_t cancel_func,
2537 apr_pool_t *result_pool,
2538 apr_pool_t *scratch_pool)
2540 struct edit_baton *eb;
2541 svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
2543 struct svn_wc__shim_fetch_baton_t *sfb;
2544 const svn_delta_editor_t *inner_editor;
2545 svn_delta_shim_callbacks_t *shim_callbacks =
2546 svn_delta_shim_callbacks_default(result_pool);
2548 /* Construct an edit baton. */
2549 eb = apr_pcalloc(result_pool, sizeof(*eb));
2550 eb->default_depth = depth;
2551 eb->target_revision = edit_revision;
2552 eb->db = wc_ctx->db;
2553 eb->wc_ctx = wc_ctx;
2554 eb->get_all = get_all;
2555 eb->no_ignore = no_ignore;
2556 eb->status_func = status_func;
2557 eb->status_baton = status_baton;
2558 eb->cancel_func = cancel_func;
2559 eb->cancel_baton = cancel_baton;
2560 eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
2561 eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
2564 eb->target_basename = apr_pstrdup(result_pool, target_basename);
2565 eb->root_opened = FALSE;
2567 eb->wb.db = wc_ctx->db;
2568 eb->wb.target_abspath = eb->target_abspath;
2569 eb->wb.ignore_text_mods = FALSE;
2570 eb->wb.repos_locks = NULL;
2571 eb->wb.repos_root = NULL;
2573 SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
2574 wc_ctx->db, eb->target_abspath,
2575 result_pool, scratch_pool));
2577 /* Use the caller-provided ignore patterns if provided; the build-time
2578 configured defaults otherwise. */
2579 if (ignore_patterns)
2581 eb->ignores = ignore_patterns;
2585 apr_array_header_t *ignores;
2587 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
2588 eb->ignores = ignores;
2591 /* The edit baton's status structure maps to PATH, and the editor
2592 have to be aware of whether that is the anchor or the target. */
2593 SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
2594 result_pool, scratch_pool));
2596 /* Construct an editor. */
2597 tree_editor->set_target_revision = set_target_revision;
2598 tree_editor->open_root = open_root;
2599 tree_editor->delete_entry = delete_entry;
2600 tree_editor->add_directory = add_directory;
2601 tree_editor->open_directory = open_directory;
2602 tree_editor->change_dir_prop = change_dir_prop;
2603 tree_editor->close_directory = close_directory;
2604 tree_editor->add_file = add_file;
2605 tree_editor->open_file = open_file;
2606 tree_editor->apply_textdelta = apply_textdelta;
2607 tree_editor->change_file_prop = change_file_prop;
2608 tree_editor->close_file = close_file;
2609 tree_editor->close_edit = close_edit;
2611 inner_editor = tree_editor;
2614 if (!server_performs_filtering
2615 && !depth_as_sticky)
2616 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2625 /* Conjoin a cancellation editor with our status editor. */
2626 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2627 inner_editor, inner_baton,
2631 if (set_locks_baton)
2632 *set_locks_baton = eb;
2634 sfb = apr_palloc(result_pool, sizeof(*sfb));
2635 sfb->db = wc_ctx->db;
2636 sfb->base_abspath = eb->anchor_abspath;
2637 sfb->fetch_base = FALSE;
2639 shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2640 shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2641 shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2642 shim_callbacks->fetch_baton = sfb;
2644 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2645 NULL, NULL, shim_callbacks,
2646 result_pool, scratch_pool));
2648 return SVN_NO_ERROR;
2651 /* Like svn_io_stat_dirent, but works case sensitive inside working
2652 copies. Before 1.8 we handled this with a selection filter inside
2654 static svn_error_t *
2655 stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
2657 const char *local_abspath,
2658 apr_pool_t *result_pool,
2659 apr_pool_t *scratch_pool)
2661 svn_boolean_t is_wcroot;
2663 /* The wcroot is "" inside the wc; handle it as not in the wc, as
2664 the case of the root is indifferent to us. */
2666 /* Note that for performance this is really just a few hashtable lookups,
2667 as we just used local_abspath for a db call in both our callers */
2668 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
2671 return svn_error_trace(
2672 svn_io_stat_dirent2(dirent, local_abspath,
2673 ! is_wcroot /* verify_truename */,
2674 TRUE /* ignore_enoent */,
2675 result_pool, scratch_pool));
2679 svn_wc__internal_walk_status(svn_wc__db_t *db,
2680 const char *local_abspath,
2682 svn_boolean_t get_all,
2683 svn_boolean_t no_ignore,
2684 svn_boolean_t ignore_text_mods,
2685 const apr_array_header_t *ignore_patterns,
2686 svn_wc_status_func4_t status_func,
2688 svn_cancel_func_t cancel_func,
2690 apr_pool_t *scratch_pool)
2692 struct walk_status_baton wb;
2693 const svn_io_dirent2_t *dirent;
2694 const struct svn_wc__db_info_t *info;
2698 wb.target_abspath = local_abspath;
2699 wb.ignore_text_mods = ignore_text_mods;
2700 wb.repos_root = NULL;
2701 wb.repos_locks = NULL;
2703 /* Use the caller-provided ignore patterns if provided; the build-time
2704 configured defaults otherwise. */
2705 if (!ignore_patterns)
2707 apr_array_header_t *ignores;
2709 SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
2710 ignore_patterns = ignores;
2713 err = read_info(&info, local_abspath, db, scratch_pool, scratch_pool);
2717 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2719 svn_error_clear(err);
2723 return svn_error_trace(err);
2725 wb.externals = apr_hash_make(scratch_pool);
2727 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2728 scratch_pool, scratch_pool));
2732 SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
2734 scratch_pool, scratch_pool));
2736 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2737 scratch_pool, scratch_pool));
2741 && info->kind == svn_node_dir
2742 && info->status != svn_wc__db_status_not_present
2743 && info->status != svn_wc__db_status_excluded
2744 && info->status != svn_wc__db_status_server_excluded)
2746 SVN_ERR(get_dir_status(&wb,
2748 FALSE /* skip_root */,
2756 status_func, status_baton,
2757 cancel_func, cancel_baton,
2762 /* It may be a file or an unversioned item. And this is an explicit
2763 * target, so no ignoring. An unversioned item (file or dir) shows a
2764 * status like '?', and can yield a tree conflicted path. */
2765 err = get_child_status(&wb,
2771 status_func, status_baton,
2772 cancel_func, cancel_baton,
2775 if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
2777 /* The parent is also not versioned, but it is not nice to show
2778 an error about a path a user didn't intend to touch. */
2779 svn_error_clear(err);
2780 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
2781 _("The node '%s' was not found."),
2782 svn_dirent_local_style(local_abspath,
2788 return SVN_NO_ERROR;
2792 svn_wc_walk_status(svn_wc_context_t *wc_ctx,
2793 const char *local_abspath,
2795 svn_boolean_t get_all,
2796 svn_boolean_t no_ignore,
2797 svn_boolean_t ignore_text_mods,
2798 const apr_array_header_t *ignore_patterns,
2799 svn_wc_status_func4_t status_func,
2801 svn_cancel_func_t cancel_func,
2803 apr_pool_t *scratch_pool)
2805 return svn_error_trace(
2806 svn_wc__internal_walk_status(wc_ctx->db,
2822 svn_wc_status_set_repos_locks(void *edit_baton,
2824 const char *repos_root,
2827 struct edit_baton *eb = edit_baton;
2829 eb->wb.repos_locks = locks;
2830 eb->wb.repos_root = apr_pstrdup(pool, repos_root);
2832 return SVN_NO_ERROR;
2837 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2841 svn_config_t *cfg = config
2842 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
2846 /* Check the Subversion run-time configuration for global ignores.
2847 If no configuration value exists, we fall back to our defaults. */
2848 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2849 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2850 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2851 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2853 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2854 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2855 return SVN_NO_ERROR;
2860 static svn_error_t *
2861 internal_status(svn_wc_status3_t **status,
2863 const char *local_abspath,
2864 apr_pool_t *result_pool,
2865 apr_pool_t *scratch_pool)
2867 const svn_io_dirent2_t *dirent;
2868 svn_node_kind_t node_kind;
2869 const char *parent_repos_relpath;
2870 const char *parent_repos_root_url;
2871 const char *parent_repos_uuid;
2872 svn_wc__db_status_t node_status;
2873 svn_boolean_t conflicted;
2874 svn_boolean_t is_root = FALSE;
2877 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2879 err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL,
2880 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2881 NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
2882 NULL, NULL, NULL, NULL, NULL, NULL,
2884 scratch_pool, scratch_pool);
2888 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
2889 return svn_error_trace(err);
2891 svn_error_clear(err);
2892 node_kind = svn_node_unknown;
2893 /* Ensure conflicted is always set, but don't hide tree conflicts
2894 on 'hidden' nodes. */
2897 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
2898 scratch_pool, scratch_pool));
2901 SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
2902 scratch_pool, scratch_pool));
2904 if (node_kind != svn_node_unknown
2905 && (node_status == svn_wc__db_status_not_present
2906 || node_status == svn_wc__db_status_server_excluded
2907 || node_status == svn_wc__db_status_excluded))
2909 node_kind = svn_node_unknown;
2912 if (node_kind == svn_node_unknown)
2913 return svn_error_trace(assemble_unversioned(status,
2916 FALSE /* is_ignored */,
2917 result_pool, scratch_pool));
2919 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
2922 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
2926 svn_wc__db_status_t parent_status;
2927 const char *parent_abspath = svn_dirent_dirname(local_abspath,
2930 err = svn_wc__db_read_info(&parent_status, NULL, NULL,
2931 &parent_repos_relpath, &parent_repos_root_url,
2932 &parent_repos_uuid, NULL, NULL, NULL,
2933 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2934 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2935 NULL, NULL, NULL, NULL,
2937 result_pool, scratch_pool);
2939 if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
2940 || SVN_WC__ERR_IS_NOT_CURRENT_WC(err)))
2942 svn_error_clear(err);
2943 parent_repos_root_url = NULL;
2944 parent_repos_relpath = NULL;
2945 parent_repos_uuid = NULL;
2951 parent_repos_root_url = NULL;
2952 parent_repos_relpath = NULL;
2953 parent_repos_uuid = NULL;
2956 return svn_error_trace(assemble_status(status, db, local_abspath,
2957 parent_repos_root_url,
2958 parent_repos_relpath,
2964 NULL /* repos_lock */,
2965 result_pool, scratch_pool));
2970 svn_wc_status3(svn_wc_status3_t **status,
2971 svn_wc_context_t *wc_ctx,
2972 const char *local_abspath,
2973 apr_pool_t *result_pool,
2974 apr_pool_t *scratch_pool)
2976 return svn_error_trace(
2977 internal_status(status, wc_ctx->db, local_abspath, result_pool,
2982 svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
2985 svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2987 /* Shallow copy all members. */
2988 *new_stat = *orig_stat;
2990 /* Now go back and dup the deep items into this pool. */
2991 if (orig_stat->repos_lock)
2992 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2994 if (orig_stat->changed_author)
2995 new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
2997 if (orig_stat->ood_changed_author)
2998 new_stat->ood_changed_author
2999 = apr_pstrdup(pool, orig_stat->ood_changed_author);
3001 if (orig_stat->lock)
3002 new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
3004 if (orig_stat->changelist)
3005 new_stat->changelist
3006 = apr_pstrdup(pool, orig_stat->changelist);
3008 if (orig_stat->repos_root_url)
3009 new_stat->repos_root_url
3010 = apr_pstrdup(pool, orig_stat->repos_root_url);
3012 if (orig_stat->repos_relpath)
3013 new_stat->repos_relpath
3014 = apr_pstrdup(pool, orig_stat->repos_relpath);
3016 if (orig_stat->repos_uuid)
3017 new_stat->repos_uuid
3018 = apr_pstrdup(pool, orig_stat->repos_uuid);
3020 if (orig_stat->moved_from_abspath)
3021 new_stat->moved_from_abspath
3022 = apr_pstrdup(pool, orig_stat->moved_from_abspath);
3024 if (orig_stat->moved_to_abspath)
3025 new_stat->moved_to_abspath
3026 = apr_pstrdup(pool, orig_stat->moved_to_abspath);
3028 /* Return the new hotness. */
3033 svn_wc_get_ignores2(apr_array_header_t **patterns,
3034 svn_wc_context_t *wc_ctx,
3035 const char *local_abspath,
3037 apr_pool_t *result_pool,
3038 apr_pool_t *scratch_pool)
3040 apr_array_header_t *default_ignores;
3042 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
3043 return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
3046 result_pool, scratch_pool));