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;
50 svn_client_ctx_t *ctx;
55 /* This callback is called by the ra_layer for each path locked.
56 * BATON is a 'struct lock_baton *', PATH is the path being locked,
57 * and LOCK is the lock itself.
59 * If BATON->base_dir_abspath is not null, then this function either
60 * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
61 * (depending on whether DO_LOCK is true or false respectively), but
62 * only if RA_ERR is null, or (in the unlock case) is something other
63 * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
65 * Implements svn_ra_lock_callback_t.
68 store_locks_callback(void *baton,
70 svn_boolean_t do_lock,
71 const svn_lock_t *lock,
72 svn_error_t *ra_err, apr_pool_t *pool)
74 struct lock_baton *lb = baton;
75 svn_wc_notify_t *notify;
77 /* Create the notify struct first, so we can tweak it below. */
78 notify = svn_wc_create_notify(rel_url,
81 ? svn_wc_notify_failed_lock
82 : svn_wc_notify_locked)
84 ? svn_wc_notify_failed_unlock
85 : svn_wc_notify_unlocked),
90 if (lb->base_dir_abspath)
92 char *path = svn_hash_gets(lb->urls_to_paths, rel_url);
93 const char *local_abspath;
95 local_abspath = svn_dirent_join(lb->base_dir_abspath, path, pool);
97 /* Notify a valid working copy path */
98 notify->path = local_abspath;
99 notify->path_prefix = lb->base_dir_abspath;
105 SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
107 notify->lock_state = svn_wc_notify_lock_state_locked;
110 notify->lock_state = svn_wc_notify_lock_state_unchanged;
114 /* Remove our wc lock token either a) if we got no error, or b) if
115 we got any error except for owner mismatch. Note that the only
116 errors that are handed to this callback will be locking-related
120 (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH)))
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 = rel_url; /* Notify that path is actually a url */
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;
200 /* Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS.
201 * If TARGETS are local paths, then the entry for each path is examined
202 * and *COMMON_PARENT is set to the common parent URL for all the
203 * targets (as opposed to the common local path).
205 * If there is no common parent, either because the targets are a
206 * mixture of URLs and local paths, or because they simply do not
207 * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
209 * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
210 * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
212 * Each key stored in *REL_TARGETS_P is a path relative to
213 * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is
214 * true, the value is a pointer to the corresponding base_revision
215 * (allocated in POOL) for the path, else the value is the lock token
216 * (or "" if no token found in the wc).
218 * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
219 * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
220 * COMMON_PARENT) mapped to the target path for TARGET (relative to
221 * the common parent WC path). working copy targets that they "belong" to.
223 * If *COMMON_PARENT is a URL, then the values are a pointer to
224 * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
226 * TARGETS may not be empty.
229 organize_lock_targets(const char **common_parent_url,
230 const char **base_dir,
231 apr_hash_t **rel_targets_p,
232 apr_hash_t **rel_fs_paths_p,
233 const apr_array_header_t *targets,
234 svn_boolean_t do_lock,
236 svn_wc_context_t *wc_ctx,
237 apr_pool_t *result_pool,
238 apr_pool_t *scratch_pool)
240 const char *common_url = NULL;
241 const char *common_dirent = NULL;
242 apr_hash_t *rel_targets_ret = apr_hash_make(result_pool);
243 apr_hash_t *rel_fs_paths = NULL;
244 apr_array_header_t *rel_targets;
245 apr_hash_t *wc_info = apr_hash_make(scratch_pool);
246 svn_boolean_t url_mode;
249 SVN_ERR_ASSERT(targets->nelts);
250 SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
252 url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
256 svn_revnum_t *invalid_revnum =
257 apr_palloc(result_pool, sizeof(*invalid_revnum));
259 *invalid_revnum = SVN_INVALID_REVNUM;
261 /* Get the common parent URL and a bunch of relpaths, one per target. */
262 SVN_ERR(condense_targets(&common_url, &rel_targets, targets,
263 TRUE, TRUE, result_pool, scratch_pool));
264 if (! (common_url && *common_url))
265 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
266 _("No common parent found, unable to operate "
267 "on disjoint arguments"));
269 /* Create mapping of the target relpaths to either
270 SVN_INVALID_REVNUM (if our caller is locking) or to an empty
271 lock token string (if the caller is unlocking). */
272 for (i = 0; i < rel_targets->nelts; i++)
274 svn_hash_sets(rel_targets_ret,
275 APR_ARRAY_IDX(rel_targets, i, const char *),
277 ? (const void *)invalid_revnum
283 apr_array_header_t *rel_urls, *target_urls;
284 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
286 /* Get the common parent dirent and a bunch of relpaths, one per
288 SVN_ERR(condense_targets(&common_dirent, &rel_targets, targets,
289 FALSE, TRUE, result_pool, scratch_pool));
290 if (! (common_dirent && *common_dirent))
291 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
292 _("No common parent found, unable to operate "
293 "on disjoint arguments"));
295 /* Get the URL for each target (which also serves to verify that
296 the dirent targets are sane). */
297 target_urls = apr_array_make(scratch_pool, rel_targets->nelts,
298 sizeof(const char *));
299 for (i = 0; i < rel_targets->nelts; i++)
301 const char *rel_target;
302 const char *repos_relpath;
303 const char *repos_root_url;
304 const char *target_url;
305 struct wc_lock_item_t *wli;
306 const char *local_abspath;
307 svn_node_kind_t kind;
309 svn_pool_clear(iterpool);
311 rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
312 local_abspath = svn_dirent_join(common_dirent, rel_target, scratch_pool);
313 wli = apr_pcalloc(scratch_pool, sizeof(*wli));
315 SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, &repos_relpath,
316 &repos_root_url, NULL,
318 wc_ctx, local_abspath,
319 FALSE /* ignore_enoent */,
320 FALSE /* show_hidden */,
321 result_pool, iterpool));
323 if (kind != svn_node_file)
324 return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL,
325 _("The node '%s' is not a file"),
326 svn_dirent_local_style(local_abspath,
329 svn_hash_sets(wc_info, local_abspath, wli);
331 target_url = svn_path_url_add_component2(repos_root_url,
335 APR_ARRAY_PUSH(target_urls, const char *) = target_url;
338 /* Now that we have a bunch of URLs for our dirent targets,
339 condense those into a single common parent URL and a bunch of
340 paths relative to that. */
341 SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls,
342 TRUE, FALSE, result_pool, scratch_pool));
343 if (! (common_url && *common_url))
344 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
345 _("Unable to lock/unlock across multiple "
348 /* Now we need to create a couple of different hash mappings. */
349 rel_fs_paths = apr_hash_make(result_pool);
350 for (i = 0; i < rel_targets->nelts; i++)
352 const char *rel_target, *rel_url;
353 const char *local_abspath;
355 svn_pool_clear(iterpool);
357 /* First, we need to map our REL_URL (which is relative to
358 COMMON_URL) to our REL_TARGET (which is relative to
360 rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
361 rel_url = APR_ARRAY_IDX(rel_urls, i, const char *);
362 svn_hash_sets(rel_fs_paths, rel_url,
363 apr_pstrdup(result_pool, rel_target));
365 /* Then, we map our REL_URL (again) to either the base
366 revision of the dirent target with which it is associated
367 (if our caller is locking) or to a (possible empty) lock
368 token string (if the caller is unlocking). */
369 local_abspath = svn_dirent_join(common_dirent, rel_target, iterpool);
371 if (do_lock) /* Lock. */
373 svn_revnum_t *revnum;
374 struct wc_lock_item_t *wli;
375 revnum = apr_palloc(result_pool, sizeof(* revnum));
377 wli = svn_hash_gets(wc_info, local_abspath);
379 SVN_ERR_ASSERT(wli != NULL);
381 *revnum = wli->revision;
383 svn_hash_sets(rel_targets_ret, rel_url, revnum);
387 const char *lock_token;
388 struct wc_lock_item_t *wli;
390 /* If not forcing the unlock, get the lock token. */
393 wli = svn_hash_gets(wc_info, local_abspath);
395 SVN_ERR_ASSERT(wli != NULL);
397 if (! wli->lock_token)
398 return svn_error_createf(
399 SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
400 _("'%s' is not locked in this working copy"),
401 svn_dirent_local_style(local_abspath,
404 lock_token = wli->lock_token
405 ? apr_pstrdup(result_pool, wli->lock_token)
411 /* If breaking a lock, we shouldn't pass any lock token. */
412 svn_hash_sets(rel_targets_ret, rel_url,
413 lock_token ? lock_token : "");
417 svn_pool_destroy(iterpool);
420 /* Set our return variables. */
421 *common_parent_url = common_url;
422 *base_dir = common_dirent;
423 *rel_targets_p = rel_targets_ret;
424 *rel_fs_paths_p = rel_fs_paths;
429 /* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
430 setting the values to the fetched tokens, allocated in pool. */
432 fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
435 apr_hash_index_t *hi;
436 apr_pool_t *iterpool = svn_pool_create(pool);
438 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
440 const char *path = svn__apr_hash_index_key(hi);
443 svn_pool_clear(iterpool);
445 SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
448 return svn_error_createf
449 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
450 _("'%s' is not locked"), path);
452 svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
455 svn_pool_destroy(iterpool);
461 svn_client_lock(const apr_array_header_t *targets,
463 svn_boolean_t steal_lock,
464 svn_client_ctx_t *ctx,
467 const char *base_dir;
468 const char *base_dir_abspath = NULL;
469 const char *common_parent_url;
470 svn_ra_session_t *ra_session;
471 apr_hash_t *path_revs, *urls_to_paths;
472 struct lock_baton cb;
474 if (apr_is_empty_array(targets))
477 /* Enforce that the comment be xml-escapable. */
480 if (! svn_xml_is_xml_safe(comment, strlen(comment)))
481 return svn_error_create
482 (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
483 _("Lock comment contains illegal characters"));
486 SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_revs,
487 &urls_to_paths, targets, TRUE, steal_lock,
488 ctx->wc_ctx, pool, pool));
490 /* Open an RA session to the common parent of TARGETS. */
492 SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
493 SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
494 base_dir_abspath, ctx, pool, pool));
496 cb.base_dir_abspath = base_dir_abspath;
497 cb.urls_to_paths = urls_to_paths;
501 /* Lock the paths. */
502 SVN_ERR(svn_ra_lock(ra_session, path_revs, comment,
503 steal_lock, store_locks_callback, &cb, pool));
509 svn_client_unlock(const apr_array_header_t *targets,
510 svn_boolean_t break_lock,
511 svn_client_ctx_t *ctx,
514 const char *base_dir;
515 const char *base_dir_abspath = NULL;
516 const char *common_parent_url;
517 svn_ra_session_t *ra_session;
518 apr_hash_t *path_tokens, *urls_to_paths;
519 struct lock_baton cb;
521 if (apr_is_empty_array(targets))
524 SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_tokens,
525 &urls_to_paths, targets, FALSE, break_lock,
526 ctx->wc_ctx, pool, pool));
528 /* Open an RA session. */
530 SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
531 SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
532 base_dir_abspath, ctx, pool, pool));
534 /* If break_lock is not set, lock tokens are required by the server.
535 If the targets were all URLs, ensure that we provide lock tokens,
536 so the repository will only check that the user owns the
538 if (! base_dir && !break_lock)
539 SVN_ERR(fetch_tokens(ra_session, path_tokens, pool));
541 cb.base_dir_abspath = base_dir_abspath;
542 cb.urls_to_paths = urls_to_paths;
546 /* Unlock the paths. */
547 SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock,
548 store_locks_callback, &cb, pool));