Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / status.c
blob8ee3005db2e789353de1657b2498c7ef38b80b77
1 /*
2 * status.c: construct a status structure from an entry structure
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <assert.h>
22 #include <string.h>
23 #include <apr_pools.h>
24 #include <apr_file_io.h>
25 #include <apr_hash.h>
26 #include "svn_pools.h"
27 #include "svn_types.h"
28 #include "svn_delta.h"
29 #include "svn_string.h"
30 #include "svn_error.h"
31 #include "svn_path.h"
32 #include "svn_io.h"
33 #include "svn_config.h"
34 #include "svn_time.h"
35 #include "svn_private_config.h"
37 #include "wc.h"
38 #include "lock.h"
39 #include "props.h"
40 #include "translate.h"
42 #include "private/svn_wc_private.h"
45 /*** Editor batons ***/
47 struct edit_baton
49 /* For status, the "destination" of the edit. */
50 const char *anchor;
51 const char *target;
52 svn_wc_adm_access_t *adm_access;
54 /* The overall depth of this edit (a dir baton may override this).
56 * If this is svn_depth_unknown, the depths found in the working
57 * copy will govern the edit; or if the edit depth indicates a
58 * descent deeper than the found depths are capable of, the found
59 * depths also govern, of course (there's no point descending into
60 * something that's not there).
62 svn_depth_t default_depth;
64 /* Do we want all statuses (instead of just the interesting ones) ? */
65 svn_boolean_t get_all;
67 /* Ignore the svn:ignores. */
68 svn_boolean_t no_ignore;
70 /* The comparison revision in the repository. This is a reference
71 because this editor returns this rev to the driver directly, as
72 well as in each statushash entry. */
73 svn_revnum_t *target_revision;
75 /* Status function/baton. */
76 svn_wc_status_func2_t status_func;
77 void *status_baton;
79 /* Cancellation function/baton. */
80 svn_cancel_func_t cancel_func;
81 void *cancel_baton;
83 /* The configured set of default ignores. */
84 apr_array_header_t *ignores;
86 /* Externals info harvested during the status run. */
87 svn_wc_traversal_info_t *traversal_info;
88 apr_hash_t *externals;
90 /* Status item for the path represented by the anchor of the edit. */
91 svn_wc_status2_t *anchor_status;
93 /* Was open_root() called for this edit drive? */
94 svn_boolean_t root_opened;
96 /* The repository root URL, if set. */
97 const char *repos_root;
99 /* Repository locks, if set. */
100 apr_hash_t *repos_locks;
104 struct dir_baton
106 /* The path to this directory. */
107 const char *path;
109 /* Basename of this directory. */
110 const char *name;
112 /* The global edit baton. */
113 struct edit_baton *edit_baton;
115 /* Baton for this directory's parent, or NULL if this is the root
116 directory. */
117 struct dir_baton *parent_baton;
119 /* The ambient requested depth below this point in the edit. This
120 can differ from the parent baton's depth (with the edit baton
121 considered the ultimate parent baton). For example, if the
122 parent baton has svn_depth_immediates, then here we should have
123 svn_depth_empty, because there would be no further recursion, not
124 even to file children. */
125 svn_depth_t depth;
127 /* Is this directory filtered out due to depth? (Note that if this
128 is TRUE, the depth field is undefined.) */
129 svn_boolean_t excluded;
131 /* 'svn status' shouldn't print status lines for things that are
132 added; we're only interest in asking if objects that the user
133 *already* has are up-to-date or not. Thus if this flag is set,
134 the next two will be ignored. :-) */
135 svn_boolean_t added;
137 /* Gets set iff there's a change to this directory's properties, to
138 guide us when syncing adm files later. */
139 svn_boolean_t prop_changed;
141 /* This means (in terms of 'svn status') that some child was deleted
142 or added to the directory */
143 svn_boolean_t text_changed;
145 /* Working copy status structures for children of this directory.
146 This hash maps const char * paths (relative to the root of the
147 edit) to svn_wc_status2_t * status items. */
148 apr_hash_t *statii;
150 /* The pool in which this baton itself is allocated. */
151 apr_pool_t *pool;
153 /* The URI to this item in the repository. */
154 const char *url;
156 /* out-of-date info corresponding to ood_* fields in svn_wc_status2_t. */
157 svn_revnum_t ood_last_cmt_rev;
158 apr_time_t ood_last_cmt_date;
159 svn_node_kind_t ood_kind;
160 const char *ood_last_cmt_author;
164 struct file_baton
166 /* The global edit baton. */
167 struct edit_baton *edit_baton;
169 /* Baton for this file's parent directory. */
170 struct dir_baton *dir_baton;
172 /* Pool specific to this file_baton. */
173 apr_pool_t *pool;
175 /* Name of this file (its entry in the directory). */
176 const char *name;
178 /* Path to this file, either abs or relative to the change-root. */
179 const char *path;
181 /* 'svn status' shouldn't print status lines for things that are
182 added; we're only interest in asking if objects that the user
183 *already* has are up-to-date or not. Thus if this flag is set,
184 the next two will be ignored. :-) */
185 svn_boolean_t added;
187 /* This gets set if the file underwent a text change, which guides
188 the code that syncs up the adm dir and working copy. */
189 svn_boolean_t text_changed;
191 /* This gets set if the file underwent a prop change, which guides
192 the code that syncs up the adm dir and working copy. */
193 svn_boolean_t prop_changed;
195 /* The URI to this item in the repository. */
196 const char *url;
198 /* out-of-date info corresponding to ood_* fields in svn_wc_status2_t. */
199 svn_revnum_t ood_last_cmt_rev;
200 apr_time_t ood_last_cmt_date;
201 svn_node_kind_t ood_kind;
202 const char *ood_last_cmt_author;
206 /** Code **/
208 /* Fill in *STATUS for PATH, whose entry data is in ENTRY. Allocate
209 *STATUS in POOL.
211 ENTRY may be null, for non-versioned entities. In this case, we
212 will assemble a special status structure item which implies a
213 non-versioned thing.
215 PARENT_ENTRY is the entry for the parent directory of PATH, it may be
216 NULL if ENTRY is NULL or if PATH is a working copy root. The lifetime
217 of PARENT_ENTRY's pool is not important.
219 PATH_KIND is the node kind of PATH as determined by the caller.
220 NOTE: this may be svn_node_unknown if the caller has made no such
221 determination.
223 If PATH_KIND is not svn_node_unknown, PATH_SPECIAL indicates whether
224 the entry is a special file.
226 If GET_ALL is zero, and ENTRY is not locally modified, then *STATUS
227 will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
228 allocated and returned no matter what.
230 If IS_IGNORED is non-zero and this is a non-versioned entity, set
231 the text_status to svn_wc_status_none. Otherwise set the
232 text_status to svn_wc_status_unversioned.
234 If non-NULL, look up a repository lock in REPOS_LOCKS and set the repos_lock
235 field of the status struct to that lock if it exists. If REPOS_LOCKS is
236 non-NULL, REPOS_ROOT must contain the repository root URL of the entry.
238 static svn_error_t *
239 assemble_status(svn_wc_status2_t **status,
240 const char *path,
241 svn_wc_adm_access_t *adm_access,
242 const svn_wc_entry_t *entry,
243 const svn_wc_entry_t *parent_entry,
244 svn_node_kind_t path_kind, svn_boolean_t path_special,
245 svn_boolean_t get_all,
246 svn_boolean_t is_ignored,
247 apr_hash_t *repos_locks,
248 const char *repos_root,
249 apr_pool_t *pool)
251 svn_wc_status2_t *stat;
252 svn_boolean_t has_props;
253 svn_boolean_t text_modified_p = FALSE;
254 svn_boolean_t prop_modified_p = FALSE;
255 svn_boolean_t locked_p = FALSE;
256 svn_boolean_t switched_p = FALSE;
257 #ifdef HAVE_SYMLINK
258 svn_boolean_t wc_special;
259 #endif /* HAVE_SYMLINK */
261 /* Defaults for two main variables. */
262 enum svn_wc_status_kind final_text_status = svn_wc_status_normal;
263 enum svn_wc_status_kind final_prop_status = svn_wc_status_none;
265 svn_lock_t *repos_lock = NULL;
267 /* Check for a repository lock. */
268 if (repos_locks)
270 const char *abs_path;
272 if (entry && entry->url)
273 abs_path = entry->url + strlen(repos_root);
274 else if (parent_entry && parent_entry->url)
275 abs_path = svn_path_join(parent_entry->url + strlen(repos_root),
276 svn_path_basename(path, pool), pool);
277 else
278 abs_path = NULL;
280 if (abs_path)
281 repos_lock = apr_hash_get(repos_locks,
282 svn_path_uri_decode(abs_path, pool),
283 APR_HASH_KEY_STRING);
286 /* Check the path kind for PATH. */
287 if (path_kind == svn_node_unknown)
288 SVN_ERR(svn_io_check_special_path(path, &path_kind, &path_special,
289 pool));
291 if (! entry)
293 /* return a blank structure. */
294 stat = apr_pcalloc(pool, sizeof(*stat));
295 stat->entry = NULL;
296 stat->text_status = svn_wc_status_none;
297 stat->prop_status = svn_wc_status_none;
298 stat->repos_text_status = svn_wc_status_none;
299 stat->repos_prop_status = svn_wc_status_none;
300 stat->locked = FALSE;
301 stat->copied = FALSE;
302 stat->switched = FALSE;
304 /* If this path has no entry, but IS present on disk, it's
305 unversioned. If this file is being explicitly ignored (due
306 to matching an ignore-pattern), the text_status is set to
307 svn_wc_status_ignored. Otherwise the text_status is set to
308 svn_wc_status_unversioned. */
309 if (path_kind != svn_node_none)
311 if (is_ignored)
312 stat->text_status = svn_wc_status_ignored;
313 else
314 stat->text_status = svn_wc_status_unversioned;
317 stat->repos_lock = repos_lock;
318 stat->url = NULL;
319 stat->ood_last_cmt_rev = SVN_INVALID_REVNUM;
320 stat->ood_last_cmt_date = 0;
321 stat->ood_kind = svn_node_none;
322 stat->ood_last_cmt_author = NULL;
324 *status = stat;
325 return SVN_NO_ERROR;
328 /* Someone either deleted the administrative directory in the versioned
329 subdir, or deleted the directory altogether and created a new one.
330 In any case, what is currently there is in the way.
332 if (entry->kind == svn_node_dir)
334 if (path_kind == svn_node_dir)
336 if (svn_wc__adm_missing(adm_access, path))
337 final_text_status = svn_wc_status_obstructed;
339 else if (path_kind != svn_node_none)
340 final_text_status = svn_wc_status_obstructed;
343 /* Is this item switched? Well, to be switched it must have both an URL
344 and a parent with an URL, at the very least.
345 If this is the root folder on the (virtual) disk, entry and parent_entry
346 will be equal. */
347 if (entry->url && parent_entry && parent_entry->url &&
348 entry != parent_entry)
350 /* An item is switched if its working copy basename differs from the
351 basename of its URL. */
352 if (strcmp(svn_path_uri_encode(svn_path_basename(path, pool), pool),
353 svn_path_basename(entry->url, pool)))
354 switched_p = TRUE;
356 /* An item is switched if its URL, without the basename, does not
357 equal its parent's URL. */
358 if (! switched_p
359 && strcmp(svn_path_dirname(entry->url, pool),
360 parent_entry->url))
361 switched_p = TRUE;
364 if (final_text_status != svn_wc_status_obstructed)
366 /* Implement predecence rules: */
368 /* 1. Set the two main variables to "discovered" values first (M, C).
369 Together, these two stati are of lowest precedence, and C has
370 precedence over M. */
372 /* Does the entry have props? */
373 SVN_ERR(svn_wc__has_props(&has_props, path, adm_access, pool));
374 if (has_props)
375 final_prop_status = svn_wc_status_normal;
377 /* If the entry has a property file, see if it has local changes. */
378 SVN_ERR(svn_wc_props_modified_p(&prop_modified_p, path, adm_access,
379 pool));
381 #ifdef HAVE_SYMLINK
382 if (has_props)
383 SVN_ERR(svn_wc__get_special(&wc_special, path, adm_access, pool));
384 else
385 wc_special = FALSE;
386 #endif /* HAVE_SYMLINK */
388 /* If the entry is a file, check for textual modifications */
389 if ((entry->kind == svn_node_file)
390 #ifdef HAVE_SYMLINK
391 && (wc_special == path_special)
392 #endif /* HAVE_SYMLINK */
394 SVN_ERR(svn_wc_text_modified_p(&text_modified_p, path, FALSE,
395 adm_access, pool));
397 if (text_modified_p)
398 final_text_status = svn_wc_status_modified;
400 if (prop_modified_p)
401 final_prop_status = svn_wc_status_modified;
403 if (entry->prejfile || entry->conflict_old ||
404 entry->conflict_new || entry->conflict_wrk)
406 svn_boolean_t text_conflict_p, prop_conflict_p;
407 const char *parent_dir;
409 if (entry->kind == svn_node_dir)
410 parent_dir = path;
411 else /* non-directory, that's all we need to know */
412 parent_dir = svn_path_dirname(path, pool);
414 SVN_ERR(svn_wc_conflicted_p(&text_conflict_p, &prop_conflict_p,
415 parent_dir, entry, pool));
417 if (text_conflict_p)
418 final_text_status = svn_wc_status_conflicted;
419 if (prop_conflict_p)
420 final_prop_status = svn_wc_status_conflicted;
423 /* 2. Possibly overwrite the text_status variable with "scheduled"
424 states from the entry (A, D, R). As a group, these states are
425 of medium precedence. They also override any C or M that may
426 be in the prop_status field at this point, although they do not
427 override a C text status.*/
429 if (entry->schedule == svn_wc_schedule_add
430 && final_text_status != svn_wc_status_conflicted)
432 final_text_status = svn_wc_status_added;
433 final_prop_status = svn_wc_status_none;
436 else if (entry->schedule == svn_wc_schedule_replace
437 && final_text_status != svn_wc_status_conflicted)
439 final_text_status = svn_wc_status_replaced;
440 final_prop_status = svn_wc_status_none;
443 else if (entry->schedule == svn_wc_schedule_delete
444 && final_text_status != svn_wc_status_conflicted)
446 final_text_status = svn_wc_status_deleted;
447 final_prop_status = svn_wc_status_none;
451 /* 3. Highest precedence:
453 a. check to see if file or dir is just missing, or
454 incomplete. This overrides every possible state
455 *except* deletion. (If something is deleted or
456 scheduled for it, we don't care if the working file
457 exists.)
459 b. check to see if the file or dir is present in the
460 file system as the same kind it was versioned as.
462 4. Check for locked directory (only for directories). */
464 if (entry->incomplete
465 && (final_text_status != svn_wc_status_deleted)
466 && (final_text_status != svn_wc_status_added))
468 final_text_status = svn_wc_status_incomplete;
470 else if (path_kind == svn_node_none)
472 if (final_text_status != svn_wc_status_deleted)
473 final_text_status = svn_wc_status_missing;
475 else if (path_kind != entry->kind)
476 final_text_status = svn_wc_status_obstructed;
477 #ifdef HAVE_SYMLINK
478 else if (((! wc_special) && (path_special))
479 || (wc_special && (! path_special))
481 final_text_status = svn_wc_status_obstructed;
482 #endif /* HAVE_SYMLINK */
484 if (path_kind == svn_node_dir && entry->kind == svn_node_dir)
485 SVN_ERR(svn_wc_locked(&locked_p, path, pool));
488 /* 5. Easy out: unless we're fetching -every- entry, don't bother
489 to allocate a struct for an uninteresting entry. */
491 if (! get_all)
492 if (((final_text_status == svn_wc_status_none)
493 || (final_text_status == svn_wc_status_normal))
494 && ((final_prop_status == svn_wc_status_none)
495 || (final_prop_status == svn_wc_status_normal))
496 && (! locked_p) && (! switched_p) && (! entry->lock_token)
497 && (! repos_lock) && (! entry->changelist))
499 *status = NULL;
500 return SVN_NO_ERROR;
504 /* 6. Build and return a status structure. */
506 stat = apr_pcalloc(pool, sizeof(**status));
507 stat->entry = svn_wc_entry_dup(entry, pool);
508 stat->text_status = final_text_status;
509 stat->prop_status = final_prop_status;
510 stat->repos_text_status = svn_wc_status_none; /* default */
511 stat->repos_prop_status = svn_wc_status_none; /* default */
512 stat->locked = locked_p;
513 stat->switched = switched_p;
514 stat->copied = entry->copied;
515 stat->repos_lock = repos_lock;
516 stat->url = (entry->url ? entry->url : NULL);
517 stat->ood_last_cmt_rev = SVN_INVALID_REVNUM;
518 stat->ood_last_cmt_date = 0;
519 stat->ood_kind = svn_node_none;
520 stat->ood_last_cmt_author = NULL;
522 *status = stat;
524 return SVN_NO_ERROR;
530 /* Given an ENTRY object representing PATH, build a status structure
531 and pass it off to the STATUS_FUNC/STATUS_BATON. All other
532 arguments are the same as those passed to assemble_status(). */
533 static svn_error_t *
534 send_status_structure(const char *path,
535 svn_wc_adm_access_t *adm_access,
536 const svn_wc_entry_t *entry,
537 const svn_wc_entry_t *parent_entry,
538 svn_node_kind_t path_kind,
539 svn_boolean_t path_special,
540 svn_boolean_t get_all,
541 svn_boolean_t is_ignored,
542 apr_hash_t *repos_locks,
543 const char *repos_root,
544 svn_wc_status_func2_t status_func,
545 void *status_baton,
546 apr_pool_t *pool)
548 svn_wc_status2_t *statstruct;
550 SVN_ERR(assemble_status(&statstruct, path, adm_access, entry, parent_entry,
551 path_kind, path_special, get_all, is_ignored,
552 repos_locks, repos_root, pool));
553 if (statstruct && (status_func))
554 (*status_func)(status_baton, path, statstruct);
556 return SVN_NO_ERROR;
560 /* Store in PATTERNS a list of all svn:ignore properties from
561 the working copy directory, including the default ignores
562 passed in as IGNORES.
564 Upon return, *PATTERNS will contain zero or more (const char *)
565 patterns from the value of the SVN_PROP_IGNORE property set on
566 the working directory path.
568 IGNORES is a list of patterns to include; typically this will
569 be the default ignores as, for example, specified in a config file.
571 ADM_ACCESS is an access baton for the working copy path.
573 Allocate everything in POOL.
575 None of the arguments may be NULL.
577 static svn_error_t *
578 collect_ignore_patterns(apr_array_header_t **patterns,
579 apr_array_header_t *ignores,
580 svn_wc_adm_access_t *adm_access,
581 apr_pool_t *pool)
583 int i;
584 const svn_string_t *value;
586 *patterns = apr_array_make(pool, 1, sizeof(const char *));
588 /* Copy default ignores into the local PATTERNS array. */
589 for (i = 0; i < ignores->nelts; i++)
591 const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
592 APR_ARRAY_PUSH(*patterns, const char *) = ignore;
595 /* Then add any svn:ignore globs to the PATTERNS array. */
596 SVN_ERR(svn_wc_prop_get(&value, SVN_PROP_IGNORE,
597 svn_wc_adm_access_path(adm_access), adm_access,
598 pool));
599 if (value != NULL)
600 svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, pool);
602 return SVN_NO_ERROR;
606 /* Compare PATH with items in the EXTERNALS hash to see if PATH is the
607 drop location for, or an intermediate directory of the drop
608 location for, an externals definition. Use POOL for
609 scratchwork. */
610 static svn_boolean_t
611 is_external_path(apr_hash_t *externals,
612 const char *path,
613 apr_pool_t *pool)
615 apr_hash_index_t *hi;
617 /* First try: does the path exist as a key in the hash? */
618 if (apr_hash_get(externals, path, APR_HASH_KEY_STRING))
619 return TRUE;
621 /* Failing that, we need to check if any external is a child of
622 PATH. */
623 for (hi = apr_hash_first(pool, externals); hi; hi = apr_hash_next(hi))
625 const void *key;
626 apr_hash_this(hi, &key, NULL, NULL);
627 if (svn_path_is_child(path, key, pool))
628 return TRUE;
631 return FALSE;
635 /* Assuming that NAME is unversioned, send a status structure
636 for it through STATUS_FUNC/STATUS_BATON unless this path is being
637 ignored. This function should never be called on a versioned entry.
639 NAME is the basename of the unversioned file whose status is being
640 requested. PATH_KIND is the node kind of NAME as determined by the
641 caller. PATH_SPECIAL is the special status of the path, also determined
642 by the caller. ADM_ACCESS is an access baton for the working copy path.
643 PATTERNS points to a list of filename patterns which are marked as
644 ignored. None of these parameter may be NULL. EXTERNALS is a hash
645 of known externals definitions for this status run.
647 If NO_IGNORE is non-zero, the item will be added regardless of
648 whether it is ignored; otherwise we will only add the item if it
649 does not match any of the patterns in PATTERNS.
651 Allocate everything in POOL.
653 static svn_error_t *
654 send_unversioned_item(const char *name,
655 svn_node_kind_t path_kind, svn_boolean_t path_special,
656 svn_wc_adm_access_t *adm_access,
657 apr_array_header_t *patterns,
658 apr_hash_t *externals,
659 svn_boolean_t no_ignore,
660 apr_hash_t *repos_locks,
661 const char *repos_root,
662 svn_wc_status_func2_t status_func,
663 void *status_baton,
664 apr_pool_t *pool)
666 int ignore_me = svn_wc_match_ignore_list(name, patterns, pool);
667 const char *path = svn_path_join(svn_wc_adm_access_path(adm_access),
668 name, pool);
669 int is_external = is_external_path(externals, path, pool);
670 svn_wc_status2_t *status;
672 SVN_ERR(assemble_status(&status, path, adm_access, NULL, NULL,
673 path_kind, path_special, FALSE, ignore_me,
674 repos_locks, repos_root, pool));
676 if (is_external)
677 status->text_status = svn_wc_status_external;
679 /* If we aren't ignoring it, or if it's an externals path, or it has a lock
680 in the repository, pass this entry to the status func. */
681 if (no_ignore || (! ignore_me) || is_external || status->repos_lock)
682 (status_func)(status_baton, path, status);
684 return SVN_NO_ERROR;
688 /* Prototype for untangling a tango-ing two-some. */
689 static svn_error_t *get_dir_status(struct edit_baton *eb,
690 const svn_wc_entry_t *parent_entry,
691 svn_wc_adm_access_t *adm_access,
692 const char *entry,
693 apr_array_header_t *ignores,
694 svn_depth_t depth,
695 svn_boolean_t get_all,
696 svn_boolean_t no_ignore,
697 svn_boolean_t skip_this_dir,
698 svn_wc_status_func2_t status_func,
699 void *status_baton,
700 svn_cancel_func_t cancel_func,
701 void *cancel_baton,
702 apr_pool_t *pool);
704 /* Handle NAME (whose entry is ENTRY) as a directory entry of the
705 directory represented by ADM_ACCESS (and whose entry is
706 DIR_ENTRY). All other arguments are the same as those passed to
707 get_dir_status(), the function for which this one is a helper. */
708 static svn_error_t *
709 handle_dir_entry(struct edit_baton *eb,
710 svn_wc_adm_access_t *adm_access,
711 const char *name,
712 const svn_wc_entry_t *dir_entry,
713 const svn_wc_entry_t *entry,
714 svn_node_kind_t kind,
715 svn_boolean_t special,
716 apr_array_header_t *ignores,
717 svn_depth_t depth,
718 svn_boolean_t get_all,
719 svn_boolean_t no_ignore,
720 svn_wc_status_func2_t status_func,
721 void *status_baton,
722 svn_cancel_func_t cancel_func,
723 void *cancel_baton,
724 apr_pool_t *pool)
726 const char *dirname = svn_wc_adm_access_path(adm_access);
727 const char *path = svn_path_join(dirname, name, pool);
729 if (kind == svn_node_dir)
731 /* Directory entries are incomplete. We must get their full
732 entry from their own THIS_DIR entry. svn_wc_entry does this
733 for us if it can.
735 Of course, if there has been a kind-changing replacement (for
736 example, there is an entry for a file 'foo', but 'foo' exists
737 as a *directory* on disk), we don't want to reach down into
738 that subdir to try to flesh out a "complete entry". */
739 const svn_wc_entry_t *full_entry = entry;
741 if (entry->kind == kind)
742 SVN_ERR(svn_wc__entry_versioned(&full_entry, path, adm_access, FALSE,
743 pool));
745 /* Descend only if the subdirectory is a working copy directory
746 (and DEPTH permits it, of course) */
747 if (full_entry != entry
748 && (depth == svn_depth_unknown
749 || depth == svn_depth_immediates
750 || depth == svn_depth_infinity))
752 svn_wc_adm_access_t *dir_access;
753 SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, path, pool));
754 SVN_ERR(get_dir_status(eb, dir_entry, dir_access, NULL, ignores,
755 depth, get_all, no_ignore, FALSE,
756 status_func, status_baton, cancel_func,
757 cancel_baton, pool));
759 else
761 SVN_ERR(send_status_structure(path, adm_access, full_entry,
762 dir_entry, kind, special, get_all,
763 FALSE, eb->repos_locks,
764 eb->repos_root,
765 status_func, status_baton, pool));
768 else
770 /* File entries are ... just fine! */
771 SVN_ERR(send_status_structure(path, adm_access, entry, dir_entry,
772 kind, special, get_all, FALSE,
773 eb->repos_locks, eb->repos_root,
774 status_func, status_baton, pool));
776 return SVN_NO_ERROR;
780 /* Send svn_wc_status2_t * structures for the directory ADM_ACCESS and
781 for all its entries through STATUS_FUNC/STATUS_BATON, or, if ENTRY
782 is non-NULL, only for that directory entry.
784 PARENT_ENTRY is the entry for the parent of the directory or NULL
785 if that directory is a working copy root.
787 If SKIP_THIS_DIR is TRUE (and ENTRY is NULL), the directory's own
788 status will not be reported. However, upon recursing, all subdirs
789 *will* be reported, regardless of this parameter's value.
791 Other arguments are the same as those passed to
792 svn_wc_get_status_editor3(). */
793 static svn_error_t *
794 get_dir_status(struct edit_baton *eb,
795 const svn_wc_entry_t *parent_entry,
796 svn_wc_adm_access_t *adm_access,
797 const char *entry,
798 apr_array_header_t *ignore_patterns,
799 svn_depth_t depth,
800 svn_boolean_t get_all,
801 svn_boolean_t no_ignore,
802 svn_boolean_t skip_this_dir,
803 svn_wc_status_func2_t status_func,
804 void *status_baton,
805 svn_cancel_func_t cancel_func,
806 void *cancel_baton,
807 apr_pool_t *pool)
809 apr_hash_t *entries;
810 apr_hash_index_t *hi;
811 const svn_wc_entry_t *dir_entry;
812 const char *path = svn_wc_adm_access_path(adm_access);
813 apr_hash_t *dirents;
814 apr_array_header_t *patterns = NULL;
815 apr_pool_t *iterpool, *subpool = svn_pool_create(pool);
817 /* See if someone wants to cancel this operation. */
818 if (cancel_func)
819 SVN_ERR(cancel_func(cancel_baton));
821 if (depth == svn_depth_unknown)
822 depth = svn_depth_infinity;
824 /* Load entries file for the directory into the requested pool. */
825 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, subpool));
827 /* Read PATH's dirents. */
828 SVN_ERR(svn_io_get_dirents2(&dirents, path, subpool));
830 /* Get this directory's entry. */
831 SVN_ERR(svn_wc_entry(&dir_entry, path, adm_access, FALSE, subpool));
833 /* If "this dir" has "svn:externals" property set on it, store the
834 name and value in traversal_info, along with this directory's depth.
835 (Also, we want to track the externals internally so we can report
836 status more accurately.) */
838 const svn_string_t *prop_val;
839 SVN_ERR(svn_wc_prop_get(&prop_val, SVN_PROP_EXTERNALS, path,
840 adm_access, subpool));
841 if (prop_val)
843 apr_array_header_t *ext_items;
844 int i;
846 if (eb->traversal_info)
848 apr_pool_t *dup_pool = eb->traversal_info->pool;
849 const char *dup_path = apr_pstrdup(dup_pool, path);
850 const char *dup_val = apr_pstrmemdup(dup_pool, prop_val->data,
851 prop_val->len);
853 /* First things first -- we put the externals information
854 into the "global" traversal info structure. */
855 apr_hash_set(eb->traversal_info->externals_old,
856 dup_path, APR_HASH_KEY_STRING, dup_val);
857 apr_hash_set(eb->traversal_info->externals_new,
858 dup_path, APR_HASH_KEY_STRING, dup_val);
859 apr_hash_set(eb->traversal_info->depths,
860 dup_path, APR_HASH_KEY_STRING,
861 svn_depth_to_word(dir_entry->depth));
864 /* Now, parse the thing, and copy the parsed results into
865 our "global" externals hash. */
866 SVN_ERR(svn_wc_parse_externals_description3(&ext_items, path,
867 prop_val->data, FALSE,
868 pool));
869 for (i = 0; ext_items && i < ext_items->nelts; i++)
871 svn_wc_external_item2_t *item;
873 item = APR_ARRAY_IDX(ext_items, i, svn_wc_external_item2_t *);
874 apr_hash_set(eb->externals, svn_path_join(path,
875 item->target_dir,
876 pool),
877 APR_HASH_KEY_STRING, item);
882 /* Early out -- our caller only cares about a single ENTRY in this
883 directory. */
884 if (entry)
886 const svn_wc_entry_t *entry_entry;
887 svn_io_dirent_t* dirent_p = apr_hash_get(dirents, entry,
888 APR_HASH_KEY_STRING);
889 entry_entry = apr_hash_get(entries, entry, APR_HASH_KEY_STRING);
891 /* If ENTRY is versioned, send its versioned status. */
892 if (entry_entry)
894 SVN_ERR(handle_dir_entry(eb, adm_access, entry, dir_entry,
895 entry_entry,
896 dirent_p ? dirent_p->kind : svn_node_none,
897 dirent_p ? dirent_p->special : FALSE,
898 ignore_patterns, depth, get_all,
899 no_ignore, status_func, status_baton,
900 cancel_func, cancel_baton, subpool));
902 /* Otherwise, if it exists, send its unversioned status. */
903 else if (dirent_p)
905 if (ignore_patterns && ! patterns)
906 SVN_ERR(collect_ignore_patterns(&patterns, ignore_patterns,
907 adm_access, subpool));
908 SVN_ERR(send_unversioned_item(entry, dirent_p->kind,
909 dirent_p->special, adm_access,
910 patterns, eb->externals, no_ignore,
911 eb->repos_locks, eb->repos_root,
912 status_func, status_baton, subpool));
915 /* Regardless, we're done here. Let's go home. */
916 return SVN_NO_ERROR;
919 /** If we get here, ENTRY is NULL and we are handling all the
920 directory entries (depending on specified depth). */
922 /* Handle "this-dir" first. */
923 if (! skip_this_dir)
924 SVN_ERR(send_status_structure(path, adm_access, dir_entry,
925 parent_entry, svn_node_dir, FALSE,
926 get_all, FALSE, eb->repos_locks,
927 eb->repos_root, status_func, status_baton,
928 subpool));
930 /* If the requested depth is empty, we only need status on this-dir. */
931 if (depth == svn_depth_empty)
932 return SVN_NO_ERROR;
934 /* Make our iteration pool. */
935 iterpool = svn_pool_create(subpool);
937 /* Add empty status structures for each of the unversioned things. */
938 for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
940 const void *key;
941 apr_ssize_t klen;
942 void *val;
943 svn_io_dirent_t *dirent_p;
945 apr_hash_this(hi, &key, &klen, &val);
947 /* Skip versioned things, and skip the administrative
948 directory. */
949 if (apr_hash_get(entries, key, klen)
950 || svn_wc_is_adm_dir(key, subpool))
951 continue;
953 svn_pool_clear(iterpool);
955 if (ignore_patterns && ! patterns)
956 SVN_ERR(collect_ignore_patterns(&patterns, ignore_patterns,
957 adm_access, subpool));
959 /* Make an unversioned status item for KEY, and put it into our
960 return hash. */
961 dirent_p = val;
962 SVN_ERR(send_unversioned_item(key, dirent_p->kind, dirent_p->special,
963 adm_access,
964 patterns, eb->externals, no_ignore,
965 eb->repos_locks, eb->repos_root,
966 status_func, status_baton, iterpool));
969 /* Loop over entries hash */
970 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
972 const void *key;
973 void *val;
974 svn_io_dirent_t *dirent_p;
976 /* Get the next entry */
977 apr_hash_this(hi, &key, NULL, &val);
979 dirent_p = apr_hash_get(dirents, key, APR_HASH_KEY_STRING);
981 /* ### todo: What if the subdir is from another repository? */
983 /* Skip "this-dir". */
984 if (strcmp(key, SVN_WC_ENTRY_THIS_DIR) == 0)
985 continue;
987 /* Skip directories if user is only interested in files */
988 if (depth == svn_depth_files
989 && dirent_p && dirent_p->kind == svn_node_dir)
990 continue;
992 /* Clear the iteration subpool. */
993 svn_pool_clear(iterpool);
995 /* Handle this directory entry (possibly recursing). */
996 SVN_ERR(handle_dir_entry(eb, adm_access, key, dir_entry, val,
997 dirent_p ? dirent_p->kind : svn_node_none,
998 dirent_p ? dirent_p->special : FALSE,
999 ignore_patterns,
1000 depth == svn_depth_infinity ? depth
1001 : svn_depth_empty,
1002 get_all, no_ignore,
1003 status_func, status_baton, cancel_func,
1004 cancel_baton, iterpool));
1007 /* Destroy our subpools. */
1008 svn_pool_destroy(subpool);
1010 return SVN_NO_ERROR;
1015 /*** Helpers ***/
1017 /* A faux status callback function for stashing STATUS item in an hash
1018 (which is the BATON), keyed on PATH. This implements the
1019 svn_wc_status_func2_t interface. */
1020 static void
1021 hash_stash(void *baton,
1022 const char *path,
1023 svn_wc_status2_t *status)
1025 apr_hash_t *stat_hash = baton;
1026 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1027 assert(! apr_hash_get(stat_hash, path, APR_HASH_KEY_STRING));
1028 apr_hash_set(stat_hash, apr_pstrdup(hash_pool, path),
1029 APR_HASH_KEY_STRING, svn_wc_dup_status2(status, hash_pool));
1033 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1034 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1035 yet exist, and the REPOS_TEXT_STATUS indicates that this is an
1036 addition, create a new status struct using the hash's pool.
1038 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1039 of date (ood) information we want to set in BATON. This is necessary
1040 because this function tweaks the status of out-of-date directories
1041 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1042 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1043 contains the ood info we want to bubble up to ancestor directories so these
1044 accurately reflect the fact they have an ood descendant.
1046 Merge REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the status structure's
1047 "network" fields.
1049 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1050 is ignored:
1052 If REPOS_TEXT_STATUS is svn_wc_status_deleted then DELETED_REV is
1053 optionally the revision path was deleted, in all other cases it must
1054 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1055 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1056 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1057 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is
1058 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1059 ood_last_cmt_rev value - see comment below.
1061 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1062 static svn_error_t *
1063 tweak_statushash(void *baton,
1064 void *this_dir_baton,
1065 svn_boolean_t is_dir_baton,
1066 svn_wc_adm_access_t *adm_access,
1067 const char *path,
1068 svn_boolean_t is_dir,
1069 enum svn_wc_status_kind repos_text_status,
1070 enum svn_wc_status_kind repos_prop_status,
1071 svn_revnum_t deleted_rev,
1072 svn_lock_t *repos_lock)
1074 svn_wc_status2_t *statstruct;
1075 apr_pool_t *pool;
1076 apr_hash_t *statushash;
1078 if (is_dir_baton)
1079 statushash = ((struct dir_baton *) baton)->statii;
1080 else
1081 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1082 pool = apr_hash_pool_get(statushash);
1084 /* Is PATH already a hash-key? */
1085 statstruct = apr_hash_get(statushash, path, APR_HASH_KEY_STRING);
1087 /* If not, make it so. */
1088 if (! statstruct)
1090 /* If this item isn't being added, then we're most likely
1091 dealing with a non-recursive (or at least partially
1092 non-recursive) working copy. Due to bugs in how the client
1093 reports the state of non-recursive working copies, the
1094 repository can send back responses about paths that don't
1095 even exist locally. Our best course here is just to ignore
1096 those responses. After all, if the client had reported
1097 correctly in the first, that path would either be mentioned
1098 as an 'add' or not mentioned at all, depending on how we
1099 eventually fix the bugs in non-recursivity. See issue
1100 #2122 for details. */
1101 if (repos_text_status != svn_wc_status_added)
1102 return SVN_NO_ERROR;
1104 /* Use the public API to get a statstruct, and put it into the hash. */
1105 SVN_ERR(svn_wc_status2(&statstruct, path, adm_access, pool));
1106 statstruct->repos_lock = repos_lock;
1107 apr_hash_set(statushash, apr_pstrdup(pool, path),
1108 APR_HASH_KEY_STRING, statstruct);
1111 /* Merge a repos "delete" + "add" into a single "replace". */
1112 if ((repos_text_status == svn_wc_status_added)
1113 && (statstruct->repos_text_status == svn_wc_status_deleted))
1114 repos_text_status = svn_wc_status_replaced;
1116 /* Tweak the structure's repos fields. */
1117 if (repos_text_status)
1118 statstruct->repos_text_status = repos_text_status;
1119 if (repos_prop_status)
1120 statstruct->repos_prop_status = repos_prop_status;
1122 /* Copy out-of-date info. */
1123 if (is_dir_baton)
1125 struct dir_baton *b = this_dir_baton;
1127 if (b->url)
1129 if (statstruct->repos_text_status == svn_wc_status_deleted)
1131 /* When deleting PATH, BATON is for PATH's parent,
1132 so we must construct PATH's real statstruct->url. */
1133 statstruct->url =
1134 svn_path_url_add_component(b->url,
1135 svn_path_basename(path, pool),
1136 pool);
1138 else
1139 statstruct->url = apr_pstrdup(pool, b->url);
1142 /* The last committed date, and author for deleted items
1143 isn't available. */
1144 if (statstruct->repos_text_status == svn_wc_status_deleted)
1146 statstruct->ood_kind = is_dir ? svn_node_dir : svn_node_file;
1148 /* Pre 1.5 servers don't provide the revision a path was deleted.
1149 So we punt and use the last committed revision of the path's
1150 parent, which has some chance of being correct. At worse it
1151 is a higher revision than the path was deleted, but this is
1152 better than nothing... */
1153 if (deleted_rev == SVN_INVALID_REVNUM)
1154 statstruct->ood_last_cmt_rev =
1155 ((struct dir_baton *) baton)->ood_last_cmt_rev;
1156 else
1157 statstruct->ood_last_cmt_rev = deleted_rev;
1159 else
1161 statstruct->ood_kind = b->ood_kind;
1162 statstruct->ood_last_cmt_rev = b->ood_last_cmt_rev;
1163 statstruct->ood_last_cmt_date = b->ood_last_cmt_date;
1164 if (b->ood_last_cmt_author)
1165 statstruct->ood_last_cmt_author =
1166 apr_pstrdup(pool, b->ood_last_cmt_author);
1170 else
1172 struct file_baton *b = baton;
1173 if (b->url)
1174 statstruct->url = apr_pstrdup(pool, b->url);
1175 statstruct->ood_last_cmt_rev = b->ood_last_cmt_rev;
1176 statstruct->ood_last_cmt_date = b->ood_last_cmt_date;
1177 statstruct->ood_kind = b->ood_kind;
1178 if (b->ood_last_cmt_author)
1179 statstruct->ood_last_cmt_author =
1180 apr_pstrdup(pool, b->ood_last_cmt_author);
1182 return SVN_NO_ERROR;
1185 /* Returns the URL for DB, or NULL: */
1186 static const char *
1187 find_dir_url(const struct dir_baton *db, apr_pool_t *pool)
1189 /* If we have no name, we're the root, return the anchor URL. */
1190 if (! db->name)
1191 return db->edit_baton->anchor_status->entry->url;
1192 else
1194 const char *url;
1195 struct dir_baton *pb = db->parent_baton;
1196 svn_wc_status2_t *status = apr_hash_get(pb->statii, db->name,
1197 APR_HASH_KEY_STRING);
1198 /* Note that status->entry->url is NULL in the case of a missing
1199 * directory, which means we need to recurse up another level to
1200 * get a useful URL. */
1201 if (status && status->entry && status->entry->url)
1202 return status->entry->url;
1204 url = find_dir_url(pb, pool);
1205 if (url)
1206 return svn_path_url_add_component(url, db->name, pool);
1207 else
1208 return NULL;
1214 /* Create a new dir_baton for subdir PATH. */
1215 static svn_error_t *
1216 make_dir_baton(void **dir_baton,
1217 const char *path,
1218 struct edit_baton *edit_baton,
1219 struct dir_baton *parent_baton,
1220 apr_pool_t *pool)
1222 struct dir_baton *pb = parent_baton;
1223 struct edit_baton *eb = edit_baton;
1224 struct dir_baton *d = apr_pcalloc(pool, sizeof(*d));
1225 const char *full_path;
1226 svn_wc_status2_t *status_in_parent;
1228 /* Don't do this. Just do NOT do this to me. */
1229 if (pb && (! path))
1230 abort();
1232 /* Construct the full path of this directory. */
1233 if (pb)
1234 full_path = svn_path_join(eb->anchor, path, pool);
1235 else
1236 full_path = apr_pstrdup(pool, eb->anchor);
1238 /* Finish populating the baton members. */
1239 d->path = full_path;
1240 d->name = path ? (svn_path_basename(path, pool)) : NULL;
1241 d->edit_baton = edit_baton;
1242 d->parent_baton = parent_baton;
1243 d->pool = pool;
1244 d->statii = apr_hash_make(pool);
1245 d->url = apr_pstrdup(pool, find_dir_url(d, pool));
1246 d->ood_last_cmt_rev = SVN_INVALID_REVNUM;
1247 d->ood_last_cmt_date = 0;
1248 d->ood_kind = svn_node_dir;
1249 d->ood_last_cmt_author = NULL;
1251 if (pb)
1253 if (pb->excluded)
1254 d->excluded = TRUE;
1255 else if (pb->depth == svn_depth_immediates)
1256 d->depth = svn_depth_empty;
1257 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1258 d->excluded = TRUE;
1259 else if (pb->depth == svn_depth_unknown)
1260 /* This is only tentative, it can be overridden from d's entry
1261 later. */
1262 d->depth = svn_depth_unknown;
1263 else
1264 d->depth = svn_depth_infinity;
1266 else
1268 d->depth = eb->default_depth;
1271 /* Get the status for this path's children. Of course, we only want
1272 to do this if the path is versioned as a directory. */
1273 if (pb)
1274 status_in_parent = apr_hash_get(pb->statii, d->path, APR_HASH_KEY_STRING);
1275 else
1276 status_in_parent = eb->anchor_status;
1278 /* Order is important here. We can't depend on status_in_parent->entry
1279 being non-NULL until after we've checked all the conditions that
1280 might indicate that the parent is unversioned ("unversioned" for
1281 our purposes includes being an external or ignored item). */
1282 if (status_in_parent
1283 && (status_in_parent->text_status != svn_wc_status_unversioned)
1284 && (status_in_parent->text_status != svn_wc_status_missing)
1285 && (status_in_parent->text_status != svn_wc_status_obstructed)
1286 && (status_in_parent->text_status != svn_wc_status_external)
1287 && (status_in_parent->text_status != svn_wc_status_ignored)
1288 && (status_in_parent->entry->kind == svn_node_dir)
1289 && (! d->excluded)
1290 && (d->depth == svn_depth_unknown
1291 || d->depth == svn_depth_infinity
1292 || d->depth == svn_depth_files
1293 || d->depth == svn_depth_immediates)
1296 svn_wc_adm_access_t *dir_access;
1297 svn_wc_status2_t *this_dir_status;
1298 apr_array_header_t *ignores = eb->ignores;
1299 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1300 d->path, pool));
1301 SVN_ERR(get_dir_status(eb, status_in_parent->entry, dir_access, NULL,
1302 ignores, d->depth == svn_depth_files ?
1303 svn_depth_files : svn_depth_immediates,
1304 TRUE, TRUE, TRUE, hash_stash, d->statii, NULL,
1305 NULL, pool));
1307 /* If we found a depth here, it should govern. */
1308 this_dir_status = apr_hash_get(d->statii, d->path, APR_HASH_KEY_STRING);
1309 if (this_dir_status && this_dir_status->entry
1310 && (d->depth == svn_depth_unknown
1311 || d->depth > status_in_parent->entry->depth))
1313 d->depth = this_dir_status->entry->depth;
1317 *dir_baton = d;
1318 return SVN_NO_ERROR;
1322 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1323 NAME is just one component, not a path. */
1324 static struct file_baton *
1325 make_file_baton(struct dir_baton *parent_dir_baton,
1326 const char *path,
1327 apr_pool_t *pool)
1329 struct dir_baton *pb = parent_dir_baton;
1330 struct edit_baton *eb = pb->edit_baton;
1331 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1332 const char *full_path;
1334 /* Construct the full path of this file. */
1335 full_path = svn_path_join(eb->anchor, path, pool);
1337 /* Finish populating the baton members. */
1338 f->path = full_path;
1339 f->name = svn_path_basename(path, pool);
1340 f->pool = pool;
1341 f->dir_baton = pb;
1342 f->edit_baton = eb;
1343 f->url = svn_path_url_add_component(find_dir_url(pb, pool),
1344 svn_path_basename(full_path, pool),
1345 pool);
1346 f->ood_last_cmt_rev = SVN_INVALID_REVNUM;
1347 f->ood_last_cmt_date = 0;
1348 f->ood_kind = svn_node_file;
1349 f->ood_last_cmt_author = NULL;
1350 return f;
1353 /* Return a boolean answer to the question "Is STATUS something that
1354 should be reported?". EB is the edit baton. */
1355 static svn_boolean_t
1356 is_sendable_status(svn_wc_status2_t *status,
1357 struct edit_baton *eb)
1359 /* If the repository status was touched at all, it's interesting. */
1360 if (status->repos_text_status != svn_wc_status_none)
1361 return TRUE;
1362 if (status->repos_prop_status != svn_wc_status_none)
1363 return TRUE;
1365 /* If there is a lock in the repository, send it. */
1366 if (status->repos_lock)
1367 return TRUE;
1369 /* If the item is ignored, and we don't want ignores, skip it. */
1370 if ((status->text_status == svn_wc_status_ignored) && (! eb->no_ignore))
1371 return FALSE;
1373 /* If we want everything, we obviously want this single-item subset
1374 of everything. */
1375 if (eb->get_all)
1376 return TRUE;
1378 /* If the item is unversioned, display it. */
1379 if (status->text_status == svn_wc_status_unversioned)
1380 return TRUE;
1382 /* If the text or property states are interesting, send it. */
1383 if ((status->text_status != svn_wc_status_none)
1384 && (status->text_status != svn_wc_status_normal))
1385 return TRUE;
1386 if ((status->prop_status != svn_wc_status_none)
1387 && (status->prop_status != svn_wc_status_normal))
1388 return TRUE;
1390 /* If it's locked or switched, send it. */
1391 if (status->locked)
1392 return TRUE;
1393 if (status->switched)
1394 return TRUE;
1396 /* If there is a lock token, send it. */
1397 if (status->entry && status->entry->lock_token)
1398 return TRUE;
1400 /* If the entry is associated with a changelist, send it. */
1401 if (status->entry && status->entry->changelist)
1402 return TRUE;
1404 /* Otherwise, don't send it. */
1405 return FALSE;
1409 /* Baton for mark_status. */
1410 struct status_baton
1412 svn_wc_status_func2_t real_status_func; /* real status function */
1413 void *real_status_baton; /* real status baton */
1416 /* A status callback function which wraps the *real* status
1417 function/baton. It simply sets the "repos_text_status" field of the
1418 STATUS to svn_wc_status_deleted and passes it off to the real
1419 status func/baton. */
1420 static void
1421 mark_deleted(void *baton,
1422 const char *path,
1423 svn_wc_status2_t *status)
1425 struct status_baton *sb = baton;
1426 status->repos_text_status = svn_wc_status_deleted;
1427 sb->real_status_func(sb->real_status_baton, path, status);
1431 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1432 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1433 directory itself. Descend into subdirectories according to DEPTH.
1434 Also, if DIR_WAS_DELETED is set, each status that is reported
1435 through this function will have its repos_text_status field showing
1436 a deletion. Use POOL for all allocations. */
1437 static svn_error_t *
1438 handle_statii(struct edit_baton *eb,
1439 svn_wc_entry_t *dir_entry,
1440 const char *dir_path,
1441 apr_hash_t *statii,
1442 svn_boolean_t dir_was_deleted,
1443 svn_depth_t depth,
1444 apr_pool_t *pool)
1446 apr_array_header_t *ignores = eb->ignores;
1447 apr_hash_index_t *hi;
1448 apr_pool_t *subpool = svn_pool_create(pool);
1449 svn_wc_status_func2_t status_func = eb->status_func;
1450 void *status_baton = eb->status_baton;
1451 struct status_baton sb;
1453 if (dir_was_deleted)
1455 sb.real_status_func = eb->status_func;
1456 sb.real_status_baton = eb->status_baton;
1457 status_func = mark_deleted;
1458 status_baton = &sb;
1461 /* Loop over all the statuses still in our hash, handling each one. */
1462 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1464 const void *key;
1465 void *val;
1466 svn_wc_status2_t *status;
1468 apr_hash_this(hi, &key, NULL, &val);
1469 status = val;
1471 /* Clear the subpool. */
1472 svn_pool_clear(subpool);
1474 /* Now, handle the status. We don't recurse for svn_depth_immediates
1475 because we already have the subdirectories' statii. */
1476 if (status->text_status != svn_wc_status_obstructed
1477 && status->text_status != svn_wc_status_missing
1478 && status->entry && status->entry->kind == svn_node_dir
1479 && (depth == svn_depth_unknown
1480 || depth == svn_depth_infinity))
1482 svn_wc_adm_access_t *dir_access;
1484 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1485 key, subpool));
1487 SVN_ERR(get_dir_status(eb, dir_entry, dir_access, NULL,
1488 ignores, depth, eb->get_all,
1489 eb->no_ignore, TRUE, status_func,
1490 status_baton, eb->cancel_func,
1491 eb->cancel_baton, subpool));
1493 if (dir_was_deleted)
1494 status->repos_text_status = svn_wc_status_deleted;
1495 if (is_sendable_status(status, eb))
1496 (eb->status_func)(eb->status_baton, key, status);
1499 /* Destroy the subpool. */
1500 svn_pool_destroy(subpool);
1502 return SVN_NO_ERROR;
1506 /*----------------------------------------------------------------------*/
1508 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1510 static svn_error_t *
1511 set_target_revision(void *edit_baton,
1512 svn_revnum_t target_revision,
1513 apr_pool_t *pool)
1515 struct edit_baton *eb = edit_baton;
1516 *(eb->target_revision) = target_revision;
1517 return SVN_NO_ERROR;
1521 static svn_error_t *
1522 open_root(void *edit_baton,
1523 svn_revnum_t base_revision,
1524 apr_pool_t *pool,
1525 void **dir_baton)
1527 struct edit_baton *eb = edit_baton;
1528 eb->root_opened = TRUE;
1529 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
1533 static svn_error_t *
1534 delete_entry(const char *path,
1535 svn_revnum_t revision,
1536 void *parent_baton,
1537 apr_pool_t *pool)
1539 struct dir_baton *db = parent_baton;
1540 struct edit_baton *eb = db->edit_baton;
1541 apr_hash_t *entries;
1542 const char *name = svn_path_basename(path, pool);
1543 const char *full_path = svn_path_join(eb->anchor, path, pool);
1544 const char *dir_path;
1545 svn_node_kind_t kind;
1546 svn_wc_adm_access_t *adm_access;
1547 const char *hash_key;
1548 const svn_wc_entry_t *entry;
1549 svn_error_t *err;
1551 /* Note: when something is deleted, it's okay to tweak the
1552 statushash immediately. No need to wait until close_file or
1553 close_dir, because there's no risk of having to honor the 'added'
1554 flag. We already know this item exists in the working copy. */
1556 /* Read the parent's entries file. If the deleted thing is not
1557 versioned in this working copy, it was probably deleted via this
1558 working copy. No need to report such a thing. */
1559 /* ### use svn_wc_entry() instead? */
1560 SVN_ERR(svn_wc__entry_versioned(&entry, full_path, eb->adm_access,
1561 FALSE, pool));
1562 if (entry->kind == svn_node_dir)
1564 dir_path = full_path;
1565 hash_key = SVN_WC_ENTRY_THIS_DIR;
1567 else
1569 dir_path = svn_path_dirname(full_path, pool);
1570 hash_key = name;
1573 err = svn_wc_adm_retrieve(&adm_access, eb->adm_access, dir_path, pool);
1574 if (err)
1576 SVN_ERR(svn_io_check_path(full_path, &kind, pool));
1577 if ((kind == svn_node_none) && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
1579 /* We're probably dealing with a non-recursive, (or
1580 partially non-recursive, working copy. Due to deep bugs
1581 in how the client reports the state of non-recursive
1582 working copies, the repository can report that a path is
1583 deleted in an area where we not only don't have the path
1584 in question, we don't even have its parent(s). A
1585 complete fix would require a serious revamp of how
1586 non-recursive working copies store and report themselves,
1587 plus some thinking about the UI behavior we want when
1588 someone runs 'svn st -u' in a [partially] non-recursive
1589 working copy.
1591 For now, we just do our best to detect the condition and
1592 not report an error if it holds. See issue #2122. */
1593 svn_error_clear(err);
1594 return SVN_NO_ERROR;
1596 else
1597 return err;
1600 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
1601 if (apr_hash_get(entries, hash_key, APR_HASH_KEY_STRING))
1602 SVN_ERR(tweak_statushash(db, db, TRUE, eb->adm_access,
1603 full_path, entry->kind == svn_node_dir,
1604 svn_wc_status_deleted, 0, revision, NULL));
1606 /* Mark the parent dir -- it lost an entry (unless that parent dir
1607 is the root node and we're not supposed to report on the root
1608 node). */
1609 if (db->parent_baton && (! *eb->target))
1610 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE, eb->adm_access,
1611 db->path, entry->kind == svn_node_dir,
1612 svn_wc_status_modified, 0, SVN_INVALID_REVNUM,
1613 NULL));
1615 return SVN_NO_ERROR;
1619 static svn_error_t *
1620 add_directory(const char *path,
1621 void *parent_baton,
1622 const char *copyfrom_path,
1623 svn_revnum_t copyfrom_revision,
1624 apr_pool_t *pool,
1625 void **child_baton)
1627 struct dir_baton *pb = parent_baton;
1628 struct edit_baton *eb = pb->edit_baton;
1629 struct dir_baton *new_db;
1631 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
1633 /* Make this dir as added. */
1634 new_db = *child_baton;
1635 new_db->added = TRUE;
1637 /* Mark the parent as changed; it gained an entry. */
1638 pb->text_changed = TRUE;
1640 return SVN_NO_ERROR;
1644 static svn_error_t *
1645 open_directory(const char *path,
1646 void *parent_baton,
1647 svn_revnum_t base_revision,
1648 apr_pool_t *pool,
1649 void **child_baton)
1651 struct dir_baton *pb = parent_baton;
1652 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
1656 static svn_error_t *
1657 change_dir_prop(void *dir_baton,
1658 const char *name,
1659 const svn_string_t *value,
1660 apr_pool_t *pool)
1662 struct dir_baton *db = dir_baton;
1663 if (svn_wc_is_normal_prop(name))
1664 db->prop_changed = TRUE;
1666 /* Note any changes to the repository. */
1667 if (value != NULL)
1669 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
1670 db->ood_last_cmt_rev = SVN_STR_TO_REV(value->data);
1671 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
1672 db->ood_last_cmt_author = apr_pstrdup(db->pool, value->data);
1673 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
1675 apr_time_t tm;
1676 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
1677 db->ood_last_cmt_date = tm;
1681 return SVN_NO_ERROR;
1686 static svn_error_t *
1687 close_directory(void *dir_baton,
1688 apr_pool_t *pool)
1690 struct dir_baton *db = dir_baton;
1691 struct dir_baton *pb = db->parent_baton;
1692 struct edit_baton *eb = db->edit_baton;
1693 svn_wc_status2_t *dir_status = NULL;
1695 /* If nothing has changed and directory has no out of
1696 date descendants, return. */
1697 if (db->added || db->prop_changed || db->text_changed
1698 || db->ood_last_cmt_rev != SVN_INVALID_REVNUM)
1700 enum svn_wc_status_kind repos_text_status;
1701 enum svn_wc_status_kind repos_prop_status;
1703 /* If this is a new directory, add it to the statushash. */
1704 if (db->added)
1706 repos_text_status = svn_wc_status_added;
1707 repos_prop_status = db->prop_changed ? svn_wc_status_added
1708 : svn_wc_status_none;
1710 else
1712 repos_text_status = db->text_changed ? svn_wc_status_modified
1713 : svn_wc_status_none;
1714 repos_prop_status = db->prop_changed ? svn_wc_status_modified
1715 : svn_wc_status_none;
1718 /* Maybe add this directory to its parent's status hash. Note
1719 that tweak_statushash won't do anything if repos_text_status
1720 is not svn_wc_status_added. */
1721 if (pb)
1723 /* ### When we add directory locking, we need to find a
1724 ### directory lock here. */
1725 SVN_ERR(tweak_statushash(pb, db, TRUE,
1726 eb->adm_access,
1727 db->path, TRUE,
1728 repos_text_status,
1729 repos_prop_status, SVN_INVALID_REVNUM,
1730 NULL));
1732 else
1734 /* We're editing the root dir of the WC. As its repos
1735 status info isn't otherwise set, set it directly to
1736 trigger invocation of the status callback below. */
1737 eb->anchor_status->repos_prop_status = repos_prop_status;
1738 eb->anchor_status->repos_text_status = repos_text_status;
1740 /* If the root dir is out of date set the ood info directly too. */
1741 if (db->ood_last_cmt_rev != eb->anchor_status->entry->revision)
1743 eb->anchor_status->ood_last_cmt_rev = db->ood_last_cmt_rev;
1744 eb->anchor_status->ood_last_cmt_date = db->ood_last_cmt_date;
1745 eb->anchor_status->ood_kind = db->ood_kind;
1746 eb->anchor_status->ood_last_cmt_author =
1747 apr_pstrdup(pool, db->ood_last_cmt_author);
1752 /* Handle this directory's statuses, and then note in the parent
1753 that this has been done. */
1754 if (pb && ! db->excluded)
1756 svn_boolean_t was_deleted = FALSE;
1758 /* See if the directory was deleted or replaced. */
1759 dir_status = apr_hash_get(pb->statii, db->path, APR_HASH_KEY_STRING);
1760 if (dir_status &&
1761 ((dir_status->repos_text_status == svn_wc_status_deleted)
1762 || (dir_status->repos_text_status == svn_wc_status_replaced)))
1763 was_deleted = TRUE;
1765 /* Now do the status reporting. */
1766 SVN_ERR(handle_statii(eb, dir_status ? dir_status->entry : NULL,
1767 db->path, db->statii, was_deleted, db->depth,
1768 pool));
1769 if (dir_status && is_sendable_status(dir_status, eb))
1770 (eb->status_func)(eb->status_baton, db->path, dir_status);
1771 apr_hash_set(pb->statii, db->path, APR_HASH_KEY_STRING, NULL);
1773 else if (! pb)
1775 /* If this is the top-most directory, and the operation had a
1776 target, we should only report the target. */
1777 if (*eb->target)
1779 svn_wc_status2_t *tgt_status;
1780 const char *path = svn_path_join(eb->anchor, eb->target, pool);
1781 dir_status = eb->anchor_status;
1782 tgt_status = apr_hash_get(db->statii, path, APR_HASH_KEY_STRING);
1783 if (tgt_status)
1785 if (tgt_status->entry
1786 && tgt_status->entry->kind == svn_node_dir)
1788 svn_wc_adm_access_t *dir_access;
1789 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1790 path, pool));
1791 SVN_ERR(get_dir_status
1792 (eb, tgt_status->entry, dir_access, NULL,
1793 eb->ignores, eb->default_depth, eb->get_all,
1794 eb->no_ignore, TRUE,
1795 eb->status_func, eb->status_baton,
1796 eb->cancel_func, eb->cancel_baton, pool));
1798 if (is_sendable_status(tgt_status, eb))
1799 (eb->status_func)(eb->status_baton, path, tgt_status);
1802 else
1804 /* Otherwise, we report on all our children and ourself.
1805 Note that our directory couldn't have been deleted,
1806 because it is the root of the edit drive. */
1807 SVN_ERR(handle_statii(eb, eb->anchor_status->entry, db->path,
1808 db->statii, FALSE, eb->default_depth, pool));
1809 if (is_sendable_status(eb->anchor_status, eb))
1810 (eb->status_func)(eb->status_baton, db->path, eb->anchor_status);
1811 eb->anchor_status = NULL;
1814 return SVN_NO_ERROR;
1819 static svn_error_t *
1820 add_file(const char *path,
1821 void *parent_baton,
1822 const char *copyfrom_path,
1823 svn_revnum_t copyfrom_revision,
1824 apr_pool_t *pool,
1825 void **file_baton)
1827 struct dir_baton *pb = parent_baton;
1828 struct file_baton *new_fb = make_file_baton(pb, path, pool);
1830 /* Mark parent dir as changed */
1831 pb->text_changed = TRUE;
1833 /* Make this file as added. */
1834 new_fb->added = TRUE;
1836 *file_baton = new_fb;
1837 return SVN_NO_ERROR;
1841 static svn_error_t *
1842 open_file(const char *path,
1843 void *parent_baton,
1844 svn_revnum_t base_revision,
1845 apr_pool_t *pool,
1846 void **file_baton)
1848 struct dir_baton *pb = parent_baton;
1849 struct file_baton *new_fb = make_file_baton(pb, path, pool);
1851 *file_baton = new_fb;
1852 return SVN_NO_ERROR;
1856 static svn_error_t *
1857 apply_textdelta(void *file_baton,
1858 const char *base_checksum,
1859 apr_pool_t *pool,
1860 svn_txdelta_window_handler_t *handler,
1861 void **handler_baton)
1863 struct file_baton *fb = file_baton;
1865 /* Mark file as having textual mods. */
1866 fb->text_changed = TRUE;
1868 /* Send back a NULL window handler -- we don't need the actual diffs. */
1869 *handler_baton = NULL;
1870 *handler = svn_delta_noop_window_handler;
1872 return SVN_NO_ERROR;
1876 static svn_error_t *
1877 change_file_prop(void *file_baton,
1878 const char *name,
1879 const svn_string_t *value,
1880 apr_pool_t *pool)
1882 struct file_baton *fb = file_baton;
1883 if (svn_wc_is_normal_prop(name))
1884 fb->prop_changed = TRUE;
1886 /* Note any changes to the repository. */
1887 if (value != NULL)
1889 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
1890 fb->ood_last_cmt_rev = SVN_STR_TO_REV(value->data);
1891 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
1892 fb->ood_last_cmt_author = apr_pstrdup(fb->dir_baton->pool,
1893 value->data);
1894 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
1896 apr_time_t tm;
1897 SVN_ERR(svn_time_from_cstring(&tm, value->data,
1898 fb->dir_baton->pool));
1899 fb->ood_last_cmt_date = tm;
1903 return SVN_NO_ERROR;
1907 static svn_error_t *
1908 close_file(void *file_baton,
1909 const char *text_checksum, /* ignored, as we receive no data */
1910 apr_pool_t *pool)
1912 struct file_baton *fb = file_baton;
1913 enum svn_wc_status_kind repos_text_status;
1914 enum svn_wc_status_kind repos_prop_status;
1915 svn_lock_t *repos_lock = NULL;
1917 /* If nothing has changed, return. */
1918 if (! (fb->added || fb->prop_changed || fb->text_changed))
1919 return SVN_NO_ERROR;
1921 /* If this is a new file, add it to the statushash. */
1922 if (fb->added)
1924 const char *url;
1925 repos_text_status = svn_wc_status_added;
1926 repos_prop_status = fb->prop_changed ? svn_wc_status_added : 0;
1928 if (fb->edit_baton->repos_locks)
1930 url = find_dir_url(fb->dir_baton, pool);
1931 if (url)
1933 url = svn_path_url_add_component(url, fb->name, pool);
1934 repos_lock = apr_hash_get
1935 (fb->edit_baton->repos_locks,
1936 svn_path_uri_decode(url +
1937 strlen(fb->edit_baton->repos_root),
1938 pool), APR_HASH_KEY_STRING);
1942 else
1944 repos_text_status = fb->text_changed ? svn_wc_status_modified : 0;
1945 repos_prop_status = fb->prop_changed ? svn_wc_status_modified : 0;
1948 SVN_ERR(tweak_statushash(fb, NULL, FALSE,
1949 fb->edit_baton->adm_access,
1950 fb->path, FALSE,
1951 repos_text_status,
1952 repos_prop_status, SVN_INVALID_REVNUM,
1953 repos_lock));
1955 return SVN_NO_ERROR;
1959 static svn_error_t *
1960 close_edit(void *edit_baton,
1961 apr_pool_t *pool)
1963 struct edit_baton *eb = edit_baton;
1964 apr_array_header_t *ignores = eb->ignores;
1965 svn_error_t *err = NULL;
1967 /* If we get here and the root was not opened as part of the edit,
1968 we need to transmit statuses for everything. Otherwise, we
1969 should be done. */
1970 if (eb->root_opened)
1971 goto cleanup;
1973 /* If we have a target, that's the thing we're sending, otherwise
1974 we're sending the anchor. */
1976 if (*eb->target)
1978 svn_node_kind_t kind;
1979 const char *full_path = svn_path_join(eb->anchor, eb->target, pool);
1981 err = svn_io_check_path(full_path, &kind, pool);
1982 if (err) goto cleanup;
1984 if (kind == svn_node_dir)
1986 svn_wc_adm_access_t *tgt_access;
1987 const svn_wc_entry_t *tgt_entry;
1989 err = svn_wc_entry(&tgt_entry, full_path, eb->adm_access,
1990 FALSE, pool);
1991 if (err) goto cleanup;
1993 if (! tgt_entry)
1995 err = get_dir_status(eb, NULL, eb->adm_access, eb->target,
1996 ignores, svn_depth_empty, eb->get_all,
1997 TRUE, TRUE,
1998 eb->status_func, eb->status_baton,
1999 eb->cancel_func, eb->cancel_baton,
2000 pool);
2001 if (err) goto cleanup;
2003 else
2005 err = svn_wc_adm_retrieve(&tgt_access, eb->adm_access,
2006 full_path, pool);
2007 if (err) goto cleanup;
2009 err = get_dir_status(eb, NULL, tgt_access, NULL, ignores,
2010 eb->default_depth, eb->get_all,
2011 eb->no_ignore, FALSE,
2012 eb->status_func, eb->status_baton,
2013 eb->cancel_func, eb->cancel_baton,
2014 pool);
2015 if (err) goto cleanup;
2018 else
2020 err = get_dir_status(eb, NULL, eb->adm_access, eb->target,
2021 ignores, svn_depth_empty, eb->get_all,
2022 TRUE, TRUE, eb->status_func, eb->status_baton,
2023 eb->cancel_func, eb->cancel_baton, pool);
2024 if (err) goto cleanup;
2027 else
2029 err = get_dir_status(eb, NULL, eb->adm_access, NULL, ignores,
2030 eb->default_depth, eb->get_all, eb->no_ignore,
2031 FALSE, eb->status_func, eb->status_baton,
2032 eb->cancel_func, eb->cancel_baton, pool);
2033 if (err) goto cleanup;
2036 cleanup:
2037 /* Let's make sure that we didn't harvest any traversal info for the
2038 anchor if we had a target. */
2039 if (eb->traversal_info && *eb->target)
2041 apr_hash_set(eb->traversal_info->externals_old,
2042 eb->anchor, APR_HASH_KEY_STRING, NULL);
2043 apr_hash_set(eb->traversal_info->externals_new,
2044 eb->anchor, APR_HASH_KEY_STRING, NULL);
2045 apr_hash_set(eb->traversal_info->depths,
2046 eb->anchor, APR_HASH_KEY_STRING, NULL);
2049 return err;
2054 /*** Public API ***/
2056 svn_error_t *
2057 svn_wc_get_status_editor3(const svn_delta_editor_t **editor,
2058 void **edit_baton,
2059 void **set_locks_baton,
2060 svn_revnum_t *edit_revision,
2061 svn_wc_adm_access_t *anchor,
2062 const char *target,
2063 svn_depth_t depth,
2064 svn_boolean_t get_all,
2065 svn_boolean_t no_ignore,
2066 apr_array_header_t *ignore_patterns,
2067 svn_wc_status_func2_t status_func,
2068 void *status_baton,
2069 svn_cancel_func_t cancel_func,
2070 void *cancel_baton,
2071 svn_wc_traversal_info_t *traversal_info,
2072 apr_pool_t *pool)
2074 struct edit_baton *eb;
2075 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
2077 /* Construct an edit baton. */
2078 eb = apr_palloc(pool, sizeof(*eb));
2079 eb->default_depth = depth;
2080 eb->target_revision = edit_revision;
2081 eb->adm_access = anchor;
2082 eb->get_all = get_all;
2083 eb->no_ignore = no_ignore;
2084 eb->status_func = status_func;
2085 eb->status_baton = status_baton;
2086 eb->cancel_func = cancel_func;
2087 eb->cancel_baton = cancel_baton;
2088 eb->traversal_info = traversal_info;
2089 eb->externals = apr_hash_make(pool);
2090 eb->anchor = svn_wc_adm_access_path(anchor);
2091 eb->target = target;
2092 eb->root_opened = FALSE;
2093 eb->repos_locks = NULL;
2094 eb->repos_root = NULL;
2096 /* Use the caller-provided ignore patterns if provided; the build-time
2097 configured defaults otherwise. */
2098 if (ignore_patterns)
2100 eb->ignores = ignore_patterns;
2102 else
2104 eb->ignores = apr_array_make(pool, 16, sizeof(const char *));
2105 svn_cstring_split_append(eb->ignores, SVN_CONFIG_DEFAULT_GLOBAL_IGNORES,
2106 "\n\r\t\v ", FALSE, pool);
2109 /* The edit baton's status structure maps to PATH, and the editor
2110 have to be aware of whether that is the anchor or the target. */
2111 SVN_ERR(svn_wc_status2(&(eb->anchor_status), eb->anchor, anchor, pool));
2113 /* Construct an editor. */
2114 tree_editor->set_target_revision = set_target_revision;
2115 tree_editor->open_root = open_root;
2116 tree_editor->delete_entry = delete_entry;
2117 tree_editor->add_directory = add_directory;
2118 tree_editor->open_directory = open_directory;
2119 tree_editor->change_dir_prop = change_dir_prop;
2120 tree_editor->close_directory = close_directory;
2121 tree_editor->add_file = add_file;
2122 tree_editor->open_file = open_file;
2123 tree_editor->apply_textdelta = apply_textdelta;
2124 tree_editor->change_file_prop = change_file_prop;
2125 tree_editor->close_file = close_file;
2126 tree_editor->close_edit = close_edit;
2128 /* Conjoin a cancellation editor with our status editor. */
2129 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2130 tree_editor, eb, editor,
2131 edit_baton, pool));
2133 if (set_locks_baton)
2134 *set_locks_baton = eb;
2136 return SVN_NO_ERROR;
2140 svn_error_t *
2141 svn_wc_get_status_editor2(const svn_delta_editor_t **editor,
2142 void **edit_baton,
2143 void **set_locks_baton,
2144 svn_revnum_t *edit_revision,
2145 svn_wc_adm_access_t *anchor,
2146 const char *target,
2147 apr_hash_t *config,
2148 svn_boolean_t recurse,
2149 svn_boolean_t get_all,
2150 svn_boolean_t no_ignore,
2151 svn_wc_status_func2_t status_func,
2152 void *status_baton,
2153 svn_cancel_func_t cancel_func,
2154 void *cancel_baton,
2155 svn_wc_traversal_info_t *traversal_info,
2156 apr_pool_t *pool)
2158 apr_array_header_t *ignores;
2159 SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
2160 return svn_wc_get_status_editor3(editor,
2161 edit_baton,
2162 set_locks_baton,
2163 edit_revision,
2164 anchor,
2165 target,
2166 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
2167 get_all,
2168 no_ignore,
2169 ignores,
2170 status_func,
2171 status_baton,
2172 cancel_func,
2173 cancel_baton,
2174 traversal_info,
2175 pool);
2179 /* Helpers for deprecated svn_wc_status_editor(), of type
2180 svn_wc_status_func2_t. */
2181 struct old_status_func_cb_baton
2183 svn_wc_status_func_t original_func;
2184 void *original_baton;
2187 static void old_status_func_cb(void *baton,
2188 const char *path,
2189 svn_wc_status2_t *status)
2191 struct old_status_func_cb_baton *b = baton;
2192 svn_wc_status_t *stat = (svn_wc_status_t *) status;
2194 b->original_func(b->original_baton, path, stat);
2197 svn_error_t *
2198 svn_wc_get_status_editor(const svn_delta_editor_t **editor,
2199 void **edit_baton,
2200 svn_revnum_t *edit_revision,
2201 svn_wc_adm_access_t *anchor,
2202 const char *target,
2203 apr_hash_t *config,
2204 svn_boolean_t recurse,
2205 svn_boolean_t get_all,
2206 svn_boolean_t no_ignore,
2207 svn_wc_status_func_t status_func,
2208 void *status_baton,
2209 svn_cancel_func_t cancel_func,
2210 void *cancel_baton,
2211 svn_wc_traversal_info_t *traversal_info,
2212 apr_pool_t *pool)
2214 struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b));
2215 apr_array_header_t *ignores;
2216 b->original_func = status_func;
2217 b->original_baton = status_baton;
2218 SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
2219 return svn_wc_get_status_editor3(editor, edit_baton, NULL, edit_revision,
2220 anchor, target,
2221 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
2222 get_all, no_ignore, ignores,
2223 old_status_func_cb, b,
2224 cancel_func, cancel_baton,
2225 traversal_info, pool);
2229 svn_error_t *
2230 svn_wc_status_set_repos_locks(void *edit_baton,
2231 apr_hash_t *locks,
2232 const char *repos_root,
2233 apr_pool_t *pool)
2235 struct edit_baton *eb = edit_baton;
2237 eb->repos_locks = locks;
2238 eb->repos_root = apr_pstrdup(pool, repos_root);
2240 return SVN_NO_ERROR;
2244 svn_error_t *
2245 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2246 apr_hash_t *config,
2247 apr_pool_t *pool)
2249 svn_config_t *cfg = config ? apr_hash_get(config,
2250 SVN_CONFIG_CATEGORY_CONFIG,
2251 APR_HASH_KEY_STRING) : NULL;
2252 const char *val;
2254 /* Check the Subversion run-time configuration for global ignores.
2255 If no configuration value exists, we fall back to our defaults. */
2256 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2257 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2258 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2259 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2261 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2262 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2263 return SVN_NO_ERROR;
2267 svn_error_t *
2268 svn_wc_status2(svn_wc_status2_t **status,
2269 const char *path,
2270 svn_wc_adm_access_t *adm_access,
2271 apr_pool_t *pool)
2273 const svn_wc_entry_t *entry = NULL;
2274 const svn_wc_entry_t *parent_entry = NULL;
2276 if (adm_access)
2277 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2279 if (entry && ! svn_path_is_empty(path))
2281 const char *parent_path = svn_path_dirname(path, pool);
2282 svn_wc_adm_access_t *parent_access;
2283 SVN_ERR(svn_wc__adm_retrieve_internal(&parent_access, adm_access,
2284 parent_path, pool));
2285 if (parent_access)
2286 SVN_ERR(svn_wc_entry(&parent_entry, parent_path, parent_access,
2287 FALSE, pool));
2290 SVN_ERR(assemble_status(status, path, adm_access, entry, parent_entry,
2291 svn_node_unknown, FALSE, /* bogus */
2292 TRUE, FALSE, NULL, NULL, pool));
2293 return SVN_NO_ERROR;
2297 svn_error_t *
2298 svn_wc_status(svn_wc_status_t **status,
2299 const char *path,
2300 svn_wc_adm_access_t *adm_access,
2301 apr_pool_t *pool)
2303 svn_wc_status2_t *stat2;
2305 SVN_ERR(svn_wc_status2(&stat2, path, adm_access, pool));
2306 *status = (svn_wc_status_t *) stat2;
2307 return SVN_NO_ERROR;
2312 svn_wc_status2_t *
2313 svn_wc_dup_status2(svn_wc_status2_t *orig_stat,
2314 apr_pool_t *pool)
2316 svn_wc_status2_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2318 /* Shallow copy all members. */
2319 *new_stat = *orig_stat;
2321 /* No go back and dup the deep item. */
2322 if (orig_stat->entry)
2323 new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
2325 if (orig_stat->repos_lock)
2326 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2328 if (orig_stat->url)
2329 new_stat->url = apr_pstrdup(pool, orig_stat->url);
2331 if (orig_stat->ood_last_cmt_author)
2332 new_stat->ood_last_cmt_author
2333 = apr_pstrdup(pool, orig_stat->ood_last_cmt_author);
2335 /* Return the new hotness. */
2336 return new_stat;
2340 svn_wc_status_t *
2341 svn_wc_dup_status(svn_wc_status_t *orig_stat,
2342 apr_pool_t *pool)
2344 svn_wc_status_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2346 /* Shallow copy all members. */
2347 *new_stat = *orig_stat;
2349 /* No go back and dup the deep item. */
2350 if (orig_stat->entry)
2351 new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
2353 /* Return the new hotness. */
2354 return new_stat;
2357 svn_error_t *
2358 svn_wc_get_ignores(apr_array_header_t **patterns,
2359 apr_hash_t *config,
2360 svn_wc_adm_access_t *adm_access,
2361 apr_pool_t *pool)
2363 apr_array_header_t *default_ignores;
2365 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, pool));
2366 SVN_ERR(collect_ignore_patterns(patterns, default_ignores, adm_access,
2367 pool));
2369 return SVN_NO_ERROR;