Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / lock.c
blobe251a747ea9033f1d7b28aa79dea9b8b45e0980c
1 /*
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 * ====================================================================
19 #include <assert.h>
21 #include <apr_pools.h>
22 #include <apr_time.h>
24 #include "svn_pools.h"
25 #include "svn_path.h"
26 #include "svn_sorts.h"
27 #include "svn_types.h"
29 #include "wc.h"
30 #include "adm_files.h"
31 #include "lock.h"
32 #include "questions.h"
33 #include "props.h"
34 #include "log.h"
35 #include "entries.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 */
45 const char *path;
47 enum svn_wc__adm_access_type {
49 /* SVN_WC__ADM_ACCESS_UNLOCKED indicates no lock is held allowing
50 read-only access */
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
58 closed. */
59 svn_wc__adm_access_closed
61 } type;
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 */
70 int wc_format;
72 /* SET is a hash of svn_wc_adm_access_t* keyed on char* representing the
73 path to directories that are open. */
74 apr_hash_t *set;
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. */
79 apr_hash_t *entries;
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. */
87 apr_hash_t *wcprops;
89 /* POOL is used to allocate cached items, they need to persist for the
90 lifetime of this access baton */
91 apr_pool_t *pool;
95 /* This is a placeholder used in the set hash to represent missing
96 directories. Only its address is important, it contains no useful
97 data. */
98 static svn_wc_adm_access_t missing;
101 static svn_error_t *
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. */
116 static svn_error_t *
117 introduce_propcaching(svn_stringbuf_t *log_accum,
118 svn_wc_adm_access_t *adm_access,
119 apr_pool_t *pool)
121 apr_hash_t *entries;
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))
132 void *val;
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);
139 entry = val;
141 if (entry->kind != svn_node_file
142 && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
143 continue;
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,
157 entrypath,
158 &tmpentry,
159 SVN_WC__ENTRY_MODIFY_PROP_TIME,
160 subpool));
163 return SVN_NO_ERROR;
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
169 allocations. */
170 static svn_error_t *
171 convert_wcprops(svn_stringbuf_t *log_accum,
172 svn_wc_adm_access_t *adm_access,
173 apr_pool_t *pool)
175 apr_hash_t *entries;
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
184 might sound. */
185 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
187 void *val;
188 const svn_wc_entry_t *entry;
189 apr_hash_t *wcprops;
190 apr_hash_index_t *hj;
191 const char *full_path;
193 apr_hash_this(hi, NULL, NULL, &val);
194 entry = val;
196 full_path = svn_path_join(svn_wc_adm_access_path(adm_access),
197 entry->name, pool);
199 if (entry->kind != svn_node_file
200 && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
201 continue;
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
209 per entry... */
210 for (hj = apr_hash_first(subpool, wcprops); hj; hj = apr_hash_next(hj))
212 const void *key2;
213 void *val2;
214 const char *propname;
215 svn_string_t *propval;
217 apr_hash_this(hj, &key2, NULL, &val2);
218 propname = key2;
219 propval = val2;
220 SVN_ERR(svn_wc__loggy_modify_wcprop(&log_accum, adm_access,
221 full_path, propname,
222 propval->data,
223 subpool));
227 return SVN_NO_ERROR;
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. */
240 static svn_error_t *
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,
244 adm_access->path,
245 pool));
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,
256 adm_access, pool));
257 if (cleanup_required)
258 return SVN_NO_ERROR;
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
269 to the poor user. */
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
279 files.
280 We just silently ignore errors, because keeping these files is
281 not catastrophic. */
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));
300 return SVN_NO_ERROR;
303 #else
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. */
309 static svn_error_t *
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,
313 adm_access->path,
314 pool));
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);
326 return SVN_NO_ERROR;
329 #endif
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
334 second.
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. */
340 static svn_error_t *
341 create_lock(svn_wc_adm_access_t *adm_access, int wait_for, apr_pool_t *pool)
343 svn_error_t *err;
345 for (;;)
347 err = svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_LOCK,
348 svn_node_file, APR_OS_DEFAULT, 0, pool);
349 if (err)
351 if (APR_STATUS_IS_EEXIST(err->apr_err))
353 svn_error_clear(err);
354 if (wait_for <= 0)
355 break;
356 wait_for--;
357 apr_sleep(apr_time_from_sec(1)); /* micro-seconds */
359 else
360 return err;
362 else
363 return SVN_NO_ERROR;
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. */
376 static svn_error_t *
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,
380 NULL);
381 if (err)
383 if (svn_wc__adm_path_exists(path, FALSE, pool, NULL))
384 return err;
385 svn_error_clear(err);
387 return SVN_NO_ERROR;
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. */
394 static apr_status_t
395 pool_cleanup(void *p)
397 svn_wc_adm_access_t *lock = p;
398 svn_boolean_t cleanup;
399 svn_error_t *err;
401 if (lock->type == svn_wc__adm_access_closed)
402 return SVN_NO_ERROR;
404 err = svn_wc__adm_is_cleanup_required(&cleanup, lock, lock->pool);
405 if (!err)
406 err = do_close(lock, cleanup, TRUE);
408 /* ### Is this the correct way to handle the error? */
409 if (err)
411 apr_status_t apr_err = err->apr_err;
412 svn_error_clear(err);
413 return apr_err;
415 else
416 return APR_SUCCESS;
419 /* An APR pool cleanup handler. This is a child handler, it removes the
420 main pool handler. */
421 static apr_status_t
422 pool_cleanup_child(void *p)
424 svn_wc_adm_access_t *lock = p;
425 apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup);
426 return APR_SUCCESS;
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,
433 const char *path,
434 apr_pool_t *pool)
436 svn_wc_adm_access_t *lock = apr_palloc(pool, sizeof(*lock));
437 lock->type = type;
438 lock->entries = NULL;
439 lock->entries_hidden = NULL;
440 lock->wcprops = NULL;
441 lock->wc_format = 0;
442 lock->set = NULL;
443 lock->lock_exists = FALSE;
444 lock->set_owner = FALSE;
445 lock->path = apr_pstrdup(pool, path);
446 lock->pool = pool;
448 return lock;
451 static void
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,
459 adm_access);
463 static svn_error_t *
464 probe(const char **dir,
465 const char *path,
466 int *wc_format,
467 apr_pool_t *pool)
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));
474 else
475 *wc_format = 0;
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);
499 else
500 *dir = path;
502 return SVN_NO_ERROR;
506 svn_error_t *
507 svn_wc__adm_steal_write_lock(svn_wc_adm_access_t **adm_access,
508 svn_wc_adm_access_t *associated,
509 const char *path,
510 apr_pool_t *pool)
512 svn_error_t *err;
513 svn_wc_adm_access_t *lock = adm_access_alloc(svn_wc__adm_access_write_lock,
514 path, pool);
516 err = create_lock(lock, 0, pool);
517 if (err)
519 if (err->apr_err == SVN_ERR_WC_LOCKED)
520 svn_error_clear(err); /* Steal existing lock */
521 else
522 return err;
525 if (associated)
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;
538 *adm_access = lock;
539 return SVN_NO_ERROR;
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.
549 static svn_error_t *
550 do_open(svn_wc_adm_access_t **adm_access,
551 svn_wc_adm_access_t *associated,
552 const char *path,
553 svn_boolean_t write_lock,
554 int levels_to_lock,
555 svn_boolean_t under_construction,
556 svn_cancel_func_t cancel_func,
557 void *cancel_baton,
558 apr_pool_t *pool)
560 svn_wc_adm_access_t *lock;
561 int wc_format;
562 svn_error_t *err;
563 apr_pool_t *subpool = svn_pool_create(pool);
565 if (associated)
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,
588 SVN_WC__ADM_ENTRIES,
589 NULL),
590 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,
599 SVN_WC__ADM_FORMAT,
600 NULL),
601 subpool);
603 if (err)
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),
612 subpool));
615 /* Need to create a new lock */
616 if (write_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;
622 else
624 lock = adm_access_alloc(svn_wc__adm_access_unlocked, path, pool);
627 if (! under_construction)
629 lock->wc_format = wc_format;
630 if (write_lock)
631 SVN_ERR(maybe_upgrade_format(lock, subpool));
634 if (levels_to_lock != 0)
636 apr_hash_t *entries;
637 apr_hash_index_t *hi;
639 /* Reduce levels_to_lock since we are about to recurse */
640 if (levels_to_lock > 0)
641 levels_to_lock--;
643 SVN_ERR(svn_wc_entries_read(&entries, lock, FALSE, subpool));
645 /* Use a temporary hash until all children have been opened. */
646 if (associated)
647 lock->set = apr_hash_make(subpool);
649 /* Open the tree */
650 for (hi = apr_hash_first(subpool, entries); hi; hi = apr_hash_next(hi))
652 void *val;
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. */
658 if (cancel_func)
660 err = cancel_func(cancel_baton);
661 if (err)
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);
666 lock->set = NULL;
667 return err;
671 apr_hash_this(hi, NULL, NULL, &val);
672 entry = val;
673 if (entry->kind != svn_node_dir
674 || ! strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
675 continue;
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,
681 lock->pool);
682 if (err)
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);
689 lock->set = NULL;
690 return err;
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);
699 continue;
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 */
707 if (associated)
709 for (hi = apr_hash_first(subpool, lock->set);
711 hi = apr_hash_next(hi))
713 const void *key;
714 void *val;
715 const char *entry_path;
716 svn_wc_adm_access_t *entry_access;
718 apr_hash_this(hi, &key, NULL, &val);
719 entry_path = key;
720 entry_access = val;
721 apr_hash_set(associated->set, entry_path, APR_HASH_KEY_STRING,
722 entry_access);
723 entry_access->set = associated->set;
725 lock->set = associated->set;
729 if (associated)
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,
742 pool_cleanup_child);
743 *adm_access = lock;
745 svn_pool_destroy(subpool);
747 return SVN_NO_ERROR;
750 /* To preserve API compatibility with Subversion 1.0.0 */
751 svn_error_t *
752 svn_wc_adm_open(svn_wc_adm_access_t **adm_access,
753 svn_wc_adm_access_t *associated,
754 const char *path,
755 svn_boolean_t write_lock,
756 svn_boolean_t tree_lock,
757 apr_pool_t *pool)
759 return svn_wc_adm_open3(adm_access, associated, path, write_lock,
760 (tree_lock ? -1 : 0), NULL, NULL, pool);
763 svn_error_t *
764 svn_wc_adm_open2(svn_wc_adm_access_t **adm_access,
765 svn_wc_adm_access_t *associated,
766 const char *path,
767 svn_boolean_t write_lock,
768 int levels_to_lock,
769 apr_pool_t *pool)
771 return svn_wc_adm_open3(adm_access, associated, path, write_lock,
772 levels_to_lock, NULL, NULL, pool);
775 svn_error_t *
776 svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
777 svn_wc_adm_access_t *associated,
778 const char *path,
779 svn_boolean_t write_lock,
780 int levels_to_lock,
781 svn_cancel_func_t cancel_func,
782 void *cancel_baton,
783 apr_pool_t *pool)
785 return do_open(adm_access, associated, path, write_lock, levels_to_lock,
786 FALSE, cancel_func, cancel_baton, pool);
789 svn_error_t *
790 svn_wc__adm_pre_open(svn_wc_adm_access_t **adm_access,
791 const char *path,
792 apr_pool_t *pool)
794 return do_open(adm_access, NULL, path, TRUE, 0, TRUE, NULL, NULL, pool);
798 /* To preserve API compatibility with Subversion 1.0.0 */
799 svn_error_t *
800 svn_wc_adm_probe_open(svn_wc_adm_access_t **adm_access,
801 svn_wc_adm_access_t *associated,
802 const char *path,
803 svn_boolean_t write_lock,
804 svn_boolean_t tree_lock,
805 apr_pool_t *pool)
807 return svn_wc_adm_probe_open3(adm_access, associated, path,
808 write_lock, (tree_lock ? -1 : 0),
809 NULL, NULL, pool);
813 svn_error_t *
814 svn_wc_adm_probe_open2(svn_wc_adm_access_t **adm_access,
815 svn_wc_adm_access_t *associated,
816 const char *path,
817 svn_boolean_t write_lock,
818 int levels_to_lock,
819 apr_pool_t *pool)
821 return svn_wc_adm_probe_open3(adm_access, associated, path, write_lock,
822 levels_to_lock, NULL, NULL, pool);
825 svn_error_t *
826 svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
827 svn_wc_adm_access_t *associated,
828 const char *path,
829 svn_boolean_t write_lock,
830 int levels_to_lock,
831 svn_cancel_func_t cancel_func,
832 void *cancel_baton,
833 apr_pool_t *pool)
835 svn_error_t *err;
836 const char *dir;
837 int wc_format;
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. */
845 if (dir != path)
846 levels_to_lock = 0;
848 err = svn_wc_adm_open3(adm_access, associated, dir, write_lock,
849 levels_to_lock, cancel_func, cancel_baton, pool);
850 if (err)
852 svn_error_t *err2;
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);
863 return err;
866 if ((dir != path)
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));
875 else
877 return err;
881 if (wc_format && ! (*adm_access)->wc_format)
882 (*adm_access)->wc_format = wc_format;
884 return SVN_NO_ERROR;
888 svn_error_t *
889 svn_wc__adm_retrieve_internal(svn_wc_adm_access_t **adm_access,
890 svn_wc_adm_access_t *associated,
891 const char *path,
892 apr_pool_t *pool)
894 if (associated->set)
895 *adm_access = apr_hash_get(associated->set, path, APR_HASH_KEY_STRING);
896 else if (! strcmp(associated->path, path))
897 *adm_access = associated;
898 else
899 *adm_access = NULL;
901 if (*adm_access == &missing)
902 *adm_access = NULL;
904 return SVN_NO_ERROR;
907 svn_error_t *
908 svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
909 svn_wc_adm_access_t *associated,
910 const char *path,
911 apr_pool_t *pool)
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
917 for NULL batons. */
918 if (! *adm_access)
920 const char *wcpath;
921 const svn_wc_entry_t *subdir_entry;
922 svn_node_kind_t wckind;
923 svn_node_kind_t kind;
924 svn_error_t *err;
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. */
931 if (err)
933 svn_error_clear(err);
934 subdir_entry = NULL;
937 err = svn_io_check_path(path, &kind, pool);
939 /* If we can't check the path, we can't make a good error
940 message. */
941 if (err)
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));
948 if (subdir_entry)
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,
957 svn_error_create
958 (SVN_ERR_WC_NOT_DIRECTORY, NULL,
959 err_msg),
960 err_msg);
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,
970 NULL, err_msg),
971 err_msg);
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
979 message. */
980 if (err)
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,
994 NULL, err_msg),
995 err_msg);
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;
1018 svn_error_t *
1019 svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
1020 svn_wc_adm_access_t *associated,
1021 const char *path,
1022 apr_pool_t *pool)
1024 const char *dir;
1025 const svn_wc_entry_t *entry;
1026 int wc_format;
1027 svn_error_t *err;
1029 SVN_ERR(svn_wc_entry(&entry, path, associated, TRUE, pool));
1031 if (! entry)
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);
1036 else
1037 dir = path;
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
1046 on the child) */
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));
1051 else
1052 return err;
1054 return SVN_NO_ERROR;
1058 /* To preserve API compatibility with Subversion 1.0.0 */
1059 svn_error_t *
1060 svn_wc_adm_probe_try(svn_wc_adm_access_t **adm_access,
1061 svn_wc_adm_access_t *associated,
1062 const char *path,
1063 svn_boolean_t write_lock,
1064 svn_boolean_t tree_lock,
1065 apr_pool_t *pool)
1067 return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock,
1068 (tree_lock ? -1 : 0), NULL, NULL, pool);
1071 svn_error_t *
1072 svn_wc_adm_probe_try2(svn_wc_adm_access_t **adm_access,
1073 svn_wc_adm_access_t *associated,
1074 const char *path,
1075 svn_boolean_t write_lock,
1076 int levels_to_lock,
1077 apr_pool_t *pool)
1079 return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock,
1080 levels_to_lock, NULL, NULL, pool);
1083 svn_error_t *
1084 svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
1085 svn_wc_adm_access_t *associated,
1086 const char *path,
1087 svn_boolean_t write_lock,
1088 int levels_to_lock,
1089 svn_cancel_func_t cancel_func,
1090 void *cancel_baton,
1091 apr_pool_t *pool)
1093 svn_error_t *err;
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
1113 case. */
1114 if (err && (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY))
1116 svn_error_clear(err);
1117 *adm_access = NULL;
1118 err = NULL;
1122 return 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,
1129 apr_pool_t *pool)
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,
1138 t_access);
1139 return;
1142 for (hi = apr_hash_first(pool, t_access->set); hi; hi = apr_hash_next(hi))
1144 const void *key;
1145 void *val;
1146 svn_wc_adm_access_t *adm_access;
1147 apr_hash_this(hi, &key, NULL, &val);
1148 adm_access = 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;
1156 svn_error_t *
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,
1160 const char *path,
1161 svn_boolean_t write_lock,
1162 int levels_to_lock,
1163 svn_cancel_func_t cancel_func,
1164 void *cancel_baton,
1165 apr_pool_t *pool)
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;
1176 *target = "";
1178 else
1180 svn_error_t *err;
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);
1188 if (err)
1190 if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
1192 svn_error_clear(err);
1193 p_access = NULL;
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,
1202 pool);
1203 if (err2)
1205 svn_error_clear(err2);
1206 return err;
1208 p_access_err = err;
1210 else
1211 return err;
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);
1217 if (err)
1219 if (! p_access || err->apr_err != SVN_ERR_WC_NOT_DIRECTORY)
1221 if (p_access)
1222 svn_error_clear(do_close(p_access, FALSE, TRUE));
1223 svn_error_clear(p_access_err);
1224 return err;
1227 svn_error_clear(err);
1228 t_access = NULL;
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);
1239 if (! err)
1240 err = svn_wc_entry(&t_entry, path, t_access, FALSE, pool);
1241 if (! err)
1242 err = svn_wc_entry(&p_entry, parent, p_access, FALSE, pool);
1243 if (err)
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));
1248 return err;
1251 /* Disjoint won't have PATH in P_ACCESS, switched will have
1252 incompatible URLs */
1253 if (! t_entry_in_p
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);
1262 if (err)
1264 svn_error_clear(p_access_err);
1265 svn_error_clear(do_close(t_access, FALSE, TRUE));
1266 return err;
1268 p_access = NULL;
1272 if (p_access)
1274 if (p_access_err)
1276 /* Need P_ACCESS, so the read-only temporary won't do */
1277 if (t_access)
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;
1282 else if (t_access)
1283 join_batons(p_access, t_access, pool);
1285 svn_error_clear(p_access_err);
1287 if (! t_access)
1289 const svn_wc_entry_t *t_entry;
1290 err = svn_wc_entry(&t_entry, path, p_access, FALSE, pool);
1291 if (err)
1293 if (p_access)
1294 svn_error_clear(do_close(p_access, FALSE, TRUE));
1295 return err;
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;
1308 if (! p_access)
1309 *target = "";
1310 else
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
1325 ### reach A/B/C/X .
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)
1339 int i;
1340 apr_array_header_t *children
1341 = svn_sort__hash(adm_access->set, svn_sort_compare_items_as_paths,
1342 adm_access->pool);
1344 /* Go backwards through the list to close children before their
1345 parents. */
1346 for (i = children->nelts - 1; i >= 0; --i)
1348 svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
1349 svn_sort__item_t);
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
1356 the set. */
1357 apr_hash_set(adm_access->set, path, APR_HASH_KEY_STRING, NULL);
1358 continue;
1361 if (! svn_path_is_ancestor(adm_access->path, path)
1362 || strcmp(adm_access->path, path) == 0)
1363 continue;
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,
1386 NULL);
1388 assert(! adm_access->set_owner || apr_hash_count(adm_access->set) == 0);
1391 return SVN_NO_ERROR;
1394 svn_error_t *
1395 svn_wc_adm_close(svn_wc_adm_access_t *adm_access)
1397 return do_close(adm_access, FALSE, TRUE);
1400 svn_boolean_t
1401 svn_wc_adm_locked(svn_wc_adm_access_t *adm_access)
1403 return adm_access->type == svn_wc__adm_access_write_lock;
1406 svn_error_t *
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
1420 check. */
1421 svn_boolean_t locked;
1423 SVN_ERR(svn_wc_locked(&locked, adm_access->path, adm_access->pool));
1424 if (! locked)
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,
1428 adm_access->pool));
1431 else
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,
1436 adm_access->pool));
1439 return SVN_NO_ERROR;
1442 svn_error_t *
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)
1451 *locked = TRUE;
1452 else if (kind == svn_node_none)
1453 *locked = FALSE;
1454 else
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;
1463 const char *
1464 svn_wc_adm_access_path(svn_wc_adm_access_t *adm_access)
1466 return adm_access->path;
1470 apr_pool_t *
1471 svn_wc_adm_access_pool(svn_wc_adm_access_t *adm_access)
1473 return adm_access->pool;
1477 svn_error_t *
1478 svn_wc__adm_is_cleanup_required(svn_boolean_t *cleanup,
1479 svn_wc_adm_access_t *adm_access,
1480 apr_pool_t *pool)
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);
1493 else
1494 *cleanup = FALSE;
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? */
1504 static void
1505 prune_deleted(svn_wc_adm_access_t *adm_access,
1506 apr_pool_t *pool)
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))
1518 void *val;
1519 const svn_wc_entry_t *entry;
1520 apr_hash_this(hi, NULL, NULL, &val);
1521 entry = val;
1522 if ((entry->deleted
1523 && (entry->schedule != svn_wc_schedule_add)
1524 && (entry->schedule != svn_wc_schedule_replace))
1525 || entry->absent)
1526 break;
1529 if (! hi)
1531 /* There are no deleted entries, so we can use the full hash */
1532 adm_access->entries = adm_access->entries_hidden;
1533 return;
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))
1542 void *val;
1543 const void *key;
1544 const svn_wc_entry_t *entry;
1546 apr_hash_this(hi, &key, NULL, &val);
1547 entry = 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);
1560 void
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)
1565 if (show_hidden)
1566 adm_access->entries_hidden = entries;
1567 else
1568 adm_access->entries = entries;
1572 apr_hash_t *
1573 svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access,
1574 svn_boolean_t show_hidden,
1575 apr_pool_t *pool)
1577 if (! show_hidden)
1579 prune_deleted(adm_access, pool);
1580 return adm_access->entries;
1582 else
1583 return adm_access->entries_hidden;
1586 void
1587 svn_wc__adm_access_set_wcprops(svn_wc_adm_access_t *adm_access,
1588 apr_hash_t *wcprops)
1590 adm_access->wcprops = wcprops;
1593 apr_hash_t *
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;
1606 void
1607 svn_wc__adm_set_wc_format(svn_wc_adm_access_t *adm_access,
1608 int format)
1610 adm_access->wc_format = format;
1614 svn_boolean_t
1615 svn_wc__adm_missing(svn_wc_adm_access_t *adm_access,
1616 const char *path)
1618 if (adm_access->set
1619 && apr_hash_get(adm_access->set, path, APR_HASH_KEY_STRING) == &missing)
1620 return TRUE;
1622 return FALSE;
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,
1632 void *walk_baton,
1633 apr_pool_t *pool)
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);
1645 if (err)
1647 if (err->apr_err == SVN_ERR_WC_LOCKED)
1648 /* Good! The directory is *already* locked... */
1649 svn_error_clear(err);
1650 else
1651 return 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
1666 svn_error_t *
1667 svn_wc__adm_extend_lock_to_tree(svn_wc_adm_access_t *adm_access,
1668 apr_pool_t *pool)
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);