2 * lock.c: routines for locking working copy subdirectories.
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 <apr_pools.h>
24 #include "svn_pools.h"
26 #include "svn_sorts.h"
27 #include "svn_types.h"
30 #include "adm_files.h"
32 #include "questions.h"
37 #include "svn_private_config.h"
38 #include "private/svn_wc_private.h"
42 struct svn_wc_adm_access_t
44 /* PATH to directory which contains the administrative area */
47 enum svn_wc__adm_access_type
{
49 /* SVN_WC__ADM_ACCESS_UNLOCKED indicates no lock is held allowing
51 svn_wc__adm_access_unlocked
,
53 /* SVN_WC__ADM_ACCESS_WRITE_LOCK indicates that a write lock is held
54 allowing read-write access */
55 svn_wc__adm_access_write_lock
,
57 /* SVN_WC__ADM_ACCESS_CLOSED indicates that the baton has been
59 svn_wc__adm_access_closed
63 /* LOCK_EXISTS is set TRUE when the write lock exists */
64 svn_boolean_t lock_exists
;
66 /* SET_OWNER is TRUE if SET is allocated from this access baton */
67 svn_boolean_t set_owner
;
69 /* The working copy format version number for the directory */
72 /* SET is a hash of svn_wc_adm_access_t* keyed on char* representing the
73 path to directories that are open. */
76 /* ENTRIES is the cached entries for PATH, without those in state
77 deleted. ENTRIES_HIDDEN is the cached entries including those in
78 state deleted or state absent. Either may be NULL. */
80 apr_hash_t
*entries_hidden
;
82 /* A hash mapping const char * entry names to hashes of wcprops.
83 These hashes map const char * names to svn_string_t * values.
84 NULL of the wcprops hasn't been read into memory.
85 ### Since there are typically just one or two wcprops per entry,
86 ### we could use a more compact way of storing them. */
89 /* POOL is used to allocate cached items, they need to persist for the
90 lifetime of this access baton */
95 /* This is a placeholder used in the set hash to represent missing
96 directories. Only its address is important, it contains no useful
98 static svn_wc_adm_access_t missing
;
102 do_close(svn_wc_adm_access_t
*adm_access
, svn_boolean_t preserve_lock
,
103 svn_boolean_t recurse
);
106 /* Defining this conditional will result in a client that will refuse to
107 upgrade working copies. This can be useful if you want to avoid
108 problems caused by accidentally running a development version of SVN
109 on a working copy that you typically use with an older version. */
110 #ifndef SVN_DISABLE_WC_UPGRADE
112 /* Write, to LOG_ACCUM, log entries to convert an old WC that did not have
113 propcaching into a WC that uses propcaching. Do this conversion for
114 the directory of ADM_ACCESS and its file children. Use POOL for
115 temporary allocations. */
117 introduce_propcaching(svn_stringbuf_t
*log_accum
,
118 svn_wc_adm_access_t
*adm_access
,
122 apr_hash_index_t
*hi
;
123 apr_pool_t
*subpool
= svn_pool_create(pool
);
124 const char *adm_path
= svn_wc_adm_access_path(adm_access
);
126 SVN_ERR(svn_wc_entries_read(&entries
, adm_access
, FALSE
, pool
));
128 /* Reinstall the properties for each file and this dir; subdirs are handled
129 when they're opened. */
130 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
133 const svn_wc_entry_t
*entry
;
134 const char *entrypath
;
135 svn_wc_entry_t tmpentry
;
136 apr_hash_t
*base_props
, *props
;
138 apr_hash_this(hi
, NULL
, NULL
, &val
);
141 if (entry
->kind
!= svn_node_file
142 && strcmp(entry
->name
, SVN_WC_ENTRY_THIS_DIR
) != 0)
145 svn_pool_clear(subpool
);
147 entrypath
= svn_path_join(adm_path
, entry
->name
, subpool
);
148 SVN_ERR(svn_wc__load_props(&base_props
, &props
, NULL
, adm_access
,
149 entrypath
, subpool
));
150 SVN_ERR(svn_wc__install_props(&log_accum
, adm_access
, entrypath
,
151 base_props
, props
, TRUE
, subpool
));
152 /* Make sure we get rid of that prop-time field.
153 It only wastes space in new WCs. */
154 tmpentry
.prop_time
= 0;
155 SVN_ERR(svn_wc__loggy_entry_modify
156 (&log_accum
, adm_access
,
159 SVN_WC__ENTRY_MODIFY_PROP_TIME
,
166 /* Write, to LOG_ACCUM, commands to convert a WC that has wcprops in individual
167 files to use one wcprops file per directory.
168 Do this for ADM_ACCESS and its file children, using POOL for temporary
171 convert_wcprops(svn_stringbuf_t
*log_accum
,
172 svn_wc_adm_access_t
*adm_access
,
176 apr_hash_index_t
*hi
;
177 apr_pool_t
*subpool
= svn_pool_create(pool
);
179 SVN_ERR(svn_wc_entries_read(&entries
, adm_access
, FALSE
, pool
));
181 /* Walk over the entries, adding a modify-wcprop command for each wcprop.
182 Note that the modifications happen in memory and are just written once
183 at the end of the log execution, so this isn't as inefficient as it
185 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
188 const svn_wc_entry_t
*entry
;
190 apr_hash_index_t
*hj
;
191 const char *full_path
;
193 apr_hash_this(hi
, NULL
, NULL
, &val
);
196 full_path
= svn_path_join(svn_wc_adm_access_path(adm_access
),
199 if (entry
->kind
!= svn_node_file
200 && strcmp(entry
->name
, SVN_WC_ENTRY_THIS_DIR
) != 0)
203 svn_pool_clear(subpool
);
205 SVN_ERR(svn_wc__wcprop_list(&wcprops
, entry
->name
, adm_access
, subpool
));
207 /* Create a subsubpool for the inner loop...
208 No, just kidding. There are typically just one or two wcprops
210 for (hj
= apr_hash_first(subpool
, wcprops
); hj
; hj
= apr_hash_next(hj
))
214 const char *propname
;
215 svn_string_t
*propval
;
217 apr_hash_this(hj
, &key2
, NULL
, &val2
);
220 SVN_ERR(svn_wc__loggy_modify_wcprop(&log_accum
, adm_access
,
230 /* Maybe upgrade the working copy directory represented by ADM_ACCESS
231 to the latest 'SVN_WC__VERSION'. ADM_ACCESS must contain a write
232 lock. Use POOL for all temporary allocation.
234 Not all upgrade paths are necessarily supported. For example,
235 upgrading a version 1 working copy results in an error.
237 Sometimes the format file can contain "0" while the administrative
238 directory is being constructed; calling this on a format 0 working
239 copy has no effect and returns no error. */
241 maybe_upgrade_format(svn_wc_adm_access_t
*adm_access
, apr_pool_t
*pool
)
243 SVN_ERR(svn_wc__check_format(adm_access
->wc_format
,
247 /* We can upgrade all formats that are accepted by
248 svn_wc__check_format. */
249 if (adm_access
->wc_format
!= SVN_WC__VERSION
)
251 svn_boolean_t cleanup_required
;
252 svn_stringbuf_t
*log_accum
= svn_stringbuf_create("", pool
);
254 /* Don't try to mess with the WC if there are old log files left. */
255 SVN_ERR(svn_wc__adm_is_cleanup_required(&cleanup_required
,
257 if (cleanup_required
)
260 /* First, loggily upgrade the format file. */
261 SVN_ERR(svn_wc__loggy_upgrade_format(&log_accum
, adm_access
,
262 SVN_WC__VERSION
, pool
));
264 /* Possibly convert an old WC that doesn't use propcaching. */
265 if (adm_access
->wc_format
<= SVN_WC__NO_PROPCACHING_VERSION
)
266 SVN_ERR(introduce_propcaching(log_accum
, adm_access
, pool
));
268 /* If the WC uses one file per entry for wcprops, give back some inodes
270 if (adm_access
->wc_format
<= SVN_WC__WCPROPS_MANY_FILES_VERSION
)
271 SVN_ERR(convert_wcprops(log_accum
, adm_access
, pool
));
273 SVN_ERR(svn_wc__write_log(adm_access
, 0, log_accum
, pool
));
275 if (adm_access
->wc_format
<= SVN_WC__WCPROPS_MANY_FILES_VERSION
)
277 const char *access_path
= svn_wc_adm_access_path(adm_access
);
278 /* Remove wcprops directory, dir-props, README.txt and empty-file
280 We just silently ignore errors, because keeping these files is
283 svn_error_clear(svn_io_remove_dir2
284 (svn_wc__adm_path(access_path
, FALSE
, pool
, SVN_WC__ADM_WCPROPS
,
285 NULL
), FALSE
, NULL
, NULL
, pool
));
286 svn_error_clear(svn_io_remove_file
287 (svn_wc__adm_path(access_path
, FALSE
, pool
,
288 SVN_WC__ADM_DIR_WCPROPS
, NULL
), pool
));
289 svn_error_clear(svn_io_remove_file
290 (svn_wc__adm_path(access_path
, FALSE
, pool
,
291 SVN_WC__ADM_EMPTY_FILE
, NULL
), pool
));
292 svn_error_clear(svn_io_remove_file
293 (svn_wc__adm_path(access_path
, FALSE
, pool
,
294 SVN_WC__ADM_README
, NULL
), pool
));
297 SVN_ERR(svn_wc__run_log(adm_access
, NULL
, pool
));
305 /* Alternate version of the above for use when working copy upgrades
306 are disabled. Return an error if the working copy described by
307 ADM_ACCESS is not at the latest 'SVN_WC__VERSION'. Use POOL for all
308 temporary allocation. */
310 maybe_upgrade_format(svn_wc_adm_access_t
*adm_access
, apr_pool_t
*pool
)
312 SVN_ERR(svn_wc__check_format(adm_access
->wc_format
,
316 if (adm_access
->wc_format
!= SVN_WC__VERSION
)
318 return svn_error_createf(SVN_ERR_WC_UNSUPPORTED_FORMAT
, NULL
,
319 "Would upgrade working copy '%s' from old "
320 "format (%d) to current format (%d), "
321 "but automatic upgrade has been disabled",
322 svn_path_local_style(adm_access
->path
, pool
),
323 adm_access
->wc_format
, SVN_WC__VERSION
);
332 /* Create a physical lock file in the admin directory for ADM_ACCESS. Wait
333 up to WAIT_FOR seconds if the lock already exists retrying every
336 Note: most callers of this function determine the wc_format for the
337 lock soon afterwards. We recommend calling maybe_upgrade_format()
338 as soon as you have the wc_format for a lock, since that's a good
339 opportunity to drag old working directories into the modern era. */
341 create_lock(svn_wc_adm_access_t
*adm_access
, int wait_for
, apr_pool_t
*pool
)
347 err
= svn_wc__make_adm_thing(adm_access
, SVN_WC__ADM_LOCK
,
348 svn_node_file
, APR_OS_DEFAULT
, 0, pool
);
351 if (APR_STATUS_IS_EEXIST(err
->apr_err
))
353 svn_error_clear(err
);
357 apr_sleep(apr_time_from_sec(1)); /* micro-seconds */
366 return svn_error_createf(SVN_ERR_WC_LOCKED
, NULL
,
367 _("Working copy '%s' locked"),
368 svn_path_local_style(adm_access
->path
, pool
));
372 /* Remove the physical lock in the admin directory for PATH. It is
373 acceptable for the administrative area to have disappeared, such as when
374 the directory is removed from the working copy. It is an error for the
375 lock to have disappeared if the administrative area still exists. */
377 remove_lock(const char *path
, apr_pool_t
*pool
)
379 svn_error_t
*err
= svn_wc__remove_adm_file(path
, pool
, SVN_WC__ADM_LOCK
,
383 if (svn_wc__adm_path_exists(path
, FALSE
, pool
, NULL
))
385 svn_error_clear(err
);
390 /* An APR pool cleanup handler. This handles access batons that have not
391 been closed when their pool gets destroyed. The physical locks
392 associated with such batons remain in the working copy if they are
393 protecting a log file. */
395 pool_cleanup(void *p
)
397 svn_wc_adm_access_t
*lock
= p
;
398 svn_boolean_t cleanup
;
401 if (lock
->type
== svn_wc__adm_access_closed
)
404 err
= svn_wc__adm_is_cleanup_required(&cleanup
, lock
, lock
->pool
);
406 err
= do_close(lock
, cleanup
, TRUE
);
408 /* ### Is this the correct way to handle the error? */
411 apr_status_t apr_err
= err
->apr_err
;
412 svn_error_clear(err
);
419 /* An APR pool cleanup handler. This is a child handler, it removes the
420 main pool handler. */
422 pool_cleanup_child(void *p
)
424 svn_wc_adm_access_t
*lock
= p
;
425 apr_pool_cleanup_kill(lock
->pool
, lock
, pool_cleanup
);
429 /* Allocate from POOL, intialise and return an access baton. TYPE and PATH
430 are used to initialise the baton. */
431 static svn_wc_adm_access_t
*
432 adm_access_alloc(enum svn_wc__adm_access_type type
,
436 svn_wc_adm_access_t
*lock
= apr_palloc(pool
, sizeof(*lock
));
438 lock
->entries
= NULL
;
439 lock
->entries_hidden
= NULL
;
440 lock
->wcprops
= NULL
;
443 lock
->lock_exists
= FALSE
;
444 lock
->set_owner
= FALSE
;
445 lock
->path
= apr_pstrdup(pool
, path
);
452 adm_ensure_set(svn_wc_adm_access_t
*adm_access
)
454 if (! adm_access
->set
)
456 adm_access
->set_owner
= TRUE
;
457 adm_access
->set
= apr_hash_make(adm_access
->pool
);
458 apr_hash_set(adm_access
->set
, adm_access
->path
, APR_HASH_KEY_STRING
,
464 probe(const char **dir
,
469 svn_node_kind_t kind
;
471 SVN_ERR(svn_io_check_path(path
, &kind
, pool
));
472 if (kind
== svn_node_dir
)
473 SVN_ERR(svn_wc_check_wc(path
, wc_format
, pool
));
477 /* a "version" of 0 means a non-wc directory */
478 if (kind
!= svn_node_dir
|| *wc_format
== 0)
480 /* Passing a path ending in "." or ".." to svn_path_dirname() is
481 probably always a bad idea; certainly it is in this case.
482 Unfortunately, svn_path_dirname()'s current signature can't
483 return an error, so we have to insert the protection in this
484 caller, as making the larger API change would be very
485 destabilizing right now (just before 1.0). See issue #1617. */
486 const char *base_name
= svn_path_basename(path
, pool
);
487 if ((strcmp(base_name
, "..") == 0)
488 || (strcmp(base_name
, ".") == 0))
490 return svn_error_createf
491 (SVN_ERR_WC_BAD_PATH
, NULL
,
492 _("Path '%s' ends in '%s', "
493 "which is unsupported for this operation"),
494 svn_path_local_style(path
, pool
), base_name
);
497 *dir
= svn_path_dirname(path
, pool
);
507 svn_wc__adm_steal_write_lock(svn_wc_adm_access_t
**adm_access
,
508 svn_wc_adm_access_t
*associated
,
513 svn_wc_adm_access_t
*lock
= adm_access_alloc(svn_wc__adm_access_write_lock
,
516 err
= create_lock(lock
, 0, pool
);
519 if (err
->apr_err
== SVN_ERR_WC_LOCKED
)
520 svn_error_clear(err
); /* Steal existing lock */
527 adm_ensure_set(associated
);
528 lock
->set
= associated
->set
;
529 apr_hash_set(lock
->set
, lock
->path
, APR_HASH_KEY_STRING
, lock
);
532 /* We have a write lock. If the working copy has an old
533 format, this is the time to upgrade it. */
534 SVN_ERR(svn_wc_check_wc(path
, &lock
->wc_format
, pool
));
535 SVN_ERR(maybe_upgrade_format(lock
, pool
));
537 lock
->lock_exists
= TRUE
;
542 /* This is essentially the guts of svn_wc_adm_open3, with the additional
543 * parameter UNDER_CONSTRUCTION that gets set TRUE only when locking the
544 * admin directory during initial creation.
546 * If the working copy is already locked, return SVN_ERR_WC_LOCKED; if
547 * it is not a versioned directory, return SVN_ERR_WC_NOT_DIRECTORY.
550 do_open(svn_wc_adm_access_t
**adm_access
,
551 svn_wc_adm_access_t
*associated
,
553 svn_boolean_t write_lock
,
555 svn_boolean_t under_construction
,
556 svn_cancel_func_t cancel_func
,
560 svn_wc_adm_access_t
*lock
;
563 apr_pool_t
*subpool
= svn_pool_create(pool
);
567 adm_ensure_set(associated
);
569 lock
= apr_hash_get(associated
->set
, path
, APR_HASH_KEY_STRING
);
570 if (lock
&& lock
!= &missing
)
571 /* Already locked. The reason we don't return the existing baton
572 here is that the user is supposed to know whether a directory is
573 locked: if it's not locked call svn_wc_adm_open, if it is locked
574 call svn_wc_adm_retrieve. */
575 return svn_error_createf(SVN_ERR_WC_LOCKED
, NULL
,
576 _("Working copy '%s' locked"),
577 svn_path_local_style(path
, pool
));
580 if (! under_construction
)
582 /* By reading the format file we check both that PATH is a directory
583 and that it is a working copy. */
584 /* ### We will read the entries file later. Maybe read the whole
585 file here instead to avoid reopening it. */
586 err
= svn_io_read_version_file(&wc_format
,
587 svn_wc__adm_path(path
, FALSE
, subpool
,
591 /* If the entries file doesn't start with a version number, we're dealing
592 with a pre-format 7 working copy, so we need to get the format from
593 the format file instead. */
594 if (err
&& err
->apr_err
== SVN_ERR_BAD_VERSION_FILE_FORMAT
)
596 svn_error_clear(err
);
597 err
= svn_io_read_version_file(&wc_format
,
598 svn_wc__adm_path(path
, FALSE
, subpool
,
605 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY
, err
,
606 _("'%s' is not a working copy"),
607 svn_path_local_style(path
, pool
));
610 SVN_ERR(svn_wc__check_format(wc_format
,
611 svn_path_local_style(path
, subpool
),
615 /* Need to create a new lock */
618 lock
= adm_access_alloc(svn_wc__adm_access_write_lock
, path
, pool
);
619 SVN_ERR(create_lock(lock
, 0, subpool
));
620 lock
->lock_exists
= TRUE
;
624 lock
= adm_access_alloc(svn_wc__adm_access_unlocked
, path
, pool
);
627 if (! under_construction
)
629 lock
->wc_format
= wc_format
;
631 SVN_ERR(maybe_upgrade_format(lock
, subpool
));
634 if (levels_to_lock
!= 0)
637 apr_hash_index_t
*hi
;
639 /* Reduce levels_to_lock since we are about to recurse */
640 if (levels_to_lock
> 0)
643 SVN_ERR(svn_wc_entries_read(&entries
, lock
, FALSE
, subpool
));
645 /* Use a temporary hash until all children have been opened. */
647 lock
->set
= apr_hash_make(subpool
);
650 for (hi
= apr_hash_first(subpool
, entries
); hi
; hi
= apr_hash_next(hi
))
653 const svn_wc_entry_t
*entry
;
654 svn_wc_adm_access_t
*entry_access
;
655 const char *entry_path
;
657 /* See if someone wants to cancel this operation. */
660 err
= cancel_func(cancel_baton
);
663 /* This closes all the children in temporary hash as well */
664 svn_error_clear(svn_wc_adm_close(lock
));
665 svn_pool_destroy(subpool
);
671 apr_hash_this(hi
, NULL
, NULL
, &val
);
673 if (entry
->kind
!= svn_node_dir
674 || ! strcmp(entry
->name
, SVN_WC_ENTRY_THIS_DIR
))
676 entry_path
= svn_path_join(lock
->path
, entry
->name
, subpool
);
678 /* Don't use the subpool pool here, the lock needs to persist */
679 err
= do_open(&entry_access
, lock
, entry_path
, write_lock
,
680 levels_to_lock
, FALSE
, cancel_func
, cancel_baton
,
684 if (err
->apr_err
!= SVN_ERR_WC_NOT_DIRECTORY
)
686 /* This closes all the children in temporary hash as well */
687 svn_error_clear(svn_wc_adm_close(lock
));
688 svn_pool_destroy(subpool
);
693 /* It's missing or obstructed, so store a placeholder */
694 svn_error_clear(err
);
695 adm_ensure_set(lock
);
696 apr_hash_set(lock
->set
, apr_pstrdup(lock
->pool
, entry_path
),
697 APR_HASH_KEY_STRING
, &missing
);
702 /* ### Perhaps we should verify that the parent and child agree
703 ### about the URL of the child? */
706 /* Switch from temporary hash to permanent hash */
709 for (hi
= apr_hash_first(subpool
, lock
->set
);
711 hi
= apr_hash_next(hi
))
715 const char *entry_path
;
716 svn_wc_adm_access_t
*entry_access
;
718 apr_hash_this(hi
, &key
, NULL
, &val
);
721 apr_hash_set(associated
->set
, entry_path
, APR_HASH_KEY_STRING
,
723 entry_access
->set
= associated
->set
;
725 lock
->set
= associated
->set
;
731 lock
->set
= associated
->set
;
732 apr_hash_set(lock
->set
, lock
->path
, APR_HASH_KEY_STRING
, lock
);
735 /* It's important that the cleanup handler is registered *after* at least
736 one UTF8 conversion has been done, since such a conversion may create
737 the apr_xlate_t object in the pool, and that object must be around
738 when the cleanup handler runs. If the apr_xlate_t cleanup handler
739 were to run *before* the access baton cleanup handler, then the access
740 baton's handler won't work. */
741 apr_pool_cleanup_register(lock
->pool
, lock
, pool_cleanup
,
745 svn_pool_destroy(subpool
);
750 /* To preserve API compatibility with Subversion 1.0.0 */
752 svn_wc_adm_open(svn_wc_adm_access_t
**adm_access
,
753 svn_wc_adm_access_t
*associated
,
755 svn_boolean_t write_lock
,
756 svn_boolean_t tree_lock
,
759 return svn_wc_adm_open3(adm_access
, associated
, path
, write_lock
,
760 (tree_lock
? -1 : 0), NULL
, NULL
, pool
);
764 svn_wc_adm_open2(svn_wc_adm_access_t
**adm_access
,
765 svn_wc_adm_access_t
*associated
,
767 svn_boolean_t write_lock
,
771 return svn_wc_adm_open3(adm_access
, associated
, path
, write_lock
,
772 levels_to_lock
, NULL
, NULL
, pool
);
776 svn_wc_adm_open3(svn_wc_adm_access_t
**adm_access
,
777 svn_wc_adm_access_t
*associated
,
779 svn_boolean_t write_lock
,
781 svn_cancel_func_t cancel_func
,
785 return do_open(adm_access
, associated
, path
, write_lock
, levels_to_lock
,
786 FALSE
, cancel_func
, cancel_baton
, pool
);
790 svn_wc__adm_pre_open(svn_wc_adm_access_t
**adm_access
,
794 return do_open(adm_access
, NULL
, path
, TRUE
, 0, TRUE
, NULL
, NULL
, pool
);
798 /* To preserve API compatibility with Subversion 1.0.0 */
800 svn_wc_adm_probe_open(svn_wc_adm_access_t
**adm_access
,
801 svn_wc_adm_access_t
*associated
,
803 svn_boolean_t write_lock
,
804 svn_boolean_t tree_lock
,
807 return svn_wc_adm_probe_open3(adm_access
, associated
, path
,
808 write_lock
, (tree_lock
? -1 : 0),
814 svn_wc_adm_probe_open2(svn_wc_adm_access_t
**adm_access
,
815 svn_wc_adm_access_t
*associated
,
817 svn_boolean_t write_lock
,
821 return svn_wc_adm_probe_open3(adm_access
, associated
, path
, write_lock
,
822 levels_to_lock
, NULL
, NULL
, pool
);
826 svn_wc_adm_probe_open3(svn_wc_adm_access_t
**adm_access
,
827 svn_wc_adm_access_t
*associated
,
829 svn_boolean_t write_lock
,
831 svn_cancel_func_t cancel_func
,
839 SVN_ERR(probe(&dir
, path
, &wc_format
, pool
));
841 /* If we moved up a directory, then the path is not a directory, or it
842 is not under version control. In either case, the notion of
843 levels_to_lock does not apply to the provided path. Disable it so
844 that we don't end up trying to lock more than we need. */
848 err
= svn_wc_adm_open3(adm_access
, associated
, dir
, write_lock
,
849 levels_to_lock
, cancel_func
, cancel_baton
, pool
);
854 /* If we got an error on the parent dir, that means we failed to
855 get an access baton for the child in the first place. And if
856 the reason we couldn't get the child access baton is that the
857 child is not a versioned directory, then return an error
858 about the child, not the parent. */
859 svn_node_kind_t child_kind
;
860 if ((err2
= svn_io_check_path(path
, &child_kind
, pool
)))
862 svn_error_compose(err
, err2
);
867 && (child_kind
== svn_node_dir
)
868 && (err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
))
870 svn_error_clear(err
);
871 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY
, NULL
,
872 _("'%s' is not a working copy"),
873 svn_path_local_style(path
, pool
));
881 if (wc_format
&& ! (*adm_access
)->wc_format
)
882 (*adm_access
)->wc_format
= wc_format
;
889 svn_wc__adm_retrieve_internal(svn_wc_adm_access_t
**adm_access
,
890 svn_wc_adm_access_t
*associated
,
895 *adm_access
= apr_hash_get(associated
->set
, path
, APR_HASH_KEY_STRING
);
896 else if (! strcmp(associated
->path
, path
))
897 *adm_access
= associated
;
901 if (*adm_access
== &missing
)
908 svn_wc_adm_retrieve(svn_wc_adm_access_t
**adm_access
,
909 svn_wc_adm_access_t
*associated
,
913 SVN_ERR(svn_wc__adm_retrieve_internal(adm_access
, associated
, path
, pool
));
915 /* Most of the code expects access batons to exist, so returning an error
916 generally makes the calling code simpler as it doesn't need to check
921 const svn_wc_entry_t
*subdir_entry
;
922 svn_node_kind_t wckind
;
923 svn_node_kind_t kind
;
926 err
= svn_wc_entry(&subdir_entry
, path
, associated
, TRUE
, pool
);
928 /* If we can't get an entry here, we are in pretty bad shape,
929 and will have to fall back to using just regular old paths to
930 see what's going on. */
933 svn_error_clear(err
);
937 err
= svn_io_check_path(path
, &kind
, pool
);
939 /* If we can't check the path, we can't make a good error
943 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, err
,
944 _("Unable to check path existence for '%s'"),
945 svn_path_local_style(path
, pool
));
950 if (subdir_entry
->kind
== svn_node_dir
951 && kind
== svn_node_file
)
953 const char *err_msg
= apr_psprintf
954 (pool
, _("Expected '%s' to be a directory but found a file"),
955 svn_path_local_style(path
, pool
));
956 return svn_error_create(SVN_ERR_WC_NOT_LOCKED
,
958 (SVN_ERR_WC_NOT_DIRECTORY
, NULL
,
962 else if (subdir_entry
->kind
== svn_node_file
963 && kind
== svn_node_dir
)
965 const char *err_msg
= apr_psprintf
966 (pool
, _("Expected '%s' to be a file but found a directory"),
967 svn_path_local_style(path
, pool
));
968 return svn_error_create(SVN_ERR_WC_NOT_LOCKED
,
969 svn_error_create(SVN_ERR_WC_NOT_FILE
,
975 wcpath
= svn_wc__adm_path(path
, FALSE
, pool
, NULL
);
976 err
= svn_io_check_path(wcpath
, &wckind
, pool
);
978 /* If we can't check the path, we can't make a good error
982 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, err
,
983 _("Unable to check path existence for '%s'"),
984 svn_path_local_style(wcpath
, pool
));
987 if (kind
== svn_node_none
)
989 const char *err_msg
= apr_psprintf(pool
,
990 _("Directory '%s' is missing"),
991 svn_path_local_style(path
, pool
));
992 return svn_error_create(SVN_ERR_WC_NOT_LOCKED
,
993 svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND
,
998 else if (kind
== svn_node_dir
&& wckind
== svn_node_none
)
999 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, NULL
,
1000 _("Directory '%s' containing working copy admin area is missing"),
1001 svn_path_local_style(wcpath
, pool
));
1003 else if (kind
== svn_node_dir
&& wckind
== svn_node_dir
)
1004 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, NULL
,
1005 _("Unable to lock '%s'"),
1006 svn_path_local_style(path
, pool
));
1008 /* If all else fails, return our useless generic error. */
1009 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, NULL
,
1010 _("Working copy '%s' is not locked"),
1011 svn_path_local_style(path
, pool
));
1014 return SVN_NO_ERROR
;
1019 svn_wc_adm_probe_retrieve(svn_wc_adm_access_t
**adm_access
,
1020 svn_wc_adm_access_t
*associated
,
1025 const svn_wc_entry_t
*entry
;
1029 SVN_ERR(svn_wc_entry(&entry
, path
, associated
, TRUE
, pool
));
1032 /* Not a versioned item, probe it */
1033 SVN_ERR(probe(&dir
, path
, &wc_format
, pool
));
1034 else if (entry
->kind
!= svn_node_dir
)
1035 dir
= svn_path_dirname(path
, pool
);
1039 err
= svn_wc_adm_retrieve(adm_access
, associated
, dir
, pool
);
1040 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
)
1042 /* We'll receive a NOT LOCKED error for various reasons,
1043 including the reason we'll actually want to test for:
1044 The path is a versioned directory, but missing, in which case
1045 we want its parent's adm_access (which holds minimal data
1047 svn_error_clear(err
);
1048 SVN_ERR(probe(&dir
, path
, &wc_format
, pool
));
1049 SVN_ERR(svn_wc_adm_retrieve(adm_access
, associated
, dir
, pool
));
1054 return SVN_NO_ERROR
;
1058 /* To preserve API compatibility with Subversion 1.0.0 */
1060 svn_wc_adm_probe_try(svn_wc_adm_access_t
**adm_access
,
1061 svn_wc_adm_access_t
*associated
,
1063 svn_boolean_t write_lock
,
1064 svn_boolean_t tree_lock
,
1067 return svn_wc_adm_probe_try3(adm_access
, associated
, path
, write_lock
,
1068 (tree_lock
? -1 : 0), NULL
, NULL
, pool
);
1072 svn_wc_adm_probe_try2(svn_wc_adm_access_t
**adm_access
,
1073 svn_wc_adm_access_t
*associated
,
1075 svn_boolean_t write_lock
,
1079 return svn_wc_adm_probe_try3(adm_access
, associated
, path
, write_lock
,
1080 levels_to_lock
, NULL
, NULL
, pool
);
1084 svn_wc_adm_probe_try3(svn_wc_adm_access_t
**adm_access
,
1085 svn_wc_adm_access_t
*associated
,
1087 svn_boolean_t write_lock
,
1089 svn_cancel_func_t cancel_func
,
1095 err
= svn_wc_adm_probe_retrieve(adm_access
, associated
, path
, pool
);
1097 /* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for
1098 path in associated, in which case we want to open an access
1099 baton and add it to associated. */
1100 if (err
&& (err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
))
1102 svn_error_clear(err
);
1103 err
= svn_wc_adm_probe_open3(adm_access
, associated
,
1104 path
, write_lock
, levels_to_lock
,
1105 cancel_func
, cancel_baton
,
1106 svn_wc_adm_access_pool(associated
));
1108 /* If the path is not a versioned directory, we just return a
1109 null access baton with no error. Note that of the errors we
1110 do report, the most important (and probably most likely) is
1111 SVN_ERR_WC_LOCKED. That error would mean that someone else
1112 has this area locked, and we definitely want to bail in that
1114 if (err
&& (err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
))
1116 svn_error_clear(err
);
1125 /* A helper for svn_wc_adm_open_anchor. Add all the access batons in the
1126 T_ACCESS set, including T_ACCESS, to the P_ACCESS set. */
1127 static void join_batons(svn_wc_adm_access_t
*p_access
,
1128 svn_wc_adm_access_t
*t_access
,
1131 apr_hash_index_t
*hi
;
1133 adm_ensure_set(p_access
);
1134 if (! t_access
->set
)
1136 t_access
->set
= p_access
->set
;
1137 apr_hash_set(p_access
->set
, t_access
->path
, APR_HASH_KEY_STRING
,
1142 for (hi
= apr_hash_first(pool
, t_access
->set
); hi
; hi
= apr_hash_next(hi
))
1146 svn_wc_adm_access_t
*adm_access
;
1147 apr_hash_this(hi
, &key
, NULL
, &val
);
1149 if (adm_access
!= &missing
)
1150 adm_access
->set
= p_access
->set
;
1151 apr_hash_set(p_access
->set
, key
, APR_HASH_KEY_STRING
, adm_access
);
1153 t_access
->set_owner
= FALSE
;
1157 svn_wc_adm_open_anchor(svn_wc_adm_access_t
**anchor_access
,
1158 svn_wc_adm_access_t
**target_access
,
1159 const char **target
,
1161 svn_boolean_t write_lock
,
1163 svn_cancel_func_t cancel_func
,
1167 const char *base_name
= svn_path_basename(path
, pool
);
1169 if (svn_path_is_empty(path
)
1170 || svn_dirent_is_root(path
, strlen(path
))
1171 || ! strcmp(base_name
, ".."))
1173 SVN_ERR(do_open(anchor_access
, NULL
, path
, write_lock
, levels_to_lock
,
1174 FALSE
, cancel_func
, cancel_baton
, pool
));
1175 *target_access
= *anchor_access
;
1181 svn_wc_adm_access_t
*p_access
, *t_access
;
1182 const char *parent
= svn_path_dirname(path
, pool
);
1183 svn_error_t
*p_access_err
= SVN_NO_ERROR
;
1185 /* Try to open parent of PATH to setup P_ACCESS */
1186 err
= do_open(&p_access
, NULL
, parent
, write_lock
, 0, FALSE
,
1187 cancel_func
, cancel_baton
, pool
);
1190 if (err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
1192 svn_error_clear(err
);
1195 else if (write_lock
&& (err
->apr_err
== SVN_ERR_WC_LOCKED
1196 || APR_STATUS_IS_EACCES(err
->apr_err
)))
1198 /* If P_ACCESS isn't to be returned then a read-only baton
1199 will do for now, but keep the error in case we need it. */
1200 svn_error_t
*err2
= do_open(&p_access
, NULL
, parent
, FALSE
, 0,
1201 FALSE
, cancel_func
, cancel_baton
,
1205 svn_error_clear(err2
);
1214 /* Try to open PATH to setup T_ACCESS */
1215 err
= do_open(&t_access
, NULL
, path
, write_lock
, levels_to_lock
,
1216 FALSE
, cancel_func
, cancel_baton
, pool
);
1219 if (! p_access
|| err
->apr_err
!= SVN_ERR_WC_NOT_DIRECTORY
)
1222 svn_error_clear(do_close(p_access
, FALSE
, TRUE
));
1223 svn_error_clear(p_access_err
);
1227 svn_error_clear(err
);
1231 /* At this stage might have P_ACCESS, T_ACCESS or both */
1233 /* Check for switched or disjoint P_ACCESS and T_ACCESS */
1234 if (p_access
&& t_access
)
1236 const svn_wc_entry_t
*t_entry
, *p_entry
, *t_entry_in_p
;
1238 err
= svn_wc_entry(&t_entry_in_p
, path
, p_access
, FALSE
, pool
);
1240 err
= svn_wc_entry(&t_entry
, path
, t_access
, FALSE
, pool
);
1242 err
= svn_wc_entry(&p_entry
, parent
, p_access
, FALSE
, pool
);
1245 svn_error_clear(p_access_err
);
1246 svn_error_clear(do_close(p_access
, FALSE
, TRUE
));
1247 svn_error_clear(do_close(t_access
, FALSE
, TRUE
));
1251 /* Disjoint won't have PATH in P_ACCESS, switched will have
1252 incompatible URLs */
1255 (p_entry
->url
&& t_entry
->url
1256 && (strcmp(svn_path_dirname(t_entry
->url
, pool
), p_entry
->url
)
1257 || strcmp(svn_path_uri_encode(base_name
, pool
),
1258 svn_path_basename(t_entry
->url
, pool
)))))
1260 /* Switched or disjoint, so drop P_ACCESS */
1261 err
= do_close(p_access
, FALSE
, TRUE
);
1264 svn_error_clear(p_access_err
);
1265 svn_error_clear(do_close(t_access
, FALSE
, TRUE
));
1276 /* Need P_ACCESS, so the read-only temporary won't do */
1278 svn_error_clear(do_close(t_access
, FALSE
, TRUE
));
1279 svn_error_clear(do_close(p_access
, FALSE
, TRUE
));
1280 return p_access_err
;
1283 join_batons(p_access
, t_access
, pool
);
1285 svn_error_clear(p_access_err
);
1289 const svn_wc_entry_t
*t_entry
;
1290 err
= svn_wc_entry(&t_entry
, path
, p_access
, FALSE
, pool
);
1294 svn_error_clear(do_close(p_access
, FALSE
, TRUE
));
1297 if (t_entry
&& t_entry
->kind
== svn_node_dir
)
1299 adm_ensure_set(p_access
);
1300 apr_hash_set(p_access
->set
, apr_pstrdup(p_access
->pool
, path
),
1301 APR_HASH_KEY_STRING
, &missing
);
1305 *anchor_access
= p_access
? p_access
: t_access
;
1306 *target_access
= t_access
? t_access
: p_access
;
1311 *target
= base_name
;
1314 return SVN_NO_ERROR
;
1318 /* Does the work of closing the access baton ADM_ACCESS. Any physical
1319 locks are removed from the working copy if PRESERVE_LOCK is FALSE, or
1320 are left if PRESERVE_LOCK is TRUE. Any associated access batons that
1321 are direct descendants will also be closed.
1323 ### FIXME: If the set has a "hole", say it contains locks for the
1324 ### directories A, A/B, A/B/C/X but not A/B/C then closing A/B will not
1327 static svn_error_t
*
1328 do_close(svn_wc_adm_access_t
*adm_access
,
1329 svn_boolean_t preserve_lock
,
1330 svn_boolean_t recurse
)
1333 if (adm_access
->type
== svn_wc__adm_access_closed
)
1334 return SVN_NO_ERROR
;
1336 /* Close descendant batons */
1337 if (recurse
&& adm_access
->set
)
1340 apr_array_header_t
*children
1341 = svn_sort__hash(adm_access
->set
, svn_sort_compare_items_as_paths
,
1344 /* Go backwards through the list to close children before their
1346 for (i
= children
->nelts
- 1; i
>= 0; --i
)
1348 svn_sort__item_t
*item
= &APR_ARRAY_IDX(children
, i
,
1350 const char *path
= item
->key
;
1351 svn_wc_adm_access_t
*child
= item
->value
;
1353 if (child
== &missing
)
1355 /* We don't close the missing entry, but get rid of it from
1357 apr_hash_set(adm_access
->set
, path
, APR_HASH_KEY_STRING
, NULL
);
1361 if (! svn_path_is_ancestor(adm_access
->path
, path
)
1362 || strcmp(adm_access
->path
, path
) == 0)
1365 SVN_ERR(do_close(child
, preserve_lock
, FALSE
));
1369 /* Physically unlock if required */
1370 if (adm_access
->type
== svn_wc__adm_access_write_lock
)
1372 if (adm_access
->lock_exists
&& ! preserve_lock
)
1374 SVN_ERR(remove_lock(adm_access
->path
, adm_access
->pool
));
1375 adm_access
->lock_exists
= FALSE
;
1379 /* Reset to prevent further use of the lock. */
1380 adm_access
->type
= svn_wc__adm_access_closed
;
1382 /* Detach from set */
1383 if (adm_access
->set
)
1385 apr_hash_set(adm_access
->set
, adm_access
->path
, APR_HASH_KEY_STRING
,
1388 assert(! adm_access
->set_owner
|| apr_hash_count(adm_access
->set
) == 0);
1391 return SVN_NO_ERROR
;
1395 svn_wc_adm_close(svn_wc_adm_access_t
*adm_access
)
1397 return do_close(adm_access
, FALSE
, TRUE
);
1401 svn_wc_adm_locked(svn_wc_adm_access_t
*adm_access
)
1403 return adm_access
->type
== svn_wc__adm_access_write_lock
;
1407 svn_wc__adm_write_check(svn_wc_adm_access_t
*adm_access
)
1409 if (adm_access
->type
== svn_wc__adm_access_write_lock
)
1411 if (adm_access
->lock_exists
)
1413 /* Check physical lock still exists and hasn't been stolen. This
1414 really is paranoia, I have only ever seen one report of this
1415 triggering (from someone using the 0.25 release) and that was
1416 never reproduced. The check accesses the physical filesystem
1417 so it is expensive, but it only runs when we are going to
1418 modify the admin area. If it ever proves to be a bottleneck
1419 the physical check could be removed, just leaving the logical
1421 svn_boolean_t locked
;
1423 SVN_ERR(svn_wc_locked(&locked
, adm_access
->path
, adm_access
->pool
));
1425 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, NULL
,
1426 _("Write-lock stolen in '%s'"),
1427 svn_path_local_style(adm_access
->path
,
1433 return svn_error_createf(SVN_ERR_WC_NOT_LOCKED
, NULL
,
1434 _("No write-lock in '%s'"),
1435 svn_path_local_style(adm_access
->path
,
1439 return SVN_NO_ERROR
;
1443 svn_wc_locked(svn_boolean_t
*locked
, const char *path
, apr_pool_t
*pool
)
1445 svn_node_kind_t kind
;
1446 const char *lockfile
1447 = svn_wc__adm_path(path
, 0, pool
, SVN_WC__ADM_LOCK
, NULL
);
1449 SVN_ERR(svn_io_check_path(lockfile
, &kind
, pool
));
1450 if (kind
== svn_node_file
)
1452 else if (kind
== svn_node_none
)
1455 return svn_error_createf(SVN_ERR_WC_LOCKED
, NULL
,
1456 _("Lock file '%s' is not a regular file"),
1457 svn_path_local_style(lockfile
, pool
));
1459 return SVN_NO_ERROR
;
1464 svn_wc_adm_access_path(svn_wc_adm_access_t
*adm_access
)
1466 return adm_access
->path
;
1471 svn_wc_adm_access_pool(svn_wc_adm_access_t
*adm_access
)
1473 return adm_access
->pool
;
1478 svn_wc__adm_is_cleanup_required(svn_boolean_t
*cleanup
,
1479 svn_wc_adm_access_t
*adm_access
,
1482 if (adm_access
->type
== svn_wc__adm_access_write_lock
)
1484 svn_node_kind_t kind
;
1485 const char *log_path
1486 = svn_wc__adm_path(svn_wc_adm_access_path(adm_access
),
1487 FALSE
, pool
, SVN_WC__ADM_LOG
, NULL
);
1489 /* The presence of a log file demands cleanup */
1490 SVN_ERR(svn_io_check_path(log_path
, &kind
, pool
));
1491 *cleanup
= (kind
== svn_node_file
);
1496 return SVN_NO_ERROR
;
1499 /* Ensure that the cache for the pruned hash (no deleted entries) in
1500 ADM_ACCESS is valid if the full hash is cached. POOL is used for
1501 local, short term, memory allocation.
1503 ### Should this sort of processing be in entries.c? */
1505 prune_deleted(svn_wc_adm_access_t
*adm_access
,
1508 if (! adm_access
->entries
&& adm_access
->entries_hidden
)
1510 apr_hash_index_t
*hi
;
1512 /* I think it will be common for there to be no deleted entries, so
1513 it is worth checking for that case as we can optimise it. */
1514 for (hi
= apr_hash_first(pool
, adm_access
->entries_hidden
);
1516 hi
= apr_hash_next(hi
))
1519 const svn_wc_entry_t
*entry
;
1520 apr_hash_this(hi
, NULL
, NULL
, &val
);
1523 && (entry
->schedule
!= svn_wc_schedule_add
)
1524 && (entry
->schedule
!= svn_wc_schedule_replace
))
1531 /* There are no deleted entries, so we can use the full hash */
1532 adm_access
->entries
= adm_access
->entries_hidden
;
1536 /* Construct pruned hash without deleted entries */
1537 adm_access
->entries
= apr_hash_make(adm_access
->pool
);
1538 for (hi
= apr_hash_first(pool
, adm_access
->entries_hidden
);
1540 hi
= apr_hash_next(hi
))
1544 const svn_wc_entry_t
*entry
;
1546 apr_hash_this(hi
, &key
, NULL
, &val
);
1548 if (((entry
->deleted
== FALSE
) && (entry
->absent
== FALSE
))
1549 || (entry
->schedule
== svn_wc_schedule_add
)
1550 || (entry
->schedule
== svn_wc_schedule_replace
))
1552 apr_hash_set(adm_access
->entries
, key
,
1553 APR_HASH_KEY_STRING
, entry
);
1561 svn_wc__adm_access_set_entries(svn_wc_adm_access_t
*adm_access
,
1562 svn_boolean_t show_hidden
,
1563 apr_hash_t
*entries
)
1566 adm_access
->entries_hidden
= entries
;
1568 adm_access
->entries
= entries
;
1573 svn_wc__adm_access_entries(svn_wc_adm_access_t
*adm_access
,
1574 svn_boolean_t show_hidden
,
1579 prune_deleted(adm_access
, pool
);
1580 return adm_access
->entries
;
1583 return adm_access
->entries_hidden
;
1587 svn_wc__adm_access_set_wcprops(svn_wc_adm_access_t
*adm_access
,
1588 apr_hash_t
*wcprops
)
1590 adm_access
->wcprops
= wcprops
;
1594 svn_wc__adm_access_wcprops(svn_wc_adm_access_t
*adm_access
)
1596 return adm_access
->wcprops
;
1601 svn_wc__adm_wc_format(svn_wc_adm_access_t
*adm_access
)
1603 return adm_access
->wc_format
;
1607 svn_wc__adm_set_wc_format(svn_wc_adm_access_t
*adm_access
,
1610 adm_access
->wc_format
= format
;
1615 svn_wc__adm_missing(svn_wc_adm_access_t
*adm_access
,
1619 && apr_hash_get(adm_access
->set
, path
, APR_HASH_KEY_STRING
) == &missing
)
1626 /* Extend the scope of the svn_adm_access_t * passed in as WALK_BATON
1627 for its entire WC tree. An implementation of
1628 svn_wc_entry_callbacks2_t's found_entry() API. */
1629 static svn_error_t
*
1630 extend_lock_found_entry(const char *path
,
1631 const svn_wc_entry_t
*entry
,
1635 /* If PATH is a directory, and it's not already locked, lock it all
1636 the way down to its leaf nodes. */
1637 if (entry
->kind
== svn_node_dir
&&
1638 strcmp(entry
->name
, SVN_WC_ENTRY_THIS_DIR
) != 0)
1640 svn_wc_adm_access_t
*anchor_access
= walk_baton
, *adm_access
;
1641 svn_boolean_t write_lock
=
1642 (anchor_access
->type
== svn_wc__adm_access_write_lock
);
1643 svn_error_t
*err
= svn_wc_adm_probe_try3(&adm_access
, anchor_access
, path
,
1644 write_lock
, -1, NULL
, NULL
, pool
);
1647 if (err
->apr_err
== SVN_ERR_WC_LOCKED
)
1648 /* Good! The directory is *already* locked... */
1649 svn_error_clear(err
);
1654 return SVN_NO_ERROR
;
1658 /* WC entry walker callbacks for svn_wc__adm_extend_lock_to_tree(). */
1659 static svn_wc_entry_callbacks2_t extend_lock_walker
=
1661 extend_lock_found_entry
,
1662 svn_wc__walker_default_error_handler
1667 svn_wc__adm_extend_lock_to_tree(svn_wc_adm_access_t
*adm_access
,
1670 return svn_wc_walk_entries3(adm_access
->path
, adm_access
,
1671 &extend_lock_walker
, adm_access
,
1672 svn_depth_infinity
, FALSE
, NULL
, NULL
, pool
);