locking_commands.c revision 289180
150476Speter/*
215903Swosch * locking_commands.c:  Implementation of lock and unlock.
315903Swosch *
415903Swosch * ====================================================================
515903Swosch *    Licensed to the Apache Software Foundation (ASF) under one
615903Swosch *    or more contributor license agreements.  See the NOTICE file
715903Swosch *    distributed with this work for additional information
8105327Sru *    regarding copyright ownership.  The ASF licenses this file
9105327Sru *    to you under the Apache License, Version 2.0 (the
10105327Sru *    "License"); you may not use this file except in compliance
11105327Sru *    with the License.  You may obtain a copy of the License at
1215903Swosch *
1315903Swosch *      http://www.apache.org/licenses/LICENSE-2.0
14105327Sru *
15105327Sru *    Unless required by applicable law or agreed to in writing,
16105327Sru *    software distributed under the License is distributed on an
17105327Sru *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1815903Swosch *    KIND, either express or implied.  See the License for the
1915903Swosch *    specific language governing permissions and limitations
2015903Swosch *    under the License.
21139761Skrion * ====================================================================
2215903Swosch */
2315903Swosch
24124490Sru/* ==================================================================== */
25124490Sru
2615903Swosch
2715903Swosch
2815903Swosch/*** Includes. ***/
2915903Swosch
3015903Swosch#include "svn_client.h"
3115903Swosch#include "svn_hash.h"
3215903Swosch#include "client.h"
3315903Swosch#include "svn_dirent_uri.h"
3415903Swosch#include "svn_path.h"
3515903Swosch#include "svn_xml.h"
3615903Swosch#include "svn_pools.h"
37105327Sru
38105327Sru#include "svn_private_config.h"
39105327Sru#include "private/svn_client_private.h"
40105327Sru#include "private/svn_wc_private.h"
411845Swollman
4295255Sru
4395255Sru/*** Code. ***/
4495255Sru
4515903Swosch/* For use with store_locks_callback, below. */
46105327Srustruct lock_baton
47105327Sru{
48105327Sru  const char *base_dir_abspath;
49105327Sru  apr_hash_t *urls_to_paths; /* url -> abspath */
50105327Sru  const char *base_url;
5194768Sru  svn_client_ctx_t *ctx;
5296132Sbde  apr_pool_t *pool;
5394768Sru};
5494768Sru
5594768Sru
5614986Swosch/* This callback is called by the ra_layer for each path locked.
5714986Swosch * BATON is a 'struct lock_baton *', PATH is the path being locked,
58105327Sru * and LOCK is the lock itself.
59139108Sru *
60105327Sru * If BATON->urls_to_paths is not null, then this function either
61202579Sru * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
62105327Sru * (depending on whether DO_LOCK is true or false respectively), but
63105327Sru * only if RA_ERR is null, or (in the unlock case) is something other
64105327Sru * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
65105327Sru *
66202579Sru * Implements svn_ra_lock_callback_t.
67202579Sru */
68202579Srustatic svn_error_t *
69105327Srustore_locks_callback(void *baton,
70105327Sru                     const char *rel_url,
71105327Sru                     svn_boolean_t do_lock,
7235951Sbde                     const svn_lock_t *lock,
7335951Sbde                     svn_error_t *ra_err, apr_pool_t *pool)
7435951Sbde{
75124637Sru  struct lock_baton *lb = baton;
76124637Sru  svn_wc_notify_t *notify;
77124637Sru  const char *local_abspath = lb->urls_to_paths
78124637Sru                                  ? svn_hash_gets(lb->urls_to_paths, rel_url)
79124637Sru                                  : NULL;
80124637Sru
8135951Sbde  /* Create the notify struct first, so we can tweak it below. */
82124435Sru  notify = svn_wc_create_notify(local_abspath ? local_abspath : rel_url,
8335951Sbde                                do_lock
8435951Sbde                                ? (ra_err
85124637Sru                                   ? svn_wc_notify_failed_lock
86124637Sru                                   : svn_wc_notify_locked)
87124637Sru                                : (ra_err
8835951Sbde                                   ? svn_wc_notify_failed_unlock
89124435Sru                                   : svn_wc_notify_unlocked),
9035951Sbde                                pool);
9135951Sbde  notify->lock = lock;
9235951Sbde  notify->err = ra_err;
9335951Sbde
94124435Sru  if (local_abspath)
9535951Sbde    {
96124435Sru      /* Notify a valid working copy path */
97125119Sru      notify->path_prefix = lb->base_dir_abspath;
98241790Smarcel
99241790Smarcel      if (do_lock)
10035951Sbde        {
10135951Sbde          if (!ra_err && lock)
102124435Sru            {
103125119Sru              SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
104124435Sru                                       lb->pool));
105241790Smarcel              notify->lock_state = svn_wc_notify_lock_state_locked;
106241790Smarcel            }
10735951Sbde          else
108124435Sru            notify->lock_state = svn_wc_notify_lock_state_unchanged;
109124435Sru        }
11035951Sbde      else /* unlocking */
11135951Sbde        {
11235951Sbde          /* Remove our wc lock token either a) if we got no error, or b) if
11335951Sbde             we got any error except for owner mismatch or hook failure (the
11435951Sbde             hook would be pre-unlock rather than post-unlock). Note that the
115124637Sru             only errors that are handed to this callback will be
116124637Sru             locking-related errors. */
117124637Sru
11835951Sbde          if (!ra_err ||
11935951Sbde              (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH
12035951Sbde                          && ra_err->apr_err != SVN_ERR_REPOS_HOOK_FAILURE)))
12135951Sbde            {
1221845Swollman              SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath,
12330113Sjkh                                          lb->pool));
12495306Sru              notify->lock_state = svn_wc_notify_lock_state_unlocked;
12514986Swosch            }
126241298Smarcel          else
127241298Smarcel            notify->lock_state = svn_wc_notify_lock_state_unchanged;
128241298Smarcel        }
12934181Sbde    }
13034181Sbde  else
131239613Sdim    notify->url = svn_path_url_add_component2(lb->base_url, rel_url, pool);
132239613Sdim
133239613Sdim  if (lb->ctx->notify_func2)
134239613Sdim    lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool);
135126890Strhodes
136124490Sru  return SVN_NO_ERROR;
137124490Sru}
13814986Swosch
139125119Sru
14034181Sbde/* This is a wrapper around svn_uri_condense_targets() and
141126890Strhodes * svn_dirent_condense_targets() (the choice of which is made based on
14214986Swosch * the value of TARGETS_ARE_URIS) which takes care of the
143125119Sru * single-target special case.
144125119Sru *
14514986Swosch * Callers are expected to check for an empty *COMMON_PARENT (which
146126890Strhodes * means, "there was nothing common") for themselves.
14736673Sdt */
14814986Swoschstatic svn_error_t *
14924750Sbdecondense_targets(const char **common_parent,
15094922Sru                 apr_array_header_t **target_relpaths,
15194841Sru                 const apr_array_header_t *targets,
15224750Sbde                 svn_boolean_t targets_are_uris,
15314986Swosch                 svn_boolean_t remove_redundancies,
15430113Sjkh                 apr_pool_t *result_pool,
1551845Swollman                 apr_pool_t *scratch_pool)
15695306Sru{
1571845Swollman  if (targets_are_uris)
1581845Swollman    {
1591845Swollman      SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths,
16030113Sjkh                                       targets, remove_redundancies,
16130113Sjkh                                       result_pool, scratch_pool));
16230113Sjkh    }
1631845Swollman  else
1641845Swollman    {
1651845Swollman      SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths,
1661845Swollman                                          targets, remove_redundancies,
1671845Swollman                                          result_pool, scratch_pool));
1681845Swollman    }
16916663Sjkh
17095306Sru  /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only
17128806Sbde     had 1 member, so we special case that. */
172202579Sru  if (apr_is_empty_array(*target_relpaths))
173105327Sru    {
17424861Sjkh      const char *base_name;
175105327Sru
17616663Sjkh      if (targets_are_uris)
177202578Sru        {
178202579Sru          svn_uri_split(common_parent, &base_name,
17916663Sjkh                        *common_parent, result_pool);
18024861Sjkh        }
181105327Sru      else
18299344Sru        {
18399344Sru          svn_dirent_split(common_parent, &base_name,
184117195Sbde                           *common_parent, result_pool);
185117195Sbde        }
18699344Sru      APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name;
187117195Sbde    }
188117195Sbde
189117195Sbde  return SVN_NO_ERROR;
190117195Sbde}
19199344Sru
192117195Sbde/* Lock info. Used in organize_lock_targets.
193   ### Maybe return this instead of the ugly hashes? */
194struct wc_lock_item_t
195{
196  svn_revnum_t revision;
197  const char *lock_token;
198  const char *url;
199};
200
201/*
202 * Sets LOCK_PATHS to an array of working copy paths that this function
203 * has obtained lock on. The caller is responsible to release the locks
204 * EVEN WHEN THIS FUNCTION RETURNS AN ERROR.
205 *
206 * Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS.
207 * If TARGETS are local paths, then the entry for each path is examined
208 * and *COMMON_PARENT is set to the common parent URL for all the
209 * targets (as opposed to the common local path).
210 *
211 * If there is no common parent, either because the targets are a
212 * mixture of URLs and local paths, or because they simply do not
213 * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
214 *
215 * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
216 * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
217 *
218 * Each key stored in *REL_TARGETS_P is a path relative to
219 * *COMMON_PARENT.  If TARGETS are local paths, then: if DO_LOCK is
220 * true, the value is a pointer to the corresponding base_revision
221 * (allocated in POOL) for the path, else the value is the lock token
222 * (or "" if no token found in the wc).
223 *
224 * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
225 * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
226 * COMMON_PARENT) mapped to the absolute path for TARGET.
227 *
228 * If *COMMON_PARENT is a URL, then the values are a pointer to
229 * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
230 *
231 * TARGETS may not be empty.
232 */
233static svn_error_t *
234organize_lock_targets(apr_array_header_t **lock_paths,
235                      const char **common_parent_url,
236                      const char **base_dir_abspath,
237                      apr_hash_t **rel_targets_p,
238                      apr_hash_t **rel_fs_paths_p,
239                      const apr_array_header_t *targets,
240                      svn_boolean_t do_lock,
241                      svn_boolean_t force,
242                      svn_wc_context_t *wc_ctx,
243                      apr_pool_t *result_pool,
244                      apr_pool_t *scratch_pool)
245{
246  const char *common_url = NULL;
247  apr_hash_t *rel_targets_ret = apr_hash_make(result_pool);
248  apr_hash_t *rel_fs_paths = NULL;
249  apr_hash_t *wc_info = apr_hash_make(scratch_pool);
250  svn_boolean_t url_mode;
251
252  *lock_paths = NULL;
253
254  SVN_ERR_ASSERT(targets->nelts);
255  SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
256
257  url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
258
259  if (url_mode)
260    {
261      apr_array_header_t *rel_targets;
262      int i;
263      svn_revnum_t *invalid_revnum =
264        apr_palloc(result_pool, sizeof(*invalid_revnum));
265
266      *invalid_revnum = SVN_INVALID_REVNUM;
267
268      /* Get the common parent URL and a bunch of relpaths, one per target. */
269      SVN_ERR(condense_targets(&common_url, &rel_targets, targets,
270                               TRUE, TRUE, result_pool, scratch_pool));
271      if (! (common_url && *common_url))
272        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
273                                _("No common parent found, unable to operate "
274                                  "on disjoint arguments"));
275
276      /* Create mapping of the target relpaths to either
277         SVN_INVALID_REVNUM (if our caller is locking) or to an empty
278         lock token string (if the caller is unlocking). */
279      for (i = 0; i < rel_targets->nelts; i++)
280        {
281          svn_hash_sets(rel_targets_ret,
282                        APR_ARRAY_IDX(rel_targets, i, const char *),
283                        do_lock
284                        ? (const void *)invalid_revnum
285                        : (const void *)"");
286        }
287    }
288  else
289    {
290      apr_array_header_t *rel_urls, *target_urls;
291      apr_hash_t *wcroot_target = apr_hash_make(scratch_pool);
292      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
293      apr_hash_index_t *hi;
294      int i;
295
296      *lock_paths = apr_array_make(result_pool, 1, sizeof(const char *));
297
298      for (i = 0; i < targets->nelts; i++)
299        {
300          const char *target_abspath;
301          const char *wcroot_abspath;
302          apr_array_header_t *wc_targets;
303
304          svn_pool_clear(iterpool);
305
306          SVN_ERR(svn_dirent_get_absolute(&target_abspath,
307                                          APR_ARRAY_IDX(targets, i, const char*),
308                                          result_pool));
309
310          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
311                                     iterpool, iterpool));
312
313          wc_targets = svn_hash_gets(wcroot_target, wcroot_abspath);
314
315          if (!wc_targets)
316            {
317              wc_targets = apr_array_make(scratch_pool, 1, sizeof(const char*));
318              svn_hash_sets(wcroot_target, apr_pstrdup(scratch_pool, wcroot_abspath),
319                            wc_targets);
320            }
321
322          APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
323        }
324
325      for (hi = apr_hash_first(scratch_pool, wcroot_target);
326           hi;
327           hi = apr_hash_next(hi))
328        {
329          const char *lock_abspath;
330          apr_array_header_t *paths = apr_hash_this_val(hi);
331
332          /* Use parent dir of a single file target */
333          if (paths->nelts == 1)
334            lock_abspath = svn_dirent_dirname(
335                                APR_ARRAY_IDX(paths, 0, const char *),
336                                result_pool);
337          else
338            SVN_ERR(svn_dirent_condense_targets(&lock_abspath, NULL, paths,
339                                                FALSE, result_pool,
340                                                scratch_pool));
341
342          SVN_ERR(svn_wc__acquire_write_lock(&lock_abspath,
343                                             wc_ctx, lock_abspath, FALSE,
344                                             result_pool, scratch_pool));
345
346          APR_ARRAY_PUSH(*lock_paths, const char *) = lock_abspath;
347        }
348
349      /* Get the URL for each target (which also serves to verify that
350         the dirent targets are sane).  */
351      target_urls = apr_array_make(scratch_pool, targets->nelts,
352                                   sizeof(const char *));
353      for (hi = apr_hash_first(scratch_pool, wcroot_target);
354           hi;
355           hi = apr_hash_next(hi))
356        {
357          apr_array_header_t *wc_targets = apr_hash_this_val(hi);
358
359          for (i = 0; i < wc_targets->nelts; i++)
360            {
361              const char *repos_relpath;
362              const char *repos_root_url;
363              struct wc_lock_item_t *wli;
364              const char *local_abspath;
365              svn_node_kind_t kind;
366
367              svn_pool_clear(iterpool);
368
369              local_abspath = APR_ARRAY_IDX(wc_targets, i, const char *);
370              wli = apr_pcalloc(scratch_pool, sizeof(*wli));
371
372              SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision,
373                                            &repos_relpath,
374                                            &repos_root_url, NULL,
375                                            &wli->lock_token,
376                                            wc_ctx, local_abspath,
377                                            FALSE /* ignore_enoent */,
378                                            result_pool, iterpool));
379
380              if (kind != svn_node_file)
381                return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL,
382                                         _("The node '%s' is not a file"),
383                                         svn_dirent_local_style(local_abspath,
384                                                                iterpool));
385
386              wli->url = svn_path_url_add_component2(repos_root_url,
387                                                     repos_relpath,
388                                                     scratch_pool);
389              svn_hash_sets(wc_info, local_abspath, wli);
390
391              APR_ARRAY_PUSH(target_urls, const char *) = wli->url;
392            }
393        }
394
395      /* Now that we have a bunch of URLs for our dirent targets,
396         condense those into a single common parent URL and a bunch of
397         paths relative to that. */
398      SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls,
399                               TRUE, FALSE, result_pool, scratch_pool));
400      if (! (common_url && *common_url))
401        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
402                                _("Unable to lock/unlock across multiple "
403                                  "repositories"));
404
405      /* Now we need to create our mapping. */
406      rel_fs_paths = apr_hash_make(result_pool);
407
408      for (hi = apr_hash_first(scratch_pool, wc_info);
409           hi;
410           hi = apr_hash_next(hi))
411        {
412          const char *local_abspath = apr_hash_this_key(hi);
413          struct wc_lock_item_t *wli = apr_hash_this_val(hi);
414          const char *rel_url;
415
416          svn_pool_clear(iterpool);
417
418          rel_url = svn_uri_skip_ancestor(common_url, wli->url, result_pool);
419
420          svn_hash_sets(rel_fs_paths, rel_url,
421                        apr_pstrdup(result_pool, local_abspath));
422
423          if (do_lock) /* Lock. */
424            {
425              svn_revnum_t *revnum;
426              revnum = apr_palloc(result_pool, sizeof(* revnum));
427
428              *revnum = wli->revision;
429
430              svn_hash_sets(rel_targets_ret, rel_url, revnum);
431            }
432          else /* Unlock. */
433            {
434              const char *lock_token;
435
436              /* If not forcing the unlock, get the lock token. */
437              if (! force)
438                {
439                  if (! wli->lock_token)
440                    return svn_error_createf(
441                               SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
442                               _("'%s' is not locked in this working copy"),
443                               svn_dirent_local_style(local_abspath,
444                                                      scratch_pool));
445
446                  lock_token = wli->lock_token
447                                ? apr_pstrdup(result_pool, wli->lock_token)
448                                : NULL;
449                }
450              else
451                lock_token = NULL;
452
453              /* If breaking a lock, we shouldn't pass any lock token. */
454              svn_hash_sets(rel_targets_ret, rel_url,
455                            lock_token ? lock_token : "");
456            }
457        }
458
459      svn_pool_destroy(iterpool);
460    }
461
462  /* Set our return variables. */
463  *common_parent_url = common_url;
464  if (*lock_paths && (*lock_paths)->nelts == 1)
465    *base_dir_abspath = APR_ARRAY_IDX(*lock_paths, 0, const char*);
466  else
467    *base_dir_abspath = NULL;
468  *rel_targets_p = rel_targets_ret;
469  *rel_fs_paths_p = rel_fs_paths;
470
471  return SVN_NO_ERROR;
472}
473
474/* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
475   setting the values to the fetched tokens, allocated in pool. */
476static svn_error_t *
477fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
478             apr_pool_t *pool)
479{
480  apr_hash_index_t *hi;
481  apr_pool_t *iterpool = svn_pool_create(pool);
482
483  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
484    {
485      const char *path = apr_hash_this_key(hi);
486      svn_lock_t *lock;
487
488      svn_pool_clear(iterpool);
489
490      SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
491
492      if (! lock)
493        return svn_error_createf
494          (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
495           _("'%s' is not locked"), path);
496
497      svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
498    }
499
500  svn_pool_destroy(iterpool);
501  return SVN_NO_ERROR;
502}
503
504
505svn_error_t *
506svn_client_lock(const apr_array_header_t *targets,
507                const char *comment,
508                svn_boolean_t steal_lock,
509                svn_client_ctx_t *ctx,
510                apr_pool_t *pool)
511{
512  const char *base_dir_abspath = NULL;
513  const char *common_parent_url;
514  svn_ra_session_t *ra_session;
515  apr_hash_t *path_revs, *urls_to_paths;
516  struct lock_baton cb;
517  apr_array_header_t *lock_abspaths;
518  svn_error_t *err;
519
520  if (apr_is_empty_array(targets))
521    return SVN_NO_ERROR;
522
523  /* Enforce that the comment be xml-escapable. */
524  if (comment)
525    {
526      if (! svn_xml_is_xml_safe(comment, strlen(comment)))
527        return svn_error_create
528          (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
529           _("Lock comment contains illegal characters"));
530    }
531
532  err = organize_lock_targets(&lock_abspaths, &common_parent_url,
533                              &base_dir_abspath, &path_revs,
534                              &urls_to_paths,
535                              targets, TRUE, steal_lock,
536                              ctx->wc_ctx, pool, pool);
537
538  if (err)
539    goto release_locks;
540
541  /* Open an RA session to the common parent URL of TARGETS. */
542  err = svn_client_open_ra_session2(&ra_session, common_parent_url,
543                                    base_dir_abspath, ctx, pool, pool);
544
545  if (err)
546    goto release_locks;
547
548  cb.base_dir_abspath = base_dir_abspath;
549  cb.base_url = common_parent_url;
550  cb.urls_to_paths = urls_to_paths;
551  cb.ctx = ctx;
552  cb.pool = pool;
553
554  /* Lock the paths. */
555  err = svn_ra_lock(ra_session, path_revs, comment,
556                    steal_lock, store_locks_callback, &cb, pool);
557
558release_locks:
559  if (lock_abspaths)
560    {
561      int i;
562
563      for (i = 0; i < lock_abspaths->nelts; i++)
564        {
565          err = svn_error_compose_create(
566                  err,
567                  svn_wc__release_write_lock(ctx->wc_ctx,
568                                             APR_ARRAY_IDX(lock_abspaths, i,
569                                                           const char *),
570                                             pool));
571        }
572    }
573
574  return svn_error_trace(err);
575}
576
577svn_error_t *
578svn_client_unlock(const apr_array_header_t *targets,
579                  svn_boolean_t break_lock,
580                  svn_client_ctx_t *ctx,
581                  apr_pool_t *pool)
582{
583  const char *base_dir_abspath = NULL;
584  const char *common_parent_url;
585  svn_ra_session_t *ra_session;
586  apr_hash_t *path_tokens, *urls_to_paths;
587  apr_array_header_t *lock_abspaths;
588  struct lock_baton cb;
589  svn_error_t *err;
590
591  if (apr_is_empty_array(targets))
592    return SVN_NO_ERROR;
593
594  err = organize_lock_targets(&lock_abspaths, &common_parent_url,
595                              &base_dir_abspath, &path_tokens, &urls_to_paths,
596                              targets, FALSE, break_lock,
597                              ctx->wc_ctx, pool, pool);
598
599  if (err)
600    goto release_locks;
601
602  /* Open an RA session to the common parent URL of TARGETS. */
603  err = svn_client_open_ra_session2(&ra_session, common_parent_url,
604                                    base_dir_abspath, ctx, pool, pool);
605
606  if (err)
607    goto release_locks;
608
609  /* If break_lock is not set, lock tokens are required by the server.
610     If the targets were all URLs, ensure that we provide lock tokens,
611     so the repository will only check that the user owns the
612     locks. */
613  if (! lock_abspaths && !break_lock)
614    {
615      err = fetch_tokens(ra_session, path_tokens, pool);
616
617      if (err)
618        goto release_locks;
619    }
620
621  cb.base_dir_abspath = base_dir_abspath;
622  cb.base_url = common_parent_url;
623  cb.urls_to_paths = urls_to_paths;
624  cb.ctx = ctx;
625  cb.pool = pool;
626
627  /* Unlock the paths. */
628  err = svn_ra_unlock(ra_session, path_tokens, break_lock,
629                      store_locks_callback, &cb, pool);
630
631release_locks:
632  if (lock_abspaths)
633    {
634      int i;
635
636      for (i = 0; i < lock_abspaths->nelts; i++)
637        {
638          err = svn_error_compose_create(
639                  err,
640                  svn_wc__release_write_lock(ctx->wc_ctx,
641                                             APR_ARRAY_IDX(lock_abspaths, i,
642                                                           const char *),
643                                             pool));
644        }
645    }
646
647  return svn_error_trace(err);
648}
649
650