2 * locking_commands.c: Implementation of lock and unlock.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_client.h"
33 #include "svn_dirent_uri.h"
36 #include "svn_pools.h"
38 #include "svn_private_config.h"
39 #include "private/svn_client_private.h"
40 #include "private/svn_wc_private.h"
45 /* For use with store_locks_callback, below. */
48 const char *base_dir_abspath;
49 apr_hash_t *urls_to_paths; /* url -> abspath */
51 svn_client_ctx_t *ctx;
56 /* This callback is called by the ra_layer for each path locked.
57 * BATON is a 'struct lock_baton *', PATH is the path being locked,
58 * and LOCK is the lock itself.
60 * If BATON->urls_to_paths is not null, then this function either
61 * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
62 * (depending on whether DO_LOCK is true or false respectively), but
63 * only if RA_ERR is null, or (in the unlock case) is something other
64 * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
66 * Implements svn_ra_lock_callback_t.
69 store_locks_callback(void *baton,
71 svn_boolean_t do_lock,
72 const svn_lock_t *lock,
73 svn_error_t *ra_err, apr_pool_t *pool)
75 struct lock_baton *lb = baton;
76 svn_wc_notify_t *notify;
77 const char *local_abspath = lb->urls_to_paths
78 ? svn_hash_gets(lb->urls_to_paths, rel_url)
81 /* Create the notify struct first, so we can tweak it below. */
82 notify = svn_wc_create_notify(local_abspath ? local_abspath : rel_url,
85 ? svn_wc_notify_failed_lock
86 : svn_wc_notify_locked)
88 ? svn_wc_notify_failed_unlock
89 : svn_wc_notify_unlocked),
96 /* Notify a valid working copy path */
97 notify->path_prefix = lb->base_dir_abspath;
103 SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
105 notify->lock_state = svn_wc_notify_lock_state_locked;
108 notify->lock_state = svn_wc_notify_lock_state_unchanged;
112 /* Remove our wc lock token either a) if we got no error, or b) if
113 we got any error except for owner mismatch or hook failure (the
114 hook would be pre-unlock rather than post-unlock). Note that the
115 only errors that are handed to this callback will be
116 locking-related errors. */
119 (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH
120 && ra_err->apr_err != SVN_ERR_REPOS_HOOK_FAILURE)))
122 SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath,
124 notify->lock_state = svn_wc_notify_lock_state_unlocked;
127 notify->lock_state = svn_wc_notify_lock_state_unchanged;
131 notify->url = svn_path_url_add_component2(lb->base_url, rel_url, pool);
133 if (lb->ctx->notify_func2)
134 lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool);
140 /* This is a wrapper around svn_uri_condense_targets() and
141 * svn_dirent_condense_targets() (the choice of which is made based on
142 * the value of TARGETS_ARE_URIS) which takes care of the
143 * single-target special case.
145 * Callers are expected to check for an empty *COMMON_PARENT (which
146 * means, "there was nothing common") for themselves.
149 condense_targets(const char **common_parent,
150 apr_array_header_t **target_relpaths,
151 const apr_array_header_t *targets,
152 svn_boolean_t targets_are_uris,
153 svn_boolean_t remove_redundancies,
154 apr_pool_t *result_pool,
155 apr_pool_t *scratch_pool)
157 if (targets_are_uris)
159 SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths,
160 targets, remove_redundancies,
161 result_pool, scratch_pool));
165 SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths,
166 targets, remove_redundancies,
167 result_pool, scratch_pool));
170 /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only
171 had 1 member, so we special case that. */
172 if (apr_is_empty_array(*target_relpaths))
174 const char *base_name;
176 if (targets_are_uris)
178 svn_uri_split(common_parent, &base_name,
179 *common_parent, result_pool);
183 svn_dirent_split(common_parent, &base_name,
184 *common_parent, result_pool);
186 APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name;
192 /* Lock info. Used in organize_lock_targets.
193 ### Maybe return this instead of the ugly hashes? */
194 struct wc_lock_item_t
196 svn_revnum_t revision;
197 const char *lock_token;
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.
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).
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.
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.
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).
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.
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 "".
231 * TARGETS may not be empty.
234 organize_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,
242 svn_wc_context_t *wc_ctx,
243 apr_pool_t *result_pool,
244 apr_pool_t *scratch_pool)
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;
254 SVN_ERR_ASSERT(targets->nelts);
255 SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
257 url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
261 apr_array_header_t *rel_targets;
263 svn_revnum_t *invalid_revnum =
264 apr_palloc(result_pool, sizeof(*invalid_revnum));
266 *invalid_revnum = SVN_INVALID_REVNUM;
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"));
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++)
281 svn_hash_sets(rel_targets_ret,
282 APR_ARRAY_IDX(rel_targets, i, const char *),
284 ? (const void *)invalid_revnum
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;
296 *lock_paths = apr_array_make(result_pool, 1, sizeof(const char *));
298 for (i = 0; i < targets->nelts; i++)
300 const char *target_abspath;
301 const char *wcroot_abspath;
302 apr_array_header_t *wc_targets;
304 svn_pool_clear(iterpool);
306 SVN_ERR(svn_dirent_get_absolute(&target_abspath,
307 APR_ARRAY_IDX(targets, i, const char*),
310 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
311 iterpool, iterpool));
313 wc_targets = svn_hash_gets(wcroot_target, wcroot_abspath);
317 wc_targets = apr_array_make(scratch_pool, 1, sizeof(const char*));
318 svn_hash_sets(wcroot_target, apr_pstrdup(scratch_pool, wcroot_abspath),
322 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
325 for (hi = apr_hash_first(scratch_pool, wcroot_target);
327 hi = apr_hash_next(hi))
329 const char *lock_abspath;
330 apr_array_header_t *paths = apr_hash_this_val(hi);
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 *),
338 SVN_ERR(svn_dirent_condense_targets(&lock_abspath, NULL, paths,
342 SVN_ERR(svn_wc__acquire_write_lock(&lock_abspath,
343 wc_ctx, lock_abspath, FALSE,
344 result_pool, scratch_pool));
346 APR_ARRAY_PUSH(*lock_paths, const char *) = lock_abspath;
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);
355 hi = apr_hash_next(hi))
357 apr_array_header_t *wc_targets = apr_hash_this_val(hi);
359 for (i = 0; i < wc_targets->nelts; i++)
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;
367 svn_pool_clear(iterpool);
369 local_abspath = APR_ARRAY_IDX(wc_targets, i, const char *);
370 wli = apr_pcalloc(scratch_pool, sizeof(*wli));
372 SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision,
374 &repos_root_url, NULL,
376 wc_ctx, local_abspath,
377 FALSE /* ignore_enoent */,
378 result_pool, iterpool));
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,
386 wli->url = svn_path_url_add_component2(repos_root_url,
389 svn_hash_sets(wc_info, local_abspath, wli);
391 APR_ARRAY_PUSH(target_urls, const char *) = wli->url;
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 "
405 /* Now we need to create our mapping. */
406 rel_fs_paths = apr_hash_make(result_pool);
408 for (hi = apr_hash_first(scratch_pool, wc_info);
410 hi = apr_hash_next(hi))
412 const char *local_abspath = apr_hash_this_key(hi);
413 struct wc_lock_item_t *wli = apr_hash_this_val(hi);
416 svn_pool_clear(iterpool);
418 rel_url = svn_uri_skip_ancestor(common_url, wli->url, result_pool);
420 svn_hash_sets(rel_fs_paths, rel_url,
421 apr_pstrdup(result_pool, local_abspath));
423 if (do_lock) /* Lock. */
425 svn_revnum_t *revnum;
426 revnum = apr_palloc(result_pool, sizeof(* revnum));
428 *revnum = wli->revision;
430 svn_hash_sets(rel_targets_ret, rel_url, revnum);
434 const char *lock_token;
436 /* If not forcing the unlock, get the lock token. */
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,
446 lock_token = wli->lock_token
447 ? apr_pstrdup(result_pool, wli->lock_token)
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 : "");
459 svn_pool_destroy(iterpool);
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*);
467 *base_dir_abspath = NULL;
468 *rel_targets_p = rel_targets_ret;
469 *rel_fs_paths_p = rel_fs_paths;
474 /* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
475 setting the values to the fetched tokens, allocated in pool. */
477 fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
480 apr_hash_index_t *hi;
481 apr_pool_t *iterpool = svn_pool_create(pool);
483 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
485 const char *path = apr_hash_this_key(hi);
488 svn_pool_clear(iterpool);
490 SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
493 return svn_error_createf
494 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
495 _("'%s' is not locked"), path);
497 svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
500 svn_pool_destroy(iterpool);
506 svn_client_lock(const apr_array_header_t *targets,
508 svn_boolean_t steal_lock,
509 svn_client_ctx_t *ctx,
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;
520 if (apr_is_empty_array(targets))
523 /* Enforce that the comment be xml-escapable. */
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"));
532 err = organize_lock_targets(&lock_abspaths, &common_parent_url,
533 &base_dir_abspath, &path_revs,
535 targets, TRUE, steal_lock,
536 ctx->wc_ctx, pool, pool);
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);
548 cb.base_dir_abspath = base_dir_abspath;
549 cb.base_url = common_parent_url;
550 cb.urls_to_paths = urls_to_paths;
554 /* Lock the paths. */
555 err = svn_ra_lock(ra_session, path_revs, comment,
556 steal_lock, store_locks_callback, &cb, pool);
563 for (i = 0; i < lock_abspaths->nelts; i++)
565 err = svn_error_compose_create(
567 svn_wc__release_write_lock(ctx->wc_ctx,
568 APR_ARRAY_IDX(lock_abspaths, i,
574 return svn_error_trace(err);
578 svn_client_unlock(const apr_array_header_t *targets,
579 svn_boolean_t break_lock,
580 svn_client_ctx_t *ctx,
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;
591 if (apr_is_empty_array(targets))
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);
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);
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
613 if (! lock_abspaths && !break_lock)
615 err = fetch_tokens(ra_session, path_tokens, pool);
621 cb.base_dir_abspath = base_dir_abspath;
622 cb.base_url = common_parent_url;
623 cb.urls_to_paths = urls_to_paths;
627 /* Unlock the paths. */
628 err = svn_ra_unlock(ra_session, path_tokens, break_lock,
629 store_locks_callback, &cb, pool);
636 for (i = 0; i < lock_abspaths->nelts; i++)
638 err = svn_error_compose_create(
640 svn_wc__release_write_lock(ctx->wc_ctx,
641 APR_ARRAY_IDX(lock_abspaths, i,
647 return svn_error_trace(err);