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