1251881Speter/* 2251881Speter * locking_commands.c: Implementation of lock and unlock. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include "svn_client.h" 31251881Speter#include "svn_hash.h" 32251881Speter#include "client.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_path.h" 35251881Speter#include "svn_xml.h" 36251881Speter#include "svn_pools.h" 37251881Speter 38251881Speter#include "svn_private_config.h" 39251881Speter#include "private/svn_client_private.h" 40251881Speter#include "private/svn_wc_private.h" 41251881Speter 42251881Speter 43251881Speter/*** Code. ***/ 44251881Speter 45251881Speter/* For use with store_locks_callback, below. */ 46251881Speterstruct lock_baton 47251881Speter{ 48251881Speter const char *base_dir_abspath; 49251881Speter apr_hash_t *urls_to_paths; 50251881Speter svn_client_ctx_t *ctx; 51251881Speter apr_pool_t *pool; 52251881Speter}; 53251881Speter 54251881Speter 55251881Speter/* This callback is called by the ra_layer for each path locked. 56251881Speter * BATON is a 'struct lock_baton *', PATH is the path being locked, 57251881Speter * and LOCK is the lock itself. 58251881Speter * 59251881Speter * If BATON->base_dir_abspath is not null, then this function either 60251881Speter * stores the LOCK on REL_URL or removes any lock tokens from REL_URL 61251881Speter * (depending on whether DO_LOCK is true or false respectively), but 62251881Speter * only if RA_ERR is null, or (in the unlock case) is something other 63251881Speter * than SVN_ERR_FS_LOCK_OWNER_MISMATCH. 64251881Speter * 65251881Speter * Implements svn_ra_lock_callback_t. 66251881Speter */ 67251881Speterstatic svn_error_t * 68251881Speterstore_locks_callback(void *baton, 69251881Speter const char *rel_url, 70251881Speter svn_boolean_t do_lock, 71251881Speter const svn_lock_t *lock, 72251881Speter svn_error_t *ra_err, apr_pool_t *pool) 73251881Speter{ 74251881Speter struct lock_baton *lb = baton; 75251881Speter svn_wc_notify_t *notify; 76251881Speter 77251881Speter /* Create the notify struct first, so we can tweak it below. */ 78251881Speter notify = svn_wc_create_notify(rel_url, 79251881Speter do_lock 80251881Speter ? (ra_err 81251881Speter ? svn_wc_notify_failed_lock 82251881Speter : svn_wc_notify_locked) 83251881Speter : (ra_err 84251881Speter ? svn_wc_notify_failed_unlock 85251881Speter : svn_wc_notify_unlocked), 86251881Speter pool); 87251881Speter notify->lock = lock; 88251881Speter notify->err = ra_err; 89251881Speter 90251881Speter if (lb->base_dir_abspath) 91251881Speter { 92251881Speter char *path = svn_hash_gets(lb->urls_to_paths, rel_url); 93251881Speter const char *local_abspath; 94251881Speter 95251881Speter local_abspath = svn_dirent_join(lb->base_dir_abspath, path, pool); 96251881Speter 97251881Speter /* Notify a valid working copy path */ 98251881Speter notify->path = local_abspath; 99251881Speter notify->path_prefix = lb->base_dir_abspath; 100251881Speter 101251881Speter if (do_lock) 102251881Speter { 103251881Speter if (!ra_err) 104251881Speter { 105251881Speter SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock, 106251881Speter lb->pool)); 107251881Speter notify->lock_state = svn_wc_notify_lock_state_locked; 108251881Speter } 109251881Speter else 110251881Speter notify->lock_state = svn_wc_notify_lock_state_unchanged; 111251881Speter } 112251881Speter else /* unlocking */ 113251881Speter { 114251881Speter /* Remove our wc lock token either a) if we got no error, or b) if 115251881Speter we got any error except for owner mismatch. Note that the only 116251881Speter errors that are handed to this callback will be locking-related 117251881Speter errors. */ 118251881Speter 119251881Speter if (!ra_err || 120251881Speter (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH))) 121251881Speter { 122251881Speter SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath, 123251881Speter lb->pool)); 124251881Speter notify->lock_state = svn_wc_notify_lock_state_unlocked; 125251881Speter } 126251881Speter else 127251881Speter notify->lock_state = svn_wc_notify_lock_state_unchanged; 128251881Speter } 129251881Speter } 130251881Speter else 131251881Speter notify->url = rel_url; /* Notify that path is actually a url */ 132251881Speter 133251881Speter if (lb->ctx->notify_func2) 134251881Speter lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool); 135251881Speter 136251881Speter return SVN_NO_ERROR; 137251881Speter} 138251881Speter 139251881Speter 140251881Speter/* This is a wrapper around svn_uri_condense_targets() and 141251881Speter * svn_dirent_condense_targets() (the choice of which is made based on 142251881Speter * the value of TARGETS_ARE_URIS) which takes care of the 143251881Speter * single-target special case. 144251881Speter * 145251881Speter * Callers are expected to check for an empty *COMMON_PARENT (which 146251881Speter * means, "there was nothing common") for themselves. 147251881Speter */ 148251881Speterstatic svn_error_t * 149251881Spetercondense_targets(const char **common_parent, 150251881Speter apr_array_header_t **target_relpaths, 151251881Speter const apr_array_header_t *targets, 152251881Speter svn_boolean_t targets_are_uris, 153251881Speter svn_boolean_t remove_redundancies, 154251881Speter apr_pool_t *result_pool, 155251881Speter apr_pool_t *scratch_pool) 156251881Speter{ 157251881Speter if (targets_are_uris) 158251881Speter { 159251881Speter SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths, 160251881Speter targets, remove_redundancies, 161251881Speter result_pool, scratch_pool)); 162251881Speter } 163251881Speter else 164251881Speter { 165251881Speter SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths, 166251881Speter targets, remove_redundancies, 167251881Speter result_pool, scratch_pool)); 168251881Speter } 169251881Speter 170251881Speter /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only 171251881Speter had 1 member, so we special case that. */ 172251881Speter if (apr_is_empty_array(*target_relpaths)) 173251881Speter { 174251881Speter const char *base_name; 175251881Speter 176251881Speter if (targets_are_uris) 177251881Speter { 178251881Speter svn_uri_split(common_parent, &base_name, 179251881Speter *common_parent, result_pool); 180251881Speter } 181251881Speter else 182251881Speter { 183251881Speter svn_dirent_split(common_parent, &base_name, 184251881Speter *common_parent, result_pool); 185251881Speter } 186251881Speter APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name; 187251881Speter } 188251881Speter 189251881Speter return SVN_NO_ERROR; 190251881Speter} 191251881Speter 192251881Speter/* Lock info. Used in organize_lock_targets. 193251881Speter ### Maybe return this instead of the ugly hashes? */ 194251881Speterstruct wc_lock_item_t 195251881Speter{ 196251881Speter svn_revnum_t revision; 197251881Speter const char *lock_token; 198251881Speter}; 199251881Speter 200251881Speter/* Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS. 201251881Speter * If TARGETS are local paths, then the entry for each path is examined 202251881Speter * and *COMMON_PARENT is set to the common parent URL for all the 203251881Speter * targets (as opposed to the common local path). 204251881Speter * 205251881Speter * If there is no common parent, either because the targets are a 206251881Speter * mixture of URLs and local paths, or because they simply do not 207251881Speter * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE. 208251881Speter * 209251881Speter * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them. 210251881Speter * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise. 211251881Speter * 212251881Speter * Each key stored in *REL_TARGETS_P is a path relative to 213251881Speter * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is 214251881Speter * true, the value is a pointer to the corresponding base_revision 215251881Speter * (allocated in POOL) for the path, else the value is the lock token 216251881Speter * (or "" if no token found in the wc). 217251881Speter * 218251881Speter * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL. 219251881Speter * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to 220251881Speter * COMMON_PARENT) mapped to the target path for TARGET (relative to 221251881Speter * the common parent WC path). working copy targets that they "belong" to. 222251881Speter * 223251881Speter * If *COMMON_PARENT is a URL, then the values are a pointer to 224251881Speter * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "". 225251881Speter * 226251881Speter * TARGETS may not be empty. 227251881Speter */ 228251881Speterstatic svn_error_t * 229251881Speterorganize_lock_targets(const char **common_parent_url, 230251881Speter const char **base_dir, 231251881Speter apr_hash_t **rel_targets_p, 232251881Speter apr_hash_t **rel_fs_paths_p, 233251881Speter const apr_array_header_t *targets, 234251881Speter svn_boolean_t do_lock, 235251881Speter svn_boolean_t force, 236251881Speter svn_wc_context_t *wc_ctx, 237251881Speter apr_pool_t *result_pool, 238251881Speter apr_pool_t *scratch_pool) 239251881Speter{ 240251881Speter const char *common_url = NULL; 241251881Speter const char *common_dirent = NULL; 242251881Speter apr_hash_t *rel_targets_ret = apr_hash_make(result_pool); 243251881Speter apr_hash_t *rel_fs_paths = NULL; 244251881Speter apr_array_header_t *rel_targets; 245251881Speter apr_hash_t *wc_info = apr_hash_make(scratch_pool); 246251881Speter svn_boolean_t url_mode; 247251881Speter int i; 248251881Speter 249251881Speter SVN_ERR_ASSERT(targets->nelts); 250251881Speter SVN_ERR(svn_client__assert_homogeneous_target_type(targets)); 251251881Speter 252251881Speter url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); 253251881Speter 254251881Speter if (url_mode) 255251881Speter { 256251881Speter svn_revnum_t *invalid_revnum = 257251881Speter apr_palloc(result_pool, sizeof(*invalid_revnum)); 258251881Speter 259251881Speter *invalid_revnum = SVN_INVALID_REVNUM; 260251881Speter 261251881Speter /* Get the common parent URL and a bunch of relpaths, one per target. */ 262251881Speter SVN_ERR(condense_targets(&common_url, &rel_targets, targets, 263251881Speter TRUE, TRUE, result_pool, scratch_pool)); 264251881Speter if (! (common_url && *common_url)) 265251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 266251881Speter _("No common parent found, unable to operate " 267251881Speter "on disjoint arguments")); 268251881Speter 269251881Speter /* Create mapping of the target relpaths to either 270251881Speter SVN_INVALID_REVNUM (if our caller is locking) or to an empty 271251881Speter lock token string (if the caller is unlocking). */ 272251881Speter for (i = 0; i < rel_targets->nelts; i++) 273251881Speter { 274251881Speter svn_hash_sets(rel_targets_ret, 275251881Speter APR_ARRAY_IDX(rel_targets, i, const char *), 276251881Speter do_lock 277251881Speter ? (const void *)invalid_revnum 278251881Speter : (const void *)""); 279251881Speter } 280251881Speter } 281251881Speter else 282251881Speter { 283251881Speter apr_array_header_t *rel_urls, *target_urls; 284251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 285251881Speter 286251881Speter /* Get the common parent dirent and a bunch of relpaths, one per 287251881Speter target. */ 288251881Speter SVN_ERR(condense_targets(&common_dirent, &rel_targets, targets, 289251881Speter FALSE, TRUE, result_pool, scratch_pool)); 290251881Speter if (! (common_dirent && *common_dirent)) 291251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 292251881Speter _("No common parent found, unable to operate " 293251881Speter "on disjoint arguments")); 294251881Speter 295251881Speter /* Get the URL for each target (which also serves to verify that 296251881Speter the dirent targets are sane). */ 297251881Speter target_urls = apr_array_make(scratch_pool, rel_targets->nelts, 298251881Speter sizeof(const char *)); 299251881Speter for (i = 0; i < rel_targets->nelts; i++) 300251881Speter { 301251881Speter const char *rel_target; 302251881Speter const char *repos_relpath; 303251881Speter const char *repos_root_url; 304251881Speter const char *target_url; 305251881Speter struct wc_lock_item_t *wli; 306251881Speter const char *local_abspath; 307251881Speter svn_node_kind_t kind; 308251881Speter 309251881Speter svn_pool_clear(iterpool); 310251881Speter 311251881Speter rel_target = APR_ARRAY_IDX(rel_targets, i, const char *); 312251881Speter local_abspath = svn_dirent_join(common_dirent, rel_target, scratch_pool); 313251881Speter wli = apr_pcalloc(scratch_pool, sizeof(*wli)); 314251881Speter 315251881Speter SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, &repos_relpath, 316251881Speter &repos_root_url, NULL, 317251881Speter &wli->lock_token, 318251881Speter wc_ctx, local_abspath, 319251881Speter FALSE /* ignore_enoent */, 320251881Speter FALSE /* show_hidden */, 321251881Speter result_pool, iterpool)); 322251881Speter 323251881Speter if (kind != svn_node_file) 324251881Speter return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL, 325251881Speter _("The node '%s' is not a file"), 326251881Speter svn_dirent_local_style(local_abspath, 327251881Speter iterpool)); 328251881Speter 329251881Speter svn_hash_sets(wc_info, local_abspath, wli); 330251881Speter 331251881Speter target_url = svn_path_url_add_component2(repos_root_url, 332251881Speter repos_relpath, 333251881Speter scratch_pool); 334251881Speter 335251881Speter APR_ARRAY_PUSH(target_urls, const char *) = target_url; 336251881Speter } 337251881Speter 338251881Speter /* Now that we have a bunch of URLs for our dirent targets, 339251881Speter condense those into a single common parent URL and a bunch of 340251881Speter paths relative to that. */ 341251881Speter SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls, 342251881Speter TRUE, FALSE, result_pool, scratch_pool)); 343251881Speter if (! (common_url && *common_url)) 344251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 345251881Speter _("Unable to lock/unlock across multiple " 346251881Speter "repositories")); 347251881Speter 348251881Speter /* Now we need to create a couple of different hash mappings. */ 349251881Speter rel_fs_paths = apr_hash_make(result_pool); 350251881Speter for (i = 0; i < rel_targets->nelts; i++) 351251881Speter { 352251881Speter const char *rel_target, *rel_url; 353251881Speter const char *local_abspath; 354251881Speter 355251881Speter svn_pool_clear(iterpool); 356251881Speter 357251881Speter /* First, we need to map our REL_URL (which is relative to 358251881Speter COMMON_URL) to our REL_TARGET (which is relative to 359251881Speter COMMON_DIRENT). */ 360251881Speter rel_target = APR_ARRAY_IDX(rel_targets, i, const char *); 361251881Speter rel_url = APR_ARRAY_IDX(rel_urls, i, const char *); 362251881Speter svn_hash_sets(rel_fs_paths, rel_url, 363251881Speter apr_pstrdup(result_pool, rel_target)); 364251881Speter 365251881Speter /* Then, we map our REL_URL (again) to either the base 366251881Speter revision of the dirent target with which it is associated 367251881Speter (if our caller is locking) or to a (possible empty) lock 368251881Speter token string (if the caller is unlocking). */ 369251881Speter local_abspath = svn_dirent_join(common_dirent, rel_target, iterpool); 370251881Speter 371251881Speter if (do_lock) /* Lock. */ 372251881Speter { 373251881Speter svn_revnum_t *revnum; 374251881Speter struct wc_lock_item_t *wli; 375251881Speter revnum = apr_palloc(result_pool, sizeof(* revnum)); 376251881Speter 377251881Speter wli = svn_hash_gets(wc_info, local_abspath); 378251881Speter 379251881Speter SVN_ERR_ASSERT(wli != NULL); 380251881Speter 381251881Speter *revnum = wli->revision; 382251881Speter 383251881Speter svn_hash_sets(rel_targets_ret, rel_url, revnum); 384251881Speter } 385251881Speter else /* Unlock. */ 386251881Speter { 387251881Speter const char *lock_token; 388251881Speter struct wc_lock_item_t *wli; 389251881Speter 390251881Speter /* If not forcing the unlock, get the lock token. */ 391251881Speter if (! force) 392251881Speter { 393251881Speter wli = svn_hash_gets(wc_info, local_abspath); 394251881Speter 395251881Speter SVN_ERR_ASSERT(wli != NULL); 396251881Speter 397251881Speter if (! wli->lock_token) 398251881Speter return svn_error_createf( 399251881Speter SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 400251881Speter _("'%s' is not locked in this working copy"), 401251881Speter svn_dirent_local_style(local_abspath, 402251881Speter scratch_pool)); 403251881Speter 404251881Speter lock_token = wli->lock_token 405251881Speter ? apr_pstrdup(result_pool, wli->lock_token) 406251881Speter : NULL; 407251881Speter } 408251881Speter else 409251881Speter lock_token = NULL; 410251881Speter 411251881Speter /* If breaking a lock, we shouldn't pass any lock token. */ 412251881Speter svn_hash_sets(rel_targets_ret, rel_url, 413251881Speter lock_token ? lock_token : ""); 414251881Speter } 415251881Speter } 416251881Speter 417251881Speter svn_pool_destroy(iterpool); 418251881Speter } 419251881Speter 420251881Speter /* Set our return variables. */ 421251881Speter *common_parent_url = common_url; 422251881Speter *base_dir = common_dirent; 423251881Speter *rel_targets_p = rel_targets_ret; 424251881Speter *rel_fs_paths_p = rel_fs_paths; 425251881Speter 426251881Speter return SVN_NO_ERROR; 427251881Speter} 428251881Speter 429251881Speter/* Fetch lock tokens from the repository for the paths in PATH_TOKENS, 430251881Speter setting the values to the fetched tokens, allocated in pool. */ 431251881Speterstatic svn_error_t * 432251881Speterfetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens, 433251881Speter apr_pool_t *pool) 434251881Speter{ 435251881Speter apr_hash_index_t *hi; 436251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 437251881Speter 438251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 439251881Speter { 440251881Speter const char *path = svn__apr_hash_index_key(hi); 441251881Speter svn_lock_t *lock; 442251881Speter 443251881Speter svn_pool_clear(iterpool); 444251881Speter 445251881Speter SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool)); 446251881Speter 447251881Speter if (! lock) 448251881Speter return svn_error_createf 449251881Speter (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL, 450251881Speter _("'%s' is not locked"), path); 451251881Speter 452251881Speter svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token)); 453251881Speter } 454251881Speter 455251881Speter svn_pool_destroy(iterpool); 456251881Speter return SVN_NO_ERROR; 457251881Speter} 458251881Speter 459251881Speter 460251881Spetersvn_error_t * 461251881Spetersvn_client_lock(const apr_array_header_t *targets, 462251881Speter const char *comment, 463251881Speter svn_boolean_t steal_lock, 464251881Speter svn_client_ctx_t *ctx, 465251881Speter apr_pool_t *pool) 466251881Speter{ 467251881Speter const char *base_dir; 468251881Speter const char *base_dir_abspath = NULL; 469251881Speter const char *common_parent_url; 470251881Speter svn_ra_session_t *ra_session; 471251881Speter apr_hash_t *path_revs, *urls_to_paths; 472251881Speter struct lock_baton cb; 473251881Speter 474251881Speter if (apr_is_empty_array(targets)) 475251881Speter return SVN_NO_ERROR; 476251881Speter 477251881Speter /* Enforce that the comment be xml-escapable. */ 478251881Speter if (comment) 479251881Speter { 480251881Speter if (! svn_xml_is_xml_safe(comment, strlen(comment))) 481251881Speter return svn_error_create 482251881Speter (SVN_ERR_XML_UNESCAPABLE_DATA, NULL, 483251881Speter _("Lock comment contains illegal characters")); 484251881Speter } 485251881Speter 486251881Speter SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_revs, 487251881Speter &urls_to_paths, targets, TRUE, steal_lock, 488251881Speter ctx->wc_ctx, pool, pool)); 489251881Speter 490251881Speter /* Open an RA session to the common parent of TARGETS. */ 491251881Speter if (base_dir) 492251881Speter SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool)); 493251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url, 494251881Speter base_dir_abspath, ctx, pool, pool)); 495251881Speter 496251881Speter cb.base_dir_abspath = base_dir_abspath; 497251881Speter cb.urls_to_paths = urls_to_paths; 498251881Speter cb.ctx = ctx; 499251881Speter cb.pool = pool; 500251881Speter 501251881Speter /* Lock the paths. */ 502251881Speter SVN_ERR(svn_ra_lock(ra_session, path_revs, comment, 503251881Speter steal_lock, store_locks_callback, &cb, pool)); 504251881Speter 505251881Speter return SVN_NO_ERROR; 506251881Speter} 507251881Speter 508251881Spetersvn_error_t * 509251881Spetersvn_client_unlock(const apr_array_header_t *targets, 510251881Speter svn_boolean_t break_lock, 511251881Speter svn_client_ctx_t *ctx, 512251881Speter apr_pool_t *pool) 513251881Speter{ 514251881Speter const char *base_dir; 515251881Speter const char *base_dir_abspath = NULL; 516251881Speter const char *common_parent_url; 517251881Speter svn_ra_session_t *ra_session; 518251881Speter apr_hash_t *path_tokens, *urls_to_paths; 519251881Speter struct lock_baton cb; 520251881Speter 521251881Speter if (apr_is_empty_array(targets)) 522251881Speter return SVN_NO_ERROR; 523251881Speter 524251881Speter SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_tokens, 525251881Speter &urls_to_paths, targets, FALSE, break_lock, 526251881Speter ctx->wc_ctx, pool, pool)); 527251881Speter 528251881Speter /* Open an RA session. */ 529251881Speter if (base_dir) 530251881Speter SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool)); 531251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url, 532251881Speter base_dir_abspath, ctx, pool, pool)); 533251881Speter 534251881Speter /* If break_lock is not set, lock tokens are required by the server. 535251881Speter If the targets were all URLs, ensure that we provide lock tokens, 536251881Speter so the repository will only check that the user owns the 537251881Speter locks. */ 538251881Speter if (! base_dir && !break_lock) 539251881Speter SVN_ERR(fetch_tokens(ra_session, path_tokens, pool)); 540251881Speter 541251881Speter cb.base_dir_abspath = base_dir_abspath; 542251881Speter cb.urls_to_paths = urls_to_paths; 543251881Speter cb.ctx = ctx; 544251881Speter cb.pool = pool; 545251881Speter 546251881Speter /* Unlock the paths. */ 547251881Speter SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock, 548251881Speter store_locks_callback, &cb, pool)); 549251881Speter 550251881Speter return SVN_NO_ERROR; 551251881Speter} 552251881Speter 553