* subversion/mod_dav_svn/reports/replay.c
[svn.git] / subversion / libsvn_client / locking_commands.c
blob3aa05c652d6c6f461d2563b23dcc39d0845febbd
1 /*
2 * locking_commands.c: Implementation of lock and unlock.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include "svn_client.h"
26 #include "client.h"
27 #include "svn_path.h"
28 #include "svn_xml.h"
29 #include "svn_pools.h"
31 #include "svn_private_config.h"
32 #include "private/svn_wc_private.h"
35 /*** Code. ***/
37 /* For use with store_locks_callback, below. */
38 struct lock_baton
40 svn_wc_adm_access_t *adm_access;
41 apr_hash_t *urls_to_paths;
42 svn_client_ctx_t *ctx;
43 apr_pool_t *pool;
47 /* This callback is called by the ra_layer for each path locked.
48 * BATON is a 'struct lock_baton *', PATH is the path being locked,
49 * and LOCK is the lock itself.
51 * If BATON->adm_access is not null, then this function either stores
52 * the LOCK on REL_URL or removes any lock tokens from REL_URL
53 * (depending on whether DO_LOCK is true or false respectively), but
54 * only if RA_ERR is null, or (in the unlock case) is something other
55 * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
57 static svn_error_t *
58 store_locks_callback(void *baton,
59 const char *rel_url,
60 svn_boolean_t do_lock,
61 const svn_lock_t *lock,
62 svn_error_t *ra_err, apr_pool_t *pool)
64 struct lock_baton *lb = baton;
65 svn_wc_adm_access_t *adm_access;
66 const char *abs_path;
67 svn_wc_notify_t *notify;
69 /* Create the notify struct first, so we can tweak it below. */
70 notify = svn_wc_create_notify(rel_url,
71 do_lock
72 ? (ra_err
73 ? svn_wc_notify_failed_lock
74 : svn_wc_notify_locked)
75 : (ra_err
76 ? svn_wc_notify_failed_unlock
77 : svn_wc_notify_unlocked),
78 pool);
79 notify->lock = lock;
80 notify->err = ra_err;
82 if (lb->adm_access)
84 char *path = apr_hash_get(lb->urls_to_paths, rel_url,
85 APR_HASH_KEY_STRING);
86 abs_path = svn_path_join(svn_wc_adm_access_path(lb->adm_access),
87 path, lb->pool);
89 SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, lb->adm_access,
90 abs_path, lb->pool));
92 if (do_lock)
94 if (!ra_err)
96 SVN_ERR(svn_wc_add_lock(abs_path, lock, adm_access, lb->pool));
97 notify->lock_state = svn_wc_notify_lock_state_locked;
99 else
100 notify->lock_state = svn_wc_notify_lock_state_unchanged;
102 else /* unlocking */
104 /* Remove our wc lock token either a) if we got no error, or b) if
105 we got any error except for owner mismatch. Note that the only
106 errors that are handed to this callback will be locking-related
107 errors. */
109 if (!ra_err ||
110 (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH)))
112 SVN_ERR(svn_wc_remove_lock(abs_path, adm_access, lb->pool));
113 notify->lock_state = svn_wc_notify_lock_state_unlocked;
115 else
116 notify->lock_state = svn_wc_notify_lock_state_unchanged;
120 if (lb->ctx->notify_func2)
121 lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool);
123 return SVN_NO_ERROR;
127 /* Set *COMMON_PARENT to the nearest common parent of all TARGETS. If
128 * TARGETS are local paths, then the entry for each path is examined
129 * and *COMMON_PARENT is set to the common parent URL for all the
130 * targets (as opposed to the common local path).
132 * If all the targets are local paths within the same wc, i.e., they
133 * share a common parent at some level, set *PARENT_ADM_ACCESS_P
134 * to the adm_access of that common parent. *PARENT_ADM_ACCESS_P will
135 * be associated with adm_access objects for all the other paths,
136 * which are locked in the working copy while we lock them in the
137 * repository.
139 * If all the targets are URLs in the same repository, i.e. sharing a
140 * common parent URL prefix, then set *PARENT_ADM_ACCESS_P to null.
142 * If there is no common parent, either because the targets are a
143 * mixture of URLs and local paths, or because they simply do not
144 * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
146 * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
147 * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
149 * Each key stored in *REL_TARGETS_P is a path relative to
150 * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is
151 * true, the value is a pointer to the corresponding base_revision
152 * (allocated in POOL) for the path, else the value is the lock token
153 * (or "" if no token found in the wc).
155 * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
156 * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
157 * COMMON_PARENT) mapped to the target path for TARGET (relative to
158 * the common parent WC path). working copy targets that they "belong" to.
160 * If *COMMON_PARENT is a URL, then the values are a pointer to
161 * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
163 * TARGETS may not be empty.
165 static svn_error_t *
166 organize_lock_targets(const char **common_parent,
167 svn_wc_adm_access_t **parent_adm_access_p,
168 apr_hash_t **rel_targets_p,
169 apr_hash_t **rel_fs_paths_p,
170 const apr_array_header_t *targets,
171 svn_boolean_t do_lock,
172 svn_boolean_t force,
173 svn_client_ctx_t *ctx,
174 apr_pool_t *pool)
176 int i;
177 apr_array_header_t *rel_targets = apr_array_make(pool, 1,
178 sizeof(const char *));
179 apr_hash_t *rel_targets_ret = apr_hash_make(pool);
180 apr_pool_t *subpool = svn_pool_create(pool);
182 /* Get the common parent and all relative paths */
183 SVN_ERR(svn_path_condense_targets(common_parent, &rel_targets, targets,
184 FALSE, pool));
186 /* svn_path_condense_targets leaves paths empty if TARGETS only had
187 1 member, so we special case that. */
188 if (apr_is_empty_array(rel_targets))
190 char *base_name = svn_path_basename(*common_parent, pool);
191 *common_parent = svn_path_dirname(*common_parent, pool);
193 APR_ARRAY_PUSH(rel_targets, char *) = base_name;
196 if (*common_parent == NULL || (*common_parent)[0] == '\0')
197 return svn_error_create
198 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
199 _("No common parent found, unable to operate on disjoint arguments"));
201 if (svn_path_is_url(*common_parent))
203 svn_revnum_t *invalid_revnum;
204 invalid_revnum = apr_palloc(pool, sizeof(*invalid_revnum));
205 *invalid_revnum = SVN_INVALID_REVNUM;
206 *parent_adm_access_p = NULL;
208 for (i = 0; i < rel_targets->nelts; i++)
210 const char *target = APR_ARRAY_IDX(rel_targets, i, const char *);
211 apr_hash_set(rel_targets_ret, svn_path_uri_decode(target, pool),
212 APR_HASH_KEY_STRING,
213 do_lock ? (const void *) invalid_revnum
214 : (const void *) "");
216 *rel_fs_paths_p = NULL;
218 else /* common parent is a local path */
220 int max_levels_to_lock = 0;
221 apr_array_header_t *rel_urls;
222 apr_array_header_t *urls = apr_array_make(pool, 1,
223 sizeof(const char *));
224 apr_hash_t *urls_hash = apr_hash_make(pool);
225 const char *common_url;
227 /* Calculate the maximum number of components in the rel_targets, which
228 is the depth to which we need to lock the WC. */
229 for (i = 0; i < rel_targets->nelts; ++i)
231 const char *target = APR_ARRAY_IDX(rel_targets, i, const char *);
232 int n = svn_path_component_count(target);
234 if (n > max_levels_to_lock)
235 max_levels_to_lock = n;
238 SVN_ERR(svn_wc_adm_probe_open3(parent_adm_access_p, NULL,
239 *common_parent,
240 TRUE, max_levels_to_lock,
241 ctx->cancel_func, ctx->cancel_baton,
242 pool));
244 /* Get the url for each target and verify all paths. */
245 for (i = 0; i < rel_targets->nelts; i++)
247 const svn_wc_entry_t *entry;
248 const char *target = APR_ARRAY_IDX(rel_targets, i, const char *);
249 const char *abs_path;
251 svn_pool_clear(subpool);
253 abs_path = svn_path_join
254 (svn_wc_adm_access_path(*parent_adm_access_p), target, subpool);
256 SVN_ERR(svn_wc__entry_versioned(&entry, abs_path,
257 *parent_adm_access_p, FALSE, subpool));
259 if (! entry->url)
260 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
261 _("'%s' has no URL"),
262 svn_path_local_style(target, pool));
264 APR_ARRAY_PUSH(urls, const char *) = apr_pstrdup(pool,
265 entry->url);
268 /* Condense our absolute urls and get the relative urls. */
269 SVN_ERR(svn_path_condense_targets(&common_url, &rel_urls, urls,
270 FALSE, pool));
272 /* svn_path_condense_targets leaves paths empty if TARGETS only had
273 1 member, so we special case that (again). */
274 if (apr_is_empty_array(rel_urls))
276 char *base_name = svn_path_basename(common_url, pool);
277 common_url = svn_path_dirname(common_url, pool);
278 APR_ARRAY_PUSH(rel_urls, char *) = base_name;
281 /* If we have no common URL parent, bail (cross-repos lock attempt) */
282 if (common_url == NULL || (common_url)[0] == '\0')
283 return svn_error_create
284 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
285 _("Unable to lock/unlock across multiple repositories"));
287 /* Now that we've got the relative URLs, gather our targets and
288 store the mapping between relative repository path and WC path. */
289 for (i = 0; i < rel_targets->nelts; i++)
291 const svn_wc_entry_t *entry;
292 const char *target = APR_ARRAY_IDX(rel_targets, i, const char *);
293 const char *url = APR_ARRAY_IDX(rel_urls, i, const char *);
294 const char *abs_path;
295 const char *decoded_url = svn_path_uri_decode(url, pool);
297 svn_pool_clear(subpool);
299 apr_hash_set(urls_hash, decoded_url,
300 APR_HASH_KEY_STRING,
301 apr_pstrdup(pool, target));
303 abs_path = svn_path_join
304 (svn_wc_adm_access_path(*parent_adm_access_p), target, subpool);
306 SVN_ERR(svn_wc_entry(&entry, abs_path, *parent_adm_access_p, FALSE,
307 subpool));
309 if (do_lock) /* Lock. */
311 svn_revnum_t *revnum;
312 revnum = apr_palloc(pool, sizeof(* revnum));
313 *revnum = entry->revision;
315 apr_hash_set(rel_targets_ret, decoded_url,
316 APR_HASH_KEY_STRING, revnum);
318 else /* Unlock. */
320 /* If not force, get the lock token from the WC entry. */
321 if (! force)
323 if (! entry->lock_token)
324 return svn_error_createf
325 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
326 _("'%s' is not locked in this working copy"), target);
328 apr_hash_set(rel_targets_ret, decoded_url,
329 APR_HASH_KEY_STRING,
330 apr_pstrdup(pool, entry->lock_token));
332 else
334 /* If breaking a lock, we shouldn't pass any lock token. */
335 apr_hash_set(rel_targets_ret, decoded_url,
336 APR_HASH_KEY_STRING, "");
341 *rel_fs_paths_p = urls_hash;
342 *common_parent = common_url;
345 *rel_targets_p = rel_targets_ret;
346 svn_pool_destroy(subpool);
347 return SVN_NO_ERROR;
350 /* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
351 setting the values to the fetched tokens, allocated in pool. */
352 static svn_error_t *
353 fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
354 apr_pool_t *pool)
356 apr_hash_index_t *hi;
357 apr_pool_t *iterpool = svn_pool_create(pool);
359 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
361 const void *key;
362 const char *path;
363 svn_lock_t *lock;
365 svn_pool_clear(iterpool);
366 apr_hash_this(hi, &key, NULL, NULL);
367 path = key;
369 SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
371 if (! lock)
372 return svn_error_createf
373 (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
374 _("'%s' is not locked"), path);
376 apr_hash_set(path_tokens, path, APR_HASH_KEY_STRING,
377 apr_pstrdup(pool, lock->token));
380 svn_pool_destroy(iterpool);
381 return SVN_NO_ERROR;
385 svn_error_t *
386 svn_client_lock(const apr_array_header_t *targets,
387 const char *comment,
388 svn_boolean_t steal_lock,
389 svn_client_ctx_t *ctx,
390 apr_pool_t *pool)
392 svn_wc_adm_access_t *adm_access;
393 const char *common_parent;
394 svn_ra_session_t *ra_session;
395 apr_hash_t *path_revs, *urls_to_paths;
396 struct lock_baton cb;
398 if (apr_is_empty_array(targets))
399 return SVN_NO_ERROR;
401 /* Enforce that the comment be xml-escapable. */
402 if (comment)
404 if (! svn_xml_is_xml_safe(comment, strlen(comment)))
405 return svn_error_create
406 (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
407 _("Lock comment contains illegal characters"));
410 SVN_ERR(organize_lock_targets(&common_parent, &adm_access,
411 &path_revs, &urls_to_paths, targets, TRUE,
412 steal_lock, ctx, pool));
414 /* Open an RA session to the common parent of TARGETS. */
415 SVN_ERR(svn_client__open_ra_session_internal
416 (&ra_session, common_parent,
417 adm_access ? svn_wc_adm_access_path(adm_access) : NULL,
418 adm_access, NULL, FALSE, FALSE, ctx, pool));
420 cb.pool = pool;
421 cb.adm_access = adm_access;
422 cb.urls_to_paths = urls_to_paths;
423 cb.ctx = ctx;
425 /* Lock the paths. */
426 SVN_ERR(svn_ra_lock(ra_session, path_revs, comment,
427 steal_lock, store_locks_callback, &cb, pool));
429 /* Unlock the wc. */
430 if (adm_access)
431 SVN_ERR(svn_wc_adm_close(adm_access));
433 return SVN_NO_ERROR;
436 svn_error_t *
437 svn_client_unlock(const apr_array_header_t *targets,
438 svn_boolean_t break_lock,
439 svn_client_ctx_t *ctx,
440 apr_pool_t *pool)
442 svn_wc_adm_access_t *adm_access;
443 const char *common_parent;
444 svn_ra_session_t *ra_session;
445 apr_hash_t *path_tokens, *urls_to_paths;
446 struct lock_baton cb;
448 if (apr_is_empty_array(targets))
449 return SVN_NO_ERROR;
451 SVN_ERR(organize_lock_targets(&common_parent, &adm_access,
452 &path_tokens, &urls_to_paths, targets,
453 FALSE, break_lock, ctx, pool));
455 /* Open an RA session. */
456 SVN_ERR(svn_client__open_ra_session_internal
457 (&ra_session, common_parent,
458 adm_access ? svn_wc_adm_access_path(adm_access) : NULL,
459 adm_access, NULL, FALSE, FALSE, ctx, pool));
461 /* If break_lock is not set, lock tokens are required by the server.
462 If the targets were all URLs, ensure that we provide lock tokens,
463 so the repository will only check that the user owns the
464 locks. */
465 if (! adm_access && !break_lock)
466 SVN_ERR(fetch_tokens(ra_session, path_tokens, pool));
468 cb.pool = pool;
469 cb.adm_access = adm_access;
470 cb.urls_to_paths = urls_to_paths;
471 cb.ctx = ctx;
473 /* Unlock the paths. */
474 SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock,
475 store_locks_callback, &cb, pool));
477 /* Unlock the wc. */
478 if (adm_access)
479 SVN_ERR(svn_wc_adm_close(adm_access));
481 return SVN_NO_ERROR;