Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_wc / status.c
blobab6914cda8190851c4fa07afae75d9e4fcc74f72
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 This also catches externals; not sure whether that's good or bad,
939 but it's what's happening right now. */
940 for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
942 const void *key;
943 apr_ssize_t klen;
944 void *val;
945 svn_io_dirent_t *dirent_p;
947 svn_pool_clear(iterpool);
949 apr_hash_this(hi, &key, &klen, &val);
951 /* Skip versioned, non-external things, and skip the
952 administrative directory. */
953 if (apr_hash_get(entries, key, klen)
954 || svn_wc_is_adm_dir(key, iterpool))
955 continue;
957 dirent_p = val;
959 if (depth == svn_depth_files && dirent_p->kind == svn_node_dir)
960 continue;
962 if (ignore_patterns && ! patterns)
963 SVN_ERR(collect_ignore_patterns(&patterns, ignore_patterns,
964 adm_access, subpool));
966 SVN_ERR(send_unversioned_item(key, dirent_p->kind, dirent_p->special,
967 adm_access,
968 patterns, eb->externals, no_ignore,
969 eb->repos_locks, eb->repos_root,
970 status_func, status_baton, iterpool));
973 /* Loop over entries hash */
974 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
976 const void *key;
977 void *val;
978 svn_io_dirent_t *dirent_p;
980 /* Get the next entry */
981 apr_hash_this(hi, &key, NULL, &val);
983 dirent_p = apr_hash_get(dirents, key, APR_HASH_KEY_STRING);
985 /* ### todo: What if the subdir is from another repository? */
987 /* Skip "this-dir". */
988 if (strcmp(key, SVN_WC_ENTRY_THIS_DIR) == 0)
989 continue;
991 /* Skip directories if user is only interested in files */
992 if (depth == svn_depth_files
993 && dirent_p && dirent_p->kind == svn_node_dir)
994 continue;
996 /* Clear the iteration subpool. */
997 svn_pool_clear(iterpool);
999 /* Handle this directory entry (possibly recursing). */
1000 SVN_ERR(handle_dir_entry(eb, adm_access, key, dir_entry, val,
1001 dirent_p ? dirent_p->kind : svn_node_none,
1002 dirent_p ? dirent_p->special : FALSE,
1003 ignore_patterns,
1004 depth == svn_depth_infinity ? depth
1005 : svn_depth_empty,
1006 get_all, no_ignore,
1007 status_func, status_baton, cancel_func,
1008 cancel_baton, iterpool));
1011 /* Destroy our subpools. */
1012 svn_pool_destroy(subpool);
1014 return SVN_NO_ERROR;
1019 /*** Helpers ***/
1021 /* A faux status callback function for stashing STATUS item in an hash
1022 (which is the BATON), keyed on PATH. This implements the
1023 svn_wc_status_func2_t interface. */
1024 static void
1025 hash_stash(void *baton,
1026 const char *path,
1027 svn_wc_status2_t *status)
1029 apr_hash_t *stat_hash = baton;
1030 apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
1031 assert(! apr_hash_get(stat_hash, path, APR_HASH_KEY_STRING));
1032 apr_hash_set(stat_hash, apr_pstrdup(hash_pool, path),
1033 APR_HASH_KEY_STRING, svn_wc_dup_status2(status, hash_pool));
1037 /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
1038 baton is a struct *dir_baton or struct *file_baton. If the value doesn't
1039 yet exist, and the REPOS_TEXT_STATUS indicates that this is an
1040 addition, create a new status struct using the hash's pool.
1042 If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
1043 of date (ood) information we want to set in BATON. This is necessary
1044 because this function tweaks the status of out-of-date directories
1045 (BATON == THIS_DIR_BATON) and out-of-date directories' parents
1046 (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
1047 contains the ood info we want to bubble up to ancestor directories so these
1048 accurately reflect the fact they have an ood descendant.
1050 Merge REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the status structure's
1051 "network" fields.
1053 Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
1054 is ignored:
1056 If REPOS_TEXT_STATUS is svn_wc_status_deleted then DELETED_REV is
1057 optionally the revision path was deleted, in all other cases it must
1058 be set to SVN_INVALID_REVNUM. If DELETED_REV is not
1059 SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
1060 then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
1061 If DELETED_REV is SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is
1062 svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
1063 ood_last_cmt_rev value - see comment below.
1065 If a new struct was added, set the repos_lock to REPOS_LOCK. */
1066 static svn_error_t *
1067 tweak_statushash(void *baton,
1068 void *this_dir_baton,
1069 svn_boolean_t is_dir_baton,
1070 svn_wc_adm_access_t *adm_access,
1071 const char *path,
1072 svn_boolean_t is_dir,
1073 enum svn_wc_status_kind repos_text_status,
1074 enum svn_wc_status_kind repos_prop_status,
1075 svn_revnum_t deleted_rev,
1076 svn_lock_t *repos_lock)
1078 svn_wc_status2_t *statstruct;
1079 apr_pool_t *pool;
1080 apr_hash_t *statushash;
1082 if (is_dir_baton)
1083 statushash = ((struct dir_baton *) baton)->statii;
1084 else
1085 statushash = ((struct file_baton *) baton)->dir_baton->statii;
1086 pool = apr_hash_pool_get(statushash);
1088 /* Is PATH already a hash-key? */
1089 statstruct = apr_hash_get(statushash, path, APR_HASH_KEY_STRING);
1091 /* If not, make it so. */
1092 if (! statstruct)
1094 /* If this item isn't being added, then we're most likely
1095 dealing with a non-recursive (or at least partially
1096 non-recursive) working copy. Due to bugs in how the client
1097 reports the state of non-recursive working copies, the
1098 repository can send back responses about paths that don't
1099 even exist locally. Our best course here is just to ignore
1100 those responses. After all, if the client had reported
1101 correctly in the first, that path would either be mentioned
1102 as an 'add' or not mentioned at all, depending on how we
1103 eventually fix the bugs in non-recursivity. See issue
1104 #2122 for details. */
1105 if (repos_text_status != svn_wc_status_added)
1106 return SVN_NO_ERROR;
1108 /* Use the public API to get a statstruct, and put it into the hash. */
1109 SVN_ERR(svn_wc_status2(&statstruct, path, adm_access, pool));
1110 statstruct->repos_lock = repos_lock;
1111 apr_hash_set(statushash, apr_pstrdup(pool, path),
1112 APR_HASH_KEY_STRING, statstruct);
1115 /* Merge a repos "delete" + "add" into a single "replace". */
1116 if ((repos_text_status == svn_wc_status_added)
1117 && (statstruct->repos_text_status == svn_wc_status_deleted))
1118 repos_text_status = svn_wc_status_replaced;
1120 /* Tweak the structure's repos fields. */
1121 if (repos_text_status)
1122 statstruct->repos_text_status = repos_text_status;
1123 if (repos_prop_status)
1124 statstruct->repos_prop_status = repos_prop_status;
1126 /* Copy out-of-date info. */
1127 if (is_dir_baton)
1129 struct dir_baton *b = this_dir_baton;
1131 if (b->url)
1133 if (statstruct->repos_text_status == svn_wc_status_deleted)
1135 /* When deleting PATH, BATON is for PATH's parent,
1136 so we must construct PATH's real statstruct->url. */
1137 statstruct->url =
1138 svn_path_url_add_component(b->url,
1139 svn_path_basename(path, pool),
1140 pool);
1142 else
1143 statstruct->url = apr_pstrdup(pool, b->url);
1146 /* The last committed date, and author for deleted items
1147 isn't available. */
1148 if (statstruct->repos_text_status == svn_wc_status_deleted)
1150 statstruct->ood_kind = is_dir ? svn_node_dir : svn_node_file;
1152 /* Pre 1.5 servers don't provide the revision a path was deleted.
1153 So we punt and use the last committed revision of the path's
1154 parent, which has some chance of being correct. At worse it
1155 is a higher revision than the path was deleted, but this is
1156 better than nothing... */
1157 if (deleted_rev == SVN_INVALID_REVNUM)
1158 statstruct->ood_last_cmt_rev =
1159 ((struct dir_baton *) baton)->ood_last_cmt_rev;
1160 else
1161 statstruct->ood_last_cmt_rev = deleted_rev;
1163 else
1165 statstruct->ood_kind = b->ood_kind;
1166 statstruct->ood_last_cmt_rev = b->ood_last_cmt_rev;
1167 statstruct->ood_last_cmt_date = b->ood_last_cmt_date;
1168 if (b->ood_last_cmt_author)
1169 statstruct->ood_last_cmt_author =
1170 apr_pstrdup(pool, b->ood_last_cmt_author);
1174 else
1176 struct file_baton *b = baton;
1177 if (b->url)
1178 statstruct->url = apr_pstrdup(pool, b->url);
1179 statstruct->ood_last_cmt_rev = b->ood_last_cmt_rev;
1180 statstruct->ood_last_cmt_date = b->ood_last_cmt_date;
1181 statstruct->ood_kind = b->ood_kind;
1182 if (b->ood_last_cmt_author)
1183 statstruct->ood_last_cmt_author =
1184 apr_pstrdup(pool, b->ood_last_cmt_author);
1186 return SVN_NO_ERROR;
1189 /* Returns the URL for DB, or NULL: */
1190 static const char *
1191 find_dir_url(const struct dir_baton *db, apr_pool_t *pool)
1193 /* If we have no name, we're the root, return the anchor URL. */
1194 if (! db->name)
1195 return db->edit_baton->anchor_status->entry->url;
1196 else
1198 const char *url;
1199 struct dir_baton *pb = db->parent_baton;
1200 svn_wc_status2_t *status = apr_hash_get(pb->statii, db->name,
1201 APR_HASH_KEY_STRING);
1202 /* Note that status->entry->url is NULL in the case of a missing
1203 * directory, which means we need to recurse up another level to
1204 * get a useful URL. */
1205 if (status && status->entry && status->entry->url)
1206 return status->entry->url;
1208 url = find_dir_url(pb, pool);
1209 if (url)
1210 return svn_path_url_add_component(url, db->name, pool);
1211 else
1212 return NULL;
1218 /* Create a new dir_baton for subdir PATH. */
1219 static svn_error_t *
1220 make_dir_baton(void **dir_baton,
1221 const char *path,
1222 struct edit_baton *edit_baton,
1223 struct dir_baton *parent_baton,
1224 apr_pool_t *pool)
1226 struct dir_baton *pb = parent_baton;
1227 struct edit_baton *eb = edit_baton;
1228 struct dir_baton *d = apr_pcalloc(pool, sizeof(*d));
1229 const char *full_path;
1230 svn_wc_status2_t *status_in_parent;
1232 /* Don't do this. Just do NOT do this to me. */
1233 if (pb && (! path))
1234 abort();
1236 /* Construct the full path of this directory. */
1237 if (pb)
1238 full_path = svn_path_join(eb->anchor, path, pool);
1239 else
1240 full_path = apr_pstrdup(pool, eb->anchor);
1242 /* Finish populating the baton members. */
1243 d->path = full_path;
1244 d->name = path ? (svn_path_basename(path, pool)) : NULL;
1245 d->edit_baton = edit_baton;
1246 d->parent_baton = parent_baton;
1247 d->pool = pool;
1248 d->statii = apr_hash_make(pool);
1249 d->url = apr_pstrdup(pool, find_dir_url(d, pool));
1250 d->ood_last_cmt_rev = SVN_INVALID_REVNUM;
1251 d->ood_last_cmt_date = 0;
1252 d->ood_kind = svn_node_dir;
1253 d->ood_last_cmt_author = NULL;
1255 if (pb)
1257 if (pb->excluded)
1258 d->excluded = TRUE;
1259 else if (pb->depth == svn_depth_immediates)
1260 d->depth = svn_depth_empty;
1261 else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
1262 d->excluded = TRUE;
1263 else if (pb->depth == svn_depth_unknown)
1264 /* This is only tentative, it can be overridden from d's entry
1265 later. */
1266 d->depth = svn_depth_unknown;
1267 else
1268 d->depth = svn_depth_infinity;
1270 else
1272 d->depth = eb->default_depth;
1275 /* Get the status for this path's children. Of course, we only want
1276 to do this if the path is versioned as a directory. */
1277 if (pb)
1278 status_in_parent = apr_hash_get(pb->statii, d->path, APR_HASH_KEY_STRING);
1279 else
1280 status_in_parent = eb->anchor_status;
1282 /* Order is important here. We can't depend on status_in_parent->entry
1283 being non-NULL until after we've checked all the conditions that
1284 might indicate that the parent is unversioned ("unversioned" for
1285 our purposes includes being an external or ignored item). */
1286 if (status_in_parent
1287 && (status_in_parent->text_status != svn_wc_status_unversioned)
1288 && (status_in_parent->text_status != svn_wc_status_missing)
1289 && (status_in_parent->text_status != svn_wc_status_obstructed)
1290 && (status_in_parent->text_status != svn_wc_status_external)
1291 && (status_in_parent->text_status != svn_wc_status_ignored)
1292 && (status_in_parent->entry->kind == svn_node_dir)
1293 && (! d->excluded)
1294 && (d->depth == svn_depth_unknown
1295 || d->depth == svn_depth_infinity
1296 || d->depth == svn_depth_files
1297 || d->depth == svn_depth_immediates)
1300 svn_wc_adm_access_t *dir_access;
1301 svn_wc_status2_t *this_dir_status;
1302 apr_array_header_t *ignores = eb->ignores;
1303 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1304 d->path, pool));
1305 SVN_ERR(get_dir_status(eb, status_in_parent->entry, dir_access, NULL,
1306 ignores, d->depth == svn_depth_files ?
1307 svn_depth_files : svn_depth_immediates,
1308 TRUE, TRUE, TRUE, hash_stash, d->statii, NULL,
1309 NULL, pool));
1311 /* If we found a depth here, it should govern. */
1312 this_dir_status = apr_hash_get(d->statii, d->path, APR_HASH_KEY_STRING);
1313 if (this_dir_status && this_dir_status->entry
1314 && (d->depth == svn_depth_unknown
1315 || d->depth > status_in_parent->entry->depth))
1317 d->depth = this_dir_status->entry->depth;
1321 *dir_baton = d;
1322 return SVN_NO_ERROR;
1326 /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
1327 NAME is just one component, not a path. */
1328 static struct file_baton *
1329 make_file_baton(struct dir_baton *parent_dir_baton,
1330 const char *path,
1331 apr_pool_t *pool)
1333 struct dir_baton *pb = parent_dir_baton;
1334 struct edit_baton *eb = pb->edit_baton;
1335 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
1336 const char *full_path;
1338 /* Construct the full path of this file. */
1339 full_path = svn_path_join(eb->anchor, path, pool);
1341 /* Finish populating the baton members. */
1342 f->path = full_path;
1343 f->name = svn_path_basename(path, pool);
1344 f->pool = pool;
1345 f->dir_baton = pb;
1346 f->edit_baton = eb;
1347 f->url = svn_path_url_add_component(find_dir_url(pb, pool),
1348 svn_path_basename(full_path, pool),
1349 pool);
1350 f->ood_last_cmt_rev = SVN_INVALID_REVNUM;
1351 f->ood_last_cmt_date = 0;
1352 f->ood_kind = svn_node_file;
1353 f->ood_last_cmt_author = NULL;
1354 return f;
1357 /* Return a boolean answer to the question "Is STATUS something that
1358 should be reported?". EB is the edit baton. */
1359 static svn_boolean_t
1360 is_sendable_status(svn_wc_status2_t *status,
1361 struct edit_baton *eb)
1363 /* If the repository status was touched at all, it's interesting. */
1364 if (status->repos_text_status != svn_wc_status_none)
1365 return TRUE;
1366 if (status->repos_prop_status != svn_wc_status_none)
1367 return TRUE;
1369 /* If there is a lock in the repository, send it. */
1370 if (status->repos_lock)
1371 return TRUE;
1373 /* If the item is ignored, and we don't want ignores, skip it. */
1374 if ((status->text_status == svn_wc_status_ignored) && (! eb->no_ignore))
1375 return FALSE;
1377 /* If we want everything, we obviously want this single-item subset
1378 of everything. */
1379 if (eb->get_all)
1380 return TRUE;
1382 /* If the item is unversioned, display it. */
1383 if (status->text_status == svn_wc_status_unversioned)
1384 return TRUE;
1386 /* If the text or property states are interesting, send it. */
1387 if ((status->text_status != svn_wc_status_none)
1388 && (status->text_status != svn_wc_status_normal))
1389 return TRUE;
1390 if ((status->prop_status != svn_wc_status_none)
1391 && (status->prop_status != svn_wc_status_normal))
1392 return TRUE;
1394 /* If it's locked or switched, send it. */
1395 if (status->locked)
1396 return TRUE;
1397 if (status->switched)
1398 return TRUE;
1400 /* If there is a lock token, send it. */
1401 if (status->entry && status->entry->lock_token)
1402 return TRUE;
1404 /* If the entry is associated with a changelist, send it. */
1405 if (status->entry && status->entry->changelist)
1406 return TRUE;
1408 /* Otherwise, don't send it. */
1409 return FALSE;
1413 /* Baton for mark_status. */
1414 struct status_baton
1416 svn_wc_status_func2_t real_status_func; /* real status function */
1417 void *real_status_baton; /* real status baton */
1420 /* A status callback function which wraps the *real* status
1421 function/baton. It simply sets the "repos_text_status" field of the
1422 STATUS to svn_wc_status_deleted and passes it off to the real
1423 status func/baton. */
1424 static void
1425 mark_deleted(void *baton,
1426 const char *path,
1427 svn_wc_status2_t *status)
1429 struct status_baton *sb = baton;
1430 status->repos_text_status = svn_wc_status_deleted;
1431 sb->real_status_func(sb->real_status_baton, path, status);
1435 /* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
1436 and DIR_ENTRY are the on-disk path and entry, respectively, for the
1437 directory itself. Descend into subdirectories according to DEPTH.
1438 Also, if DIR_WAS_DELETED is set, each status that is reported
1439 through this function will have its repos_text_status field showing
1440 a deletion. Use POOL for all allocations. */
1441 static svn_error_t *
1442 handle_statii(struct edit_baton *eb,
1443 svn_wc_entry_t *dir_entry,
1444 const char *dir_path,
1445 apr_hash_t *statii,
1446 svn_boolean_t dir_was_deleted,
1447 svn_depth_t depth,
1448 apr_pool_t *pool)
1450 apr_array_header_t *ignores = eb->ignores;
1451 apr_hash_index_t *hi;
1452 apr_pool_t *subpool = svn_pool_create(pool);
1453 svn_wc_status_func2_t status_func = eb->status_func;
1454 void *status_baton = eb->status_baton;
1455 struct status_baton sb;
1457 if (dir_was_deleted)
1459 sb.real_status_func = eb->status_func;
1460 sb.real_status_baton = eb->status_baton;
1461 status_func = mark_deleted;
1462 status_baton = &sb;
1465 /* Loop over all the statuses still in our hash, handling each one. */
1466 for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
1468 const void *key;
1469 void *val;
1470 svn_wc_status2_t *status;
1472 apr_hash_this(hi, &key, NULL, &val);
1473 status = val;
1475 /* Clear the subpool. */
1476 svn_pool_clear(subpool);
1478 /* Now, handle the status. We don't recurse for svn_depth_immediates
1479 because we already have the subdirectories' statii. */
1480 if (status->text_status != svn_wc_status_obstructed
1481 && status->text_status != svn_wc_status_missing
1482 && status->entry && status->entry->kind == svn_node_dir
1483 && (depth == svn_depth_unknown
1484 || depth == svn_depth_infinity))
1486 svn_wc_adm_access_t *dir_access;
1488 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1489 key, subpool));
1491 SVN_ERR(get_dir_status(eb, dir_entry, dir_access, NULL,
1492 ignores, depth, eb->get_all,
1493 eb->no_ignore, TRUE, status_func,
1494 status_baton, eb->cancel_func,
1495 eb->cancel_baton, subpool));
1497 if (dir_was_deleted)
1498 status->repos_text_status = svn_wc_status_deleted;
1499 if (is_sendable_status(status, eb))
1500 (eb->status_func)(eb->status_baton, key, status);
1503 /* Destroy the subpool. */
1504 svn_pool_destroy(subpool);
1506 return SVN_NO_ERROR;
1510 /*----------------------------------------------------------------------*/
1512 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1514 static svn_error_t *
1515 set_target_revision(void *edit_baton,
1516 svn_revnum_t target_revision,
1517 apr_pool_t *pool)
1519 struct edit_baton *eb = edit_baton;
1520 *(eb->target_revision) = target_revision;
1521 return SVN_NO_ERROR;
1525 static svn_error_t *
1526 open_root(void *edit_baton,
1527 svn_revnum_t base_revision,
1528 apr_pool_t *pool,
1529 void **dir_baton)
1531 struct edit_baton *eb = edit_baton;
1532 eb->root_opened = TRUE;
1533 return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
1537 static svn_error_t *
1538 delete_entry(const char *path,
1539 svn_revnum_t revision,
1540 void *parent_baton,
1541 apr_pool_t *pool)
1543 struct dir_baton *db = parent_baton;
1544 struct edit_baton *eb = db->edit_baton;
1545 apr_hash_t *entries;
1546 const char *name = svn_path_basename(path, pool);
1547 const char *full_path = svn_path_join(eb->anchor, path, pool);
1548 const char *dir_path;
1549 svn_node_kind_t kind;
1550 svn_wc_adm_access_t *adm_access;
1551 const char *hash_key;
1552 const svn_wc_entry_t *entry;
1553 svn_error_t *err;
1555 /* Note: when something is deleted, it's okay to tweak the
1556 statushash immediately. No need to wait until close_file or
1557 close_dir, because there's no risk of having to honor the 'added'
1558 flag. We already know this item exists in the working copy. */
1560 /* Read the parent's entries file. If the deleted thing is not
1561 versioned in this working copy, it was probably deleted via this
1562 working copy. No need to report such a thing. */
1563 /* ### use svn_wc_entry() instead? */
1564 SVN_ERR(svn_wc__entry_versioned(&entry, full_path, eb->adm_access,
1565 FALSE, pool));
1566 if (entry->kind == svn_node_dir)
1568 dir_path = full_path;
1569 hash_key = SVN_WC_ENTRY_THIS_DIR;
1571 else
1573 dir_path = svn_path_dirname(full_path, pool);
1574 hash_key = name;
1577 err = svn_wc_adm_retrieve(&adm_access, eb->adm_access, dir_path, pool);
1578 if (err)
1580 SVN_ERR(svn_io_check_path(full_path, &kind, pool));
1581 if ((kind == svn_node_none) && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
1583 /* We're probably dealing with a non-recursive, (or
1584 partially non-recursive, working copy. Due to deep bugs
1585 in how the client reports the state of non-recursive
1586 working copies, the repository can report that a path is
1587 deleted in an area where we not only don't have the path
1588 in question, we don't even have its parent(s). A
1589 complete fix would require a serious revamp of how
1590 non-recursive working copies store and report themselves,
1591 plus some thinking about the UI behavior we want when
1592 someone runs 'svn st -u' in a [partially] non-recursive
1593 working copy.
1595 For now, we just do our best to detect the condition and
1596 not report an error if it holds. See issue #2122. */
1597 svn_error_clear(err);
1598 return SVN_NO_ERROR;
1600 else
1601 return err;
1604 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
1605 if (apr_hash_get(entries, hash_key, APR_HASH_KEY_STRING))
1606 SVN_ERR(tweak_statushash(db, db, TRUE, eb->adm_access,
1607 full_path, entry->kind == svn_node_dir,
1608 svn_wc_status_deleted, 0, revision, NULL));
1610 /* Mark the parent dir -- it lost an entry (unless that parent dir
1611 is the root node and we're not supposed to report on the root
1612 node). */
1613 if (db->parent_baton && (! *eb->target))
1614 SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE, eb->adm_access,
1615 db->path, entry->kind == svn_node_dir,
1616 svn_wc_status_modified, 0, SVN_INVALID_REVNUM,
1617 NULL));
1619 return SVN_NO_ERROR;
1623 static svn_error_t *
1624 add_directory(const char *path,
1625 void *parent_baton,
1626 const char *copyfrom_path,
1627 svn_revnum_t copyfrom_revision,
1628 apr_pool_t *pool,
1629 void **child_baton)
1631 struct dir_baton *pb = parent_baton;
1632 struct edit_baton *eb = pb->edit_baton;
1633 struct dir_baton *new_db;
1635 SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
1637 /* Make this dir as added. */
1638 new_db = *child_baton;
1639 new_db->added = TRUE;
1641 /* Mark the parent as changed; it gained an entry. */
1642 pb->text_changed = TRUE;
1644 return SVN_NO_ERROR;
1648 static svn_error_t *
1649 open_directory(const char *path,
1650 void *parent_baton,
1651 svn_revnum_t base_revision,
1652 apr_pool_t *pool,
1653 void **child_baton)
1655 struct dir_baton *pb = parent_baton;
1656 return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
1660 static svn_error_t *
1661 change_dir_prop(void *dir_baton,
1662 const char *name,
1663 const svn_string_t *value,
1664 apr_pool_t *pool)
1666 struct dir_baton *db = dir_baton;
1667 if (svn_wc_is_normal_prop(name))
1668 db->prop_changed = TRUE;
1670 /* Note any changes to the repository. */
1671 if (value != NULL)
1673 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
1674 db->ood_last_cmt_rev = SVN_STR_TO_REV(value->data);
1675 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
1676 db->ood_last_cmt_author = apr_pstrdup(db->pool, value->data);
1677 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
1679 apr_time_t tm;
1680 SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
1681 db->ood_last_cmt_date = tm;
1685 return SVN_NO_ERROR;
1690 static svn_error_t *
1691 close_directory(void *dir_baton,
1692 apr_pool_t *pool)
1694 struct dir_baton *db = dir_baton;
1695 struct dir_baton *pb = db->parent_baton;
1696 struct edit_baton *eb = db->edit_baton;
1697 svn_wc_status2_t *dir_status = NULL;
1699 /* If nothing has changed and directory has no out of
1700 date descendants, return. */
1701 if (db->added || db->prop_changed || db->text_changed
1702 || db->ood_last_cmt_rev != SVN_INVALID_REVNUM)
1704 enum svn_wc_status_kind repos_text_status;
1705 enum svn_wc_status_kind repos_prop_status;
1707 /* If this is a new directory, add it to the statushash. */
1708 if (db->added)
1710 repos_text_status = svn_wc_status_added;
1711 repos_prop_status = db->prop_changed ? svn_wc_status_added
1712 : svn_wc_status_none;
1714 else
1716 repos_text_status = db->text_changed ? svn_wc_status_modified
1717 : svn_wc_status_none;
1718 repos_prop_status = db->prop_changed ? svn_wc_status_modified
1719 : svn_wc_status_none;
1722 /* Maybe add this directory to its parent's status hash. Note
1723 that tweak_statushash won't do anything if repos_text_status
1724 is not svn_wc_status_added. */
1725 if (pb)
1727 /* ### When we add directory locking, we need to find a
1728 ### directory lock here. */
1729 SVN_ERR(tweak_statushash(pb, db, TRUE,
1730 eb->adm_access,
1731 db->path, TRUE,
1732 repos_text_status,
1733 repos_prop_status, SVN_INVALID_REVNUM,
1734 NULL));
1736 else
1738 /* We're editing the root dir of the WC. As its repos
1739 status info isn't otherwise set, set it directly to
1740 trigger invocation of the status callback below. */
1741 eb->anchor_status->repos_prop_status = repos_prop_status;
1742 eb->anchor_status->repos_text_status = repos_text_status;
1744 /* If the root dir is out of date set the ood info directly too. */
1745 if (db->ood_last_cmt_rev != eb->anchor_status->entry->revision)
1747 eb->anchor_status->ood_last_cmt_rev = db->ood_last_cmt_rev;
1748 eb->anchor_status->ood_last_cmt_date = db->ood_last_cmt_date;
1749 eb->anchor_status->ood_kind = db->ood_kind;
1750 eb->anchor_status->ood_last_cmt_author =
1751 apr_pstrdup(pool, db->ood_last_cmt_author);
1756 /* Handle this directory's statuses, and then note in the parent
1757 that this has been done. */
1758 if (pb && ! db->excluded)
1760 svn_boolean_t was_deleted = FALSE;
1762 /* See if the directory was deleted or replaced. */
1763 dir_status = apr_hash_get(pb->statii, db->path, APR_HASH_KEY_STRING);
1764 if (dir_status &&
1765 ((dir_status->repos_text_status == svn_wc_status_deleted)
1766 || (dir_status->repos_text_status == svn_wc_status_replaced)))
1767 was_deleted = TRUE;
1769 /* Now do the status reporting. */
1770 SVN_ERR(handle_statii(eb, dir_status ? dir_status->entry : NULL,
1771 db->path, db->statii, was_deleted, db->depth,
1772 pool));
1773 if (dir_status && is_sendable_status(dir_status, eb))
1774 (eb->status_func)(eb->status_baton, db->path, dir_status);
1775 apr_hash_set(pb->statii, db->path, APR_HASH_KEY_STRING, NULL);
1777 else if (! pb)
1779 /* If this is the top-most directory, and the operation had a
1780 target, we should only report the target. */
1781 if (*eb->target)
1783 svn_wc_status2_t *tgt_status;
1784 const char *path = svn_path_join(eb->anchor, eb->target, pool);
1785 dir_status = eb->anchor_status;
1786 tgt_status = apr_hash_get(db->statii, path, APR_HASH_KEY_STRING);
1787 if (tgt_status)
1789 if (tgt_status->entry
1790 && tgt_status->entry->kind == svn_node_dir)
1792 svn_wc_adm_access_t *dir_access;
1793 SVN_ERR(svn_wc_adm_retrieve(&dir_access, eb->adm_access,
1794 path, pool));
1795 SVN_ERR(get_dir_status
1796 (eb, tgt_status->entry, dir_access, NULL,
1797 eb->ignores, eb->default_depth, eb->get_all,
1798 eb->no_ignore, TRUE,
1799 eb->status_func, eb->status_baton,
1800 eb->cancel_func, eb->cancel_baton, pool));
1802 if (is_sendable_status(tgt_status, eb))
1803 (eb->status_func)(eb->status_baton, path, tgt_status);
1806 else
1808 /* Otherwise, we report on all our children and ourself.
1809 Note that our directory couldn't have been deleted,
1810 because it is the root of the edit drive. */
1811 SVN_ERR(handle_statii(eb, eb->anchor_status->entry, db->path,
1812 db->statii, FALSE, eb->default_depth, pool));
1813 if (is_sendable_status(eb->anchor_status, eb))
1814 (eb->status_func)(eb->status_baton, db->path, eb->anchor_status);
1815 eb->anchor_status = NULL;
1818 return SVN_NO_ERROR;
1823 static svn_error_t *
1824 add_file(const char *path,
1825 void *parent_baton,
1826 const char *copyfrom_path,
1827 svn_revnum_t copyfrom_revision,
1828 apr_pool_t *pool,
1829 void **file_baton)
1831 struct dir_baton *pb = parent_baton;
1832 struct file_baton *new_fb = make_file_baton(pb, path, pool);
1834 /* Mark parent dir as changed */
1835 pb->text_changed = TRUE;
1837 /* Make this file as added. */
1838 new_fb->added = TRUE;
1840 *file_baton = new_fb;
1841 return SVN_NO_ERROR;
1845 static svn_error_t *
1846 open_file(const char *path,
1847 void *parent_baton,
1848 svn_revnum_t base_revision,
1849 apr_pool_t *pool,
1850 void **file_baton)
1852 struct dir_baton *pb = parent_baton;
1853 struct file_baton *new_fb = make_file_baton(pb, path, pool);
1855 *file_baton = new_fb;
1856 return SVN_NO_ERROR;
1860 static svn_error_t *
1861 apply_textdelta(void *file_baton,
1862 const char *base_checksum,
1863 apr_pool_t *pool,
1864 svn_txdelta_window_handler_t *handler,
1865 void **handler_baton)
1867 struct file_baton *fb = file_baton;
1869 /* Mark file as having textual mods. */
1870 fb->text_changed = TRUE;
1872 /* Send back a NULL window handler -- we don't need the actual diffs. */
1873 *handler_baton = NULL;
1874 *handler = svn_delta_noop_window_handler;
1876 return SVN_NO_ERROR;
1880 static svn_error_t *
1881 change_file_prop(void *file_baton,
1882 const char *name,
1883 const svn_string_t *value,
1884 apr_pool_t *pool)
1886 struct file_baton *fb = file_baton;
1887 if (svn_wc_is_normal_prop(name))
1888 fb->prop_changed = TRUE;
1890 /* Note any changes to the repository. */
1891 if (value != NULL)
1893 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
1894 fb->ood_last_cmt_rev = SVN_STR_TO_REV(value->data);
1895 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
1896 fb->ood_last_cmt_author = apr_pstrdup(fb->dir_baton->pool,
1897 value->data);
1898 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
1900 apr_time_t tm;
1901 SVN_ERR(svn_time_from_cstring(&tm, value->data,
1902 fb->dir_baton->pool));
1903 fb->ood_last_cmt_date = tm;
1907 return SVN_NO_ERROR;
1911 static svn_error_t *
1912 close_file(void *file_baton,
1913 const char *text_checksum, /* ignored, as we receive no data */
1914 apr_pool_t *pool)
1916 struct file_baton *fb = file_baton;
1917 enum svn_wc_status_kind repos_text_status;
1918 enum svn_wc_status_kind repos_prop_status;
1919 svn_lock_t *repos_lock = NULL;
1921 /* If nothing has changed, return. */
1922 if (! (fb->added || fb->prop_changed || fb->text_changed))
1923 return SVN_NO_ERROR;
1925 /* If this is a new file, add it to the statushash. */
1926 if (fb->added)
1928 const char *url;
1929 repos_text_status = svn_wc_status_added;
1930 repos_prop_status = fb->prop_changed ? svn_wc_status_added : 0;
1932 if (fb->edit_baton->repos_locks)
1934 url = find_dir_url(fb->dir_baton, pool);
1935 if (url)
1937 url = svn_path_url_add_component(url, fb->name, pool);
1938 repos_lock = apr_hash_get
1939 (fb->edit_baton->repos_locks,
1940 svn_path_uri_decode(url +
1941 strlen(fb->edit_baton->repos_root),
1942 pool), APR_HASH_KEY_STRING);
1946 else
1948 repos_text_status = fb->text_changed ? svn_wc_status_modified : 0;
1949 repos_prop_status = fb->prop_changed ? svn_wc_status_modified : 0;
1952 SVN_ERR(tweak_statushash(fb, NULL, FALSE,
1953 fb->edit_baton->adm_access,
1954 fb->path, FALSE,
1955 repos_text_status,
1956 repos_prop_status, SVN_INVALID_REVNUM,
1957 repos_lock));
1959 return SVN_NO_ERROR;
1963 static svn_error_t *
1964 close_edit(void *edit_baton,
1965 apr_pool_t *pool)
1967 struct edit_baton *eb = edit_baton;
1968 apr_array_header_t *ignores = eb->ignores;
1969 svn_error_t *err = NULL;
1971 /* If we get here and the root was not opened as part of the edit,
1972 we need to transmit statuses for everything. Otherwise, we
1973 should be done. */
1974 if (eb->root_opened)
1975 goto cleanup;
1977 /* If we have a target, that's the thing we're sending, otherwise
1978 we're sending the anchor. */
1980 if (*eb->target)
1982 svn_node_kind_t kind;
1983 const char *full_path = svn_path_join(eb->anchor, eb->target, pool);
1985 err = svn_io_check_path(full_path, &kind, pool);
1986 if (err) goto cleanup;
1988 if (kind == svn_node_dir)
1990 svn_wc_adm_access_t *tgt_access;
1991 const svn_wc_entry_t *tgt_entry;
1993 err = svn_wc_entry(&tgt_entry, full_path, eb->adm_access,
1994 FALSE, pool);
1995 if (err) goto cleanup;
1997 if (! tgt_entry)
1999 err = get_dir_status(eb, NULL, eb->adm_access, eb->target,
2000 ignores, svn_depth_empty, eb->get_all,
2001 TRUE, TRUE,
2002 eb->status_func, eb->status_baton,
2003 eb->cancel_func, eb->cancel_baton,
2004 pool);
2005 if (err) goto cleanup;
2007 else
2009 err = svn_wc_adm_retrieve(&tgt_access, eb->adm_access,
2010 full_path, pool);
2011 if (err) goto cleanup;
2013 err = get_dir_status(eb, NULL, tgt_access, NULL, ignores,
2014 eb->default_depth, eb->get_all,
2015 eb->no_ignore, FALSE,
2016 eb->status_func, eb->status_baton,
2017 eb->cancel_func, eb->cancel_baton,
2018 pool);
2019 if (err) goto cleanup;
2022 else
2024 err = get_dir_status(eb, NULL, eb->adm_access, eb->target,
2025 ignores, svn_depth_empty, eb->get_all,
2026 TRUE, TRUE, eb->status_func, eb->status_baton,
2027 eb->cancel_func, eb->cancel_baton, pool);
2028 if (err) goto cleanup;
2031 else
2033 err = get_dir_status(eb, NULL, eb->adm_access, NULL, ignores,
2034 eb->default_depth, eb->get_all, eb->no_ignore,
2035 FALSE, eb->status_func, eb->status_baton,
2036 eb->cancel_func, eb->cancel_baton, pool);
2037 if (err) goto cleanup;
2040 cleanup:
2041 /* Let's make sure that we didn't harvest any traversal info for the
2042 anchor if we had a target. */
2043 if (eb->traversal_info && *eb->target)
2045 apr_hash_set(eb->traversal_info->externals_old,
2046 eb->anchor, APR_HASH_KEY_STRING, NULL);
2047 apr_hash_set(eb->traversal_info->externals_new,
2048 eb->anchor, APR_HASH_KEY_STRING, NULL);
2049 apr_hash_set(eb->traversal_info->depths,
2050 eb->anchor, APR_HASH_KEY_STRING, NULL);
2053 return err;
2058 /*** Public API ***/
2060 svn_error_t *
2061 svn_wc_get_status_editor3(const svn_delta_editor_t **editor,
2062 void **edit_baton,
2063 void **set_locks_baton,
2064 svn_revnum_t *edit_revision,
2065 svn_wc_adm_access_t *anchor,
2066 const char *target,
2067 svn_depth_t depth,
2068 svn_boolean_t get_all,
2069 svn_boolean_t no_ignore,
2070 apr_array_header_t *ignore_patterns,
2071 svn_wc_status_func2_t status_func,
2072 void *status_baton,
2073 svn_cancel_func_t cancel_func,
2074 void *cancel_baton,
2075 svn_wc_traversal_info_t *traversal_info,
2076 apr_pool_t *pool)
2078 struct edit_baton *eb;
2079 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
2081 /* Construct an edit baton. */
2082 eb = apr_palloc(pool, sizeof(*eb));
2083 eb->default_depth = depth;
2084 eb->target_revision = edit_revision;
2085 eb->adm_access = anchor;
2086 eb->get_all = get_all;
2087 eb->no_ignore = no_ignore;
2088 eb->status_func = status_func;
2089 eb->status_baton = status_baton;
2090 eb->cancel_func = cancel_func;
2091 eb->cancel_baton = cancel_baton;
2092 eb->traversal_info = traversal_info;
2093 eb->externals = apr_hash_make(pool);
2094 eb->anchor = svn_wc_adm_access_path(anchor);
2095 eb->target = target;
2096 eb->root_opened = FALSE;
2097 eb->repos_locks = NULL;
2098 eb->repos_root = NULL;
2100 /* Use the caller-provided ignore patterns if provided; the build-time
2101 configured defaults otherwise. */
2102 if (ignore_patterns)
2104 eb->ignores = ignore_patterns;
2106 else
2108 eb->ignores = apr_array_make(pool, 16, sizeof(const char *));
2109 svn_cstring_split_append(eb->ignores, SVN_CONFIG_DEFAULT_GLOBAL_IGNORES,
2110 "\n\r\t\v ", FALSE, pool);
2113 /* The edit baton's status structure maps to PATH, and the editor
2114 have to be aware of whether that is the anchor or the target. */
2115 SVN_ERR(svn_wc_status2(&(eb->anchor_status), eb->anchor, anchor, pool));
2117 /* Construct an editor. */
2118 tree_editor->set_target_revision = set_target_revision;
2119 tree_editor->open_root = open_root;
2120 tree_editor->delete_entry = delete_entry;
2121 tree_editor->add_directory = add_directory;
2122 tree_editor->open_directory = open_directory;
2123 tree_editor->change_dir_prop = change_dir_prop;
2124 tree_editor->close_directory = close_directory;
2125 tree_editor->add_file = add_file;
2126 tree_editor->open_file = open_file;
2127 tree_editor->apply_textdelta = apply_textdelta;
2128 tree_editor->change_file_prop = change_file_prop;
2129 tree_editor->close_file = close_file;
2130 tree_editor->close_edit = close_edit;
2132 /* Conjoin a cancellation editor with our status editor. */
2133 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2134 tree_editor, eb, editor,
2135 edit_baton, pool));
2137 if (set_locks_baton)
2138 *set_locks_baton = eb;
2140 return SVN_NO_ERROR;
2144 svn_error_t *
2145 svn_wc_get_status_editor2(const svn_delta_editor_t **editor,
2146 void **edit_baton,
2147 void **set_locks_baton,
2148 svn_revnum_t *edit_revision,
2149 svn_wc_adm_access_t *anchor,
2150 const char *target,
2151 apr_hash_t *config,
2152 svn_boolean_t recurse,
2153 svn_boolean_t get_all,
2154 svn_boolean_t no_ignore,
2155 svn_wc_status_func2_t status_func,
2156 void *status_baton,
2157 svn_cancel_func_t cancel_func,
2158 void *cancel_baton,
2159 svn_wc_traversal_info_t *traversal_info,
2160 apr_pool_t *pool)
2162 apr_array_header_t *ignores;
2163 SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
2164 return svn_wc_get_status_editor3(editor,
2165 edit_baton,
2166 set_locks_baton,
2167 edit_revision,
2168 anchor,
2169 target,
2170 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
2171 get_all,
2172 no_ignore,
2173 ignores,
2174 status_func,
2175 status_baton,
2176 cancel_func,
2177 cancel_baton,
2178 traversal_info,
2179 pool);
2183 /* Helpers for deprecated svn_wc_status_editor(), of type
2184 svn_wc_status_func2_t. */
2185 struct old_status_func_cb_baton
2187 svn_wc_status_func_t original_func;
2188 void *original_baton;
2191 static void old_status_func_cb(void *baton,
2192 const char *path,
2193 svn_wc_status2_t *status)
2195 struct old_status_func_cb_baton *b = baton;
2196 svn_wc_status_t *stat = (svn_wc_status_t *) status;
2198 b->original_func(b->original_baton, path, stat);
2201 svn_error_t *
2202 svn_wc_get_status_editor(const svn_delta_editor_t **editor,
2203 void **edit_baton,
2204 svn_revnum_t *edit_revision,
2205 svn_wc_adm_access_t *anchor,
2206 const char *target,
2207 apr_hash_t *config,
2208 svn_boolean_t recurse,
2209 svn_boolean_t get_all,
2210 svn_boolean_t no_ignore,
2211 svn_wc_status_func_t status_func,
2212 void *status_baton,
2213 svn_cancel_func_t cancel_func,
2214 void *cancel_baton,
2215 svn_wc_traversal_info_t *traversal_info,
2216 apr_pool_t *pool)
2218 struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b));
2219 apr_array_header_t *ignores;
2220 b->original_func = status_func;
2221 b->original_baton = status_baton;
2222 SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
2223 return svn_wc_get_status_editor3(editor, edit_baton, NULL, edit_revision,
2224 anchor, target,
2225 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
2226 get_all, no_ignore, ignores,
2227 old_status_func_cb, b,
2228 cancel_func, cancel_baton,
2229 traversal_info, pool);
2233 svn_error_t *
2234 svn_wc_status_set_repos_locks(void *edit_baton,
2235 apr_hash_t *locks,
2236 const char *repos_root,
2237 apr_pool_t *pool)
2239 struct edit_baton *eb = edit_baton;
2241 eb->repos_locks = locks;
2242 eb->repos_root = apr_pstrdup(pool, repos_root);
2244 return SVN_NO_ERROR;
2248 svn_error_t *
2249 svn_wc_get_default_ignores(apr_array_header_t **patterns,
2250 apr_hash_t *config,
2251 apr_pool_t *pool)
2253 svn_config_t *cfg = config ? apr_hash_get(config,
2254 SVN_CONFIG_CATEGORY_CONFIG,
2255 APR_HASH_KEY_STRING) : NULL;
2256 const char *val;
2258 /* Check the Subversion run-time configuration for global ignores.
2259 If no configuration value exists, we fall back to our defaults. */
2260 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
2261 SVN_CONFIG_OPTION_GLOBAL_IGNORES,
2262 SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
2263 *patterns = apr_array_make(pool, 16, sizeof(const char *));
2265 /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
2266 svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
2267 return SVN_NO_ERROR;
2271 svn_error_t *
2272 svn_wc_status2(svn_wc_status2_t **status,
2273 const char *path,
2274 svn_wc_adm_access_t *adm_access,
2275 apr_pool_t *pool)
2277 const svn_wc_entry_t *entry = NULL;
2278 const svn_wc_entry_t *parent_entry = NULL;
2280 if (adm_access)
2281 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2283 if (entry && ! svn_path_is_empty(path))
2285 const char *parent_path = svn_path_dirname(path, pool);
2286 svn_wc_adm_access_t *parent_access;
2287 SVN_ERR(svn_wc__adm_retrieve_internal(&parent_access, adm_access,
2288 parent_path, pool));
2289 if (parent_access)
2290 SVN_ERR(svn_wc_entry(&parent_entry, parent_path, parent_access,
2291 FALSE, pool));
2294 SVN_ERR(assemble_status(status, path, adm_access, entry, parent_entry,
2295 svn_node_unknown, FALSE, /* bogus */
2296 TRUE, FALSE, NULL, NULL, pool));
2297 return SVN_NO_ERROR;
2301 svn_error_t *
2302 svn_wc_status(svn_wc_status_t **status,
2303 const char *path,
2304 svn_wc_adm_access_t *adm_access,
2305 apr_pool_t *pool)
2307 svn_wc_status2_t *stat2;
2309 SVN_ERR(svn_wc_status2(&stat2, path, adm_access, pool));
2310 *status = (svn_wc_status_t *) stat2;
2311 return SVN_NO_ERROR;
2316 svn_wc_status2_t *
2317 svn_wc_dup_status2(svn_wc_status2_t *orig_stat,
2318 apr_pool_t *pool)
2320 svn_wc_status2_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2322 /* Shallow copy all members. */
2323 *new_stat = *orig_stat;
2325 /* No go back and dup the deep item. */
2326 if (orig_stat->entry)
2327 new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
2329 if (orig_stat->repos_lock)
2330 new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
2332 if (orig_stat->url)
2333 new_stat->url = apr_pstrdup(pool, orig_stat->url);
2335 if (orig_stat->ood_last_cmt_author)
2336 new_stat->ood_last_cmt_author
2337 = apr_pstrdup(pool, orig_stat->ood_last_cmt_author);
2339 /* Return the new hotness. */
2340 return new_stat;
2344 svn_wc_status_t *
2345 svn_wc_dup_status(svn_wc_status_t *orig_stat,
2346 apr_pool_t *pool)
2348 svn_wc_status_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
2350 /* Shallow copy all members. */
2351 *new_stat = *orig_stat;
2353 /* No go back and dup the deep item. */
2354 if (orig_stat->entry)
2355 new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
2357 /* Return the new hotness. */
2358 return new_stat;
2361 svn_error_t *
2362 svn_wc_get_ignores(apr_array_header_t **patterns,
2363 apr_hash_t *config,
2364 svn_wc_adm_access_t *adm_access,
2365 apr_pool_t *pool)
2367 apr_array_header_t *default_ignores;
2369 SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, pool));
2370 SVN_ERR(collect_ignore_patterns(patterns, default_ignores, adm_access,
2371 pool));
2373 return SVN_NO_ERROR;