1 | /* |
---|
2 | * externals.c: handle the svn:externals property |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2000-2009 CollabNet. All rights reserved. |
---|
6 | * |
---|
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. |
---|
12 | * |
---|
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 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | /* ==================================================================== */ |
---|
20 | |
---|
21 | |
---|
22 | |
---|
23 | /*** Includes. ***/ |
---|
24 | |
---|
25 | #include <apr_uri.h> |
---|
26 | #include "svn_wc.h" |
---|
27 | #include "svn_pools.h" |
---|
28 | #include "svn_client.h" |
---|
29 | #include "svn_hash.h" |
---|
30 | #include "svn_types.h" |
---|
31 | #include "svn_error.h" |
---|
32 | #include "svn_path.h" |
---|
33 | #include "svn_config.h" |
---|
34 | #include "client.h" |
---|
35 | |
---|
36 | #include "svn_private_config.h" |
---|
37 | #include "private/svn_wc_private.h" |
---|
38 | |
---|
39 | |
---|
40 | /* Closure for handle_external_item_change. */ |
---|
41 | struct handle_external_item_change_baton |
---|
42 | { |
---|
43 | /* As returned by svn_wc_parse_externals_description(). */ |
---|
44 | apr_hash_t *new_desc; |
---|
45 | apr_hash_t *old_desc; |
---|
46 | |
---|
47 | /* The directory that has this externals property. */ |
---|
48 | const char *parent_dir; |
---|
49 | |
---|
50 | /* Access baton for parent_dir. If the external is an export, this |
---|
51 | this must be NULL, otherwise it must be non-NULL. */ |
---|
52 | svn_wc_adm_access_t *adm_access; |
---|
53 | |
---|
54 | /* The URL for the directory that has this externals property. */ |
---|
55 | const char *parent_dir_url; |
---|
56 | |
---|
57 | /* The URL for the repository root. */ |
---|
58 | const char *repos_root_url; |
---|
59 | |
---|
60 | /* Passed through to svn_client_* functions. */ |
---|
61 | svn_client_ctx_t *ctx; |
---|
62 | |
---|
63 | svn_boolean_t *timestamp_sleep; |
---|
64 | svn_boolean_t is_export; |
---|
65 | |
---|
66 | /* A long lived pool. Put anything in here that needs to outlive |
---|
67 | the hash diffing callback, such as updates to the hash |
---|
68 | entries. */ |
---|
69 | apr_pool_t *pool; |
---|
70 | |
---|
71 | /* A scratchwork pool -- do not put anything in here that needs to |
---|
72 | outlive the hash diffing callback! */ |
---|
73 | apr_pool_t *iter_pool; |
---|
74 | }; |
---|
75 | |
---|
76 | |
---|
77 | /* Remove the directory at PATH from revision control, and do the same |
---|
78 | * to any revision controlled directories underneath PATH (including |
---|
79 | * directories not referred to by parent svn administrative areas); |
---|
80 | * then if PATH is empty afterwards, remove it, else rename it to a |
---|
81 | * unique name in the same parent directory. |
---|
82 | * |
---|
83 | * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. |
---|
84 | * |
---|
85 | * Use POOL for all temporary allocation. |
---|
86 | * |
---|
87 | * Note: this function is not passed a svn_wc_adm_access_t. Instead, |
---|
88 | * it separately opens the object being deleted, so that if there is a |
---|
89 | * lock on that object, the object cannot be deleted. |
---|
90 | */ |
---|
91 | static svn_error_t * |
---|
92 | relegate_dir_external(const char *path, |
---|
93 | svn_cancel_func_t cancel_func, |
---|
94 | void *cancel_baton, |
---|
95 | apr_pool_t *pool) |
---|
96 | { |
---|
97 | svn_error_t *err = SVN_NO_ERROR; |
---|
98 | svn_wc_adm_access_t *adm_access; |
---|
99 | |
---|
100 | SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, TRUE, -1, cancel_func, |
---|
101 | cancel_baton, pool)); |
---|
102 | err = svn_wc_remove_from_revision_control(adm_access, |
---|
103 | SVN_WC_ENTRY_THIS_DIR, |
---|
104 | TRUE, FALSE, |
---|
105 | cancel_func, |
---|
106 | cancel_baton, |
---|
107 | pool); |
---|
108 | |
---|
109 | /* ### Ugly. Unlock only if not going to return an error. Revisit */ |
---|
110 | if (!err || err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) |
---|
111 | SVN_ERR(svn_wc_adm_close2(adm_access, pool)); |
---|
112 | |
---|
113 | if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
---|
114 | { |
---|
115 | const char *parent_dir; |
---|
116 | const char *dirname; |
---|
117 | const char *new_path; |
---|
118 | |
---|
119 | svn_error_clear(err); |
---|
120 | err = SVN_NO_ERROR; |
---|
121 | |
---|
122 | svn_path_split(path, &parent_dir, &dirname, pool); |
---|
123 | |
---|
124 | /* Reserve the new dir name. */ |
---|
125 | SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, |
---|
126 | parent_dir, dirname, ".OLD", |
---|
127 | svn_io_file_del_none, pool, pool)); |
---|
128 | |
---|
129 | /* Sigh... We must fall ever so slightly from grace. |
---|
130 | |
---|
131 | Ideally, there would be no window, however brief, when we |
---|
132 | don't have a reservation on the new name. Unfortunately, |
---|
133 | at least in the Unix (Linux?) version of apr_file_rename(), |
---|
134 | you can't rename a directory over a file, because it's just |
---|
135 | calling stdio rename(), which says: |
---|
136 | |
---|
137 | ENOTDIR |
---|
138 | A component used as a directory in oldpath or newpath |
---|
139 | path is not, in fact, a directory. Or, oldpath is |
---|
140 | a directory, and newpath exists but is not a directory |
---|
141 | |
---|
142 | So instead, we get the name, then remove the file (ugh), then |
---|
143 | rename the directory, hoping that nobody has gotten that name |
---|
144 | in the meantime -- which would never happen in real life, so |
---|
145 | no big deal. |
---|
146 | */ |
---|
147 | /* Do our best, but no biggy if it fails. The rename will fail. */ |
---|
148 | svn_error_clear(svn_io_remove_file(new_path, pool)); |
---|
149 | |
---|
150 | /* Rename. */ |
---|
151 | SVN_ERR(svn_io_file_rename(path, new_path, pool)); |
---|
152 | } |
---|
153 | |
---|
154 | return err; |
---|
155 | } |
---|
156 | |
---|
157 | /* Try to update a directory external at PATH to URL at REVISION. |
---|
158 | Use POOL for temporary allocations, and use the client context CTX. */ |
---|
159 | static svn_error_t * |
---|
160 | switch_dir_external(const char *path, |
---|
161 | const char *url, |
---|
162 | const svn_opt_revision_t *revision, |
---|
163 | const svn_opt_revision_t *peg_revision, |
---|
164 | svn_boolean_t *timestamp_sleep, |
---|
165 | svn_client_ctx_t *ctx, |
---|
166 | apr_pool_t *pool) |
---|
167 | { |
---|
168 | svn_node_kind_t kind; |
---|
169 | svn_error_t *err; |
---|
170 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
171 | |
---|
172 | /* If path is a directory, try to update/switch to the correct URL |
---|
173 | and revision. */ |
---|
174 | SVN_ERR(svn_io_check_path(path, &kind, pool)); |
---|
175 | if (kind == svn_node_dir) |
---|
176 | { |
---|
177 | svn_wc_adm_access_t *adm_access; |
---|
178 | const svn_wc_entry_t *entry; |
---|
179 | |
---|
180 | SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, TRUE, 0, |
---|
181 | ctx->cancel_func, ctx->cancel_baton, subpool)); |
---|
182 | SVN_ERR(svn_wc_entry(&entry, path, adm_access, |
---|
183 | FALSE, subpool)); |
---|
184 | SVN_ERR(svn_wc_adm_close2(adm_access, subpool)); |
---|
185 | |
---|
186 | if (entry && entry->url) |
---|
187 | { |
---|
188 | /* If we have what appears to be a version controlled |
---|
189 | subdir, and its top-level URL matches that of our |
---|
190 | externals definition, perform an update. */ |
---|
191 | if (strcmp(entry->url, url) == 0) |
---|
192 | { |
---|
193 | SVN_ERR(svn_client__update_internal(NULL, path, revision, |
---|
194 | svn_depth_unknown, FALSE, |
---|
195 | FALSE, FALSE, |
---|
196 | timestamp_sleep, TRUE, |
---|
197 | ctx, subpool)); |
---|
198 | svn_pool_destroy(subpool); |
---|
199 | return SVN_NO_ERROR; |
---|
200 | } |
---|
201 | else if (entry->repos) |
---|
202 | { |
---|
203 | /* URLs don't match. Try to relocate (if necessary) and then |
---|
204 | switch. */ |
---|
205 | if (! svn_path_is_ancestor(entry->repos, url)) |
---|
206 | { |
---|
207 | const char *repos_root; |
---|
208 | svn_ra_session_t *ra_session; |
---|
209 | |
---|
210 | /* Get the repos root of the new URL. */ |
---|
211 | SVN_ERR(svn_client__open_ra_session_internal |
---|
212 | (&ra_session, url, NULL, NULL, NULL, FALSE, TRUE, |
---|
213 | ctx, subpool)); |
---|
214 | SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, |
---|
215 | subpool)); |
---|
216 | |
---|
217 | err = svn_client_relocate(path, entry->repos, repos_root, |
---|
218 | TRUE, ctx, subpool); |
---|
219 | /* If the relocation failed because the new URL points |
---|
220 | to another repository, then we need to relegate and |
---|
221 | check out a new WC. */ |
---|
222 | if (err |
---|
223 | && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION |
---|
224 | || (err->apr_err |
---|
225 | == SVN_ERR_CLIENT_INVALID_RELOCATION))) |
---|
226 | { |
---|
227 | svn_error_clear(err); |
---|
228 | goto relegate; |
---|
229 | } |
---|
230 | else if (err) |
---|
231 | return err; |
---|
232 | } |
---|
233 | |
---|
234 | SVN_ERR(svn_client__switch_internal(NULL, path, url, |
---|
235 | peg_revision, revision, |
---|
236 | NULL, svn_depth_infinity, |
---|
237 | TRUE, timestamp_sleep, |
---|
238 | FALSE, FALSE, ctx, subpool)); |
---|
239 | |
---|
240 | svn_pool_destroy(subpool); |
---|
241 | return SVN_NO_ERROR; |
---|
242 | } |
---|
243 | } |
---|
244 | } |
---|
245 | |
---|
246 | relegate: |
---|
247 | |
---|
248 | /* Fall back on removing the WC and checking out a new one. */ |
---|
249 | |
---|
250 | /* Ensure that we don't have any RA sessions or WC locks from failed |
---|
251 | operations above. */ |
---|
252 | svn_pool_destroy(subpool); |
---|
253 | |
---|
254 | if (kind == svn_node_dir) |
---|
255 | /* Buh-bye, old and busted ... */ |
---|
256 | SVN_ERR(relegate_dir_external(path, |
---|
257 | ctx->cancel_func, |
---|
258 | ctx->cancel_baton, |
---|
259 | pool)); |
---|
260 | else |
---|
261 | { |
---|
262 | /* The target dir might have multiple components. Guarantee |
---|
263 | the path leading down to the last component. */ |
---|
264 | const char *parent = svn_path_dirname(path, pool); |
---|
265 | SVN_ERR(svn_io_make_dir_recursively(parent, pool)); |
---|
266 | } |
---|
267 | |
---|
268 | /* ... Hello, new hotness. */ |
---|
269 | return svn_client__checkout_internal(NULL, url, path, peg_revision, |
---|
270 | revision, NULL, |
---|
271 | SVN_DEPTH_INFINITY_OR_FILES(TRUE), |
---|
272 | FALSE, FALSE, timestamp_sleep, |
---|
273 | ctx, pool); |
---|
274 | } |
---|
275 | |
---|
276 | /* Try to update a file external at PATH to URL at REVISION using a |
---|
277 | access baton that has a write lock. Use POOL for temporary |
---|
278 | allocations, and use the client context CTX. */ |
---|
279 | static svn_error_t * |
---|
280 | switch_file_external(const char *path, |
---|
281 | const char *url, |
---|
282 | const svn_opt_revision_t *peg_revision, |
---|
283 | const svn_opt_revision_t *revision, |
---|
284 | svn_wc_adm_access_t *adm_access, |
---|
285 | svn_ra_session_t *ra_session, |
---|
286 | const char *ra_session_url, |
---|
287 | svn_revnum_t ra_revnum, |
---|
288 | const char *repos_root_url, |
---|
289 | svn_boolean_t *timestamp_sleep, |
---|
290 | svn_client_ctx_t *ctx, |
---|
291 | apr_pool_t *pool) |
---|
292 | { |
---|
293 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
294 | svn_wc_adm_access_t *target_adm_access; |
---|
295 | const char *anchor; |
---|
296 | const char *target; |
---|
297 | const svn_wc_entry_t *entry; |
---|
298 | svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, |
---|
299 | SVN_CONFIG_CATEGORY_CONFIG, |
---|
300 | APR_HASH_KEY_STRING) : NULL; |
---|
301 | svn_boolean_t use_commit_times; |
---|
302 | svn_boolean_t unlink_file = FALSE; |
---|
303 | svn_boolean_t revert_file = FALSE; |
---|
304 | svn_boolean_t remove_from_revision_control = FALSE; |
---|
305 | svn_boolean_t close_adm_access = FALSE; |
---|
306 | svn_error_t *err = NULL; |
---|
307 | |
---|
308 | /* There must be a working copy to place the file external into. */ |
---|
309 | SVN_ERR(svn_wc_get_actual_target(path, &anchor, &target, subpool)); |
---|
310 | |
---|
311 | /* Try to get a access baton for the anchor using the input access |
---|
312 | baton. If this fails and returns SVN_ERR_WC_NOT_LOCKED, then try |
---|
313 | to get a new access baton to support inserting a file external |
---|
314 | into a directory external. */ |
---|
315 | err = svn_wc_adm_retrieve(&target_adm_access, adm_access, anchor, subpool); |
---|
316 | if (err) |
---|
317 | { |
---|
318 | if (err->apr_err == SVN_ERR_WC_NOT_LOCKED) |
---|
319 | { |
---|
320 | const char *dest_wc_repos_root_url; |
---|
321 | svn_opt_revision_t peg_rev; |
---|
322 | |
---|
323 | svn_error_clear(err); |
---|
324 | close_adm_access = TRUE; |
---|
325 | SVN_ERR(svn_wc_adm_open3(&target_adm_access, NULL, anchor, TRUE, 1, |
---|
326 | ctx->cancel_func, ctx->cancel_baton, |
---|
327 | subpool)); |
---|
328 | |
---|
329 | /* Check that the repository root URL for the newly opened |
---|
330 | wc is the same as the file external. */ |
---|
331 | peg_rev.kind = svn_opt_revision_base; |
---|
332 | SVN_ERR(svn_client__get_repos_root(&dest_wc_repos_root_url, |
---|
333 | anchor, &peg_rev, |
---|
334 | target_adm_access, ctx, subpool)); |
---|
335 | |
---|
336 | if (0 != strcmp(repos_root_url, dest_wc_repos_root_url)) |
---|
337 | return svn_error_createf |
---|
338 | (SVN_ERR_RA_REPOS_ROOT_URL_MISMATCH, NULL, |
---|
339 | _("Cannot insert a file external from '%s' into a working " |
---|
340 | "copy from a different repository rooted at '%s'"), |
---|
341 | url, dest_wc_repos_root_url); |
---|
342 | } |
---|
343 | else |
---|
344 | return err; |
---|
345 | } |
---|
346 | |
---|
347 | SVN_ERR(svn_wc_entry(&entry, path, target_adm_access, FALSE, subpool)); |
---|
348 | |
---|
349 | /* Only one notification is done for the external, so don't notify |
---|
350 | for any following steps. Use the following trick to add the file |
---|
351 | then switch it to the external URL. */ |
---|
352 | |
---|
353 | /* See if the user wants last-commit timestamps instead of current ones. */ |
---|
354 | SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, |
---|
355 | SVN_CONFIG_SECTION_MISCELLANY, |
---|
356 | SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); |
---|
357 | |
---|
358 | /* If there is a versioned item with this name, ensure it's a file |
---|
359 | external before working with it. If there is no entry in the |
---|
360 | working copy, then create an empty file and add it to the working |
---|
361 | copy. */ |
---|
362 | if (entry) |
---|
363 | { |
---|
364 | if (! entry->file_external_path) |
---|
365 | { |
---|
366 | if (close_adm_access) |
---|
367 | SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool)); |
---|
368 | |
---|
369 | return svn_error_createf |
---|
370 | (SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, |
---|
371 | _("The file external from '%s' cannot overwrite the existing " |
---|
372 | "versioned item at '%s'"), |
---|
373 | url, path); |
---|
374 | } |
---|
375 | } |
---|
376 | else |
---|
377 | { |
---|
378 | const svn_wc_entry_t *anchor_dir_entry; |
---|
379 | apr_file_t *f; |
---|
380 | svn_boolean_t text_conflicted; |
---|
381 | svn_boolean_t prop_conflicted; |
---|
382 | svn_boolean_t tree_conflicted; |
---|
383 | |
---|
384 | /* Check for a conflict on the containing directory. Because a |
---|
385 | switch is done on the added file later, it will leave a |
---|
386 | conflict on the directory. To prevent resolving a conflict |
---|
387 | due to another change on the directory, do not allow a file |
---|
388 | external to be added when one exists. */ |
---|
389 | SVN_ERR(svn_wc__entry_versioned(&anchor_dir_entry, anchor, |
---|
390 | target_adm_access, FALSE, subpool)); |
---|
391 | SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, |
---|
392 | &tree_conflicted, anchor, target_adm_access, |
---|
393 | subpool)); |
---|
394 | if (text_conflicted || prop_conflicted || tree_conflicted) |
---|
395 | return svn_error_createf |
---|
396 | (SVN_ERR_WC_FOUND_CONFLICT, 0, |
---|
397 | _("The file external from '%s' cannot be written to '%s' while " |
---|
398 | "'%s' remains in conflict"), |
---|
399 | url, path, anchor); |
---|
400 | |
---|
401 | /* Try to create an empty file. If there is a file already |
---|
402 | there, then don't touch it. */ |
---|
403 | SVN_ERR(svn_io_file_open(&f, |
---|
404 | path, |
---|
405 | APR_WRITE | APR_CREATE | APR_EXCL, |
---|
406 | APR_OS_DEFAULT, |
---|
407 | subpool)); |
---|
408 | unlink_file = TRUE; |
---|
409 | err = svn_io_file_close(f, pool); |
---|
410 | if (err) |
---|
411 | goto cleanup; |
---|
412 | |
---|
413 | err = svn_wc_add3(path, target_adm_access, svn_depth_infinity, |
---|
414 | NULL, /* const char *copyfrom_url */ |
---|
415 | SVN_INVALID_REVNUM, /* svn_revnum_t copyfrom_rev */ |
---|
416 | ctx->cancel_func, ctx->cancel_baton, |
---|
417 | NULL, /* svn_wc_notify_func2_t notify_func */ |
---|
418 | NULL, /* void *notify_baton */ |
---|
419 | subpool); |
---|
420 | if (err) |
---|
421 | goto cleanup; |
---|
422 | revert_file = TRUE; |
---|
423 | |
---|
424 | err = svn_wc__set_file_external_location(target_adm_access, target, |
---|
425 | url, peg_revision, revision, |
---|
426 | repos_root_url, subpool); |
---|
427 | if (err) |
---|
428 | goto cleanup; |
---|
429 | } |
---|
430 | |
---|
431 | err = svn_client__switch_internal(NULL, path, url, peg_revision, revision, |
---|
432 | target_adm_access, svn_depth_empty, |
---|
433 | FALSE, /* depth_is_sticky */ |
---|
434 | timestamp_sleep, |
---|
435 | TRUE, /* ignore_externals */ |
---|
436 | FALSE, /* allow_unver_obstructions */ |
---|
437 | ctx, |
---|
438 | pool); |
---|
439 | if (err) |
---|
440 | goto cleanup; |
---|
441 | |
---|
442 | /* Do some additional work if the file external is newly added to |
---|
443 | the wc. */ |
---|
444 | if (unlink_file) |
---|
445 | { |
---|
446 | /* At this point the newly created file external is switched, so |
---|
447 | if there is an error, to back everything out the file cannot |
---|
448 | be reverted, it needs to be removed forcibly from the wc.. */ |
---|
449 | revert_file = FALSE; |
---|
450 | remove_from_revision_control = TRUE; |
---|
451 | |
---|
452 | if (err) |
---|
453 | goto cleanup; |
---|
454 | } |
---|
455 | |
---|
456 | if (close_adm_access) |
---|
457 | SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool)); |
---|
458 | |
---|
459 | /* ### should destroy the subpool... */ |
---|
460 | |
---|
461 | return SVN_NO_ERROR; |
---|
462 | |
---|
463 | cleanup: |
---|
464 | if (revert_file) |
---|
465 | { |
---|
466 | svn_error_t *e = |
---|
467 | svn_wc_revert3(path, target_adm_access, svn_depth_empty, |
---|
468 | use_commit_times, |
---|
469 | NULL, /* apr_array_header_t *changelists */ |
---|
470 | ctx->cancel_func, |
---|
471 | ctx->cancel_baton, |
---|
472 | NULL, /* svn_wc_notify_func2_t */ |
---|
473 | NULL, /* void *notify_baton */ |
---|
474 | subpool); |
---|
475 | if (e) |
---|
476 | svn_error_clear(e); |
---|
477 | } |
---|
478 | |
---|
479 | if (remove_from_revision_control) |
---|
480 | { |
---|
481 | svn_error_t * e = svn_wc_remove_from_revision_control(target_adm_access, |
---|
482 | target, |
---|
483 | TRUE, FALSE, |
---|
484 | ctx->cancel_func, |
---|
485 | ctx->cancel_baton, |
---|
486 | subpool); |
---|
487 | if (e) |
---|
488 | svn_error_clear(e); |
---|
489 | } |
---|
490 | |
---|
491 | if (unlink_file) |
---|
492 | { |
---|
493 | svn_error_t *e = svn_io_remove_file(path, subpool); |
---|
494 | if (e) |
---|
495 | svn_error_clear(e); |
---|
496 | } |
---|
497 | |
---|
498 | if (close_adm_access) |
---|
499 | SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool)); |
---|
500 | |
---|
501 | /* ### should destroy the subpool */ |
---|
502 | |
---|
503 | return err; |
---|
504 | } |
---|
505 | |
---|
506 | /* Return the scheme of @a uri in @a scheme allocated from @a pool. |
---|
507 | If @a uri does not appear to be a valid URI, then @a scheme will |
---|
508 | not be updated. */ |
---|
509 | static svn_error_t * |
---|
510 | uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool) |
---|
511 | { |
---|
512 | apr_size_t i; |
---|
513 | |
---|
514 | for (i = 0; uri[i] && uri[i] != ':'; ++i) |
---|
515 | if (uri[i] == '/') |
---|
516 | goto error; |
---|
517 | |
---|
518 | if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/') |
---|
519 | { |
---|
520 | *scheme = apr_pstrmemdup(pool, uri, i); |
---|
521 | return SVN_NO_ERROR; |
---|
522 | } |
---|
523 | |
---|
524 | error: |
---|
525 | return svn_error_createf(SVN_ERR_BAD_URL, 0, |
---|
526 | _("URL '%s' does not begin with a scheme"), |
---|
527 | uri); |
---|
528 | } |
---|
529 | |
---|
530 | /* If the URL for @a item is relative, then using the repository root |
---|
531 | URL @a repos_root_url and the parent directory URL @parent_dir_url, |
---|
532 | resolve it into an absolute URL and save it in @a item. |
---|
533 | |
---|
534 | Regardless if the URL is absolute or not, if there are no errors, |
---|
535 | the URL in @a item will be canonicalized. |
---|
536 | |
---|
537 | The following relative URL formats are supported: |
---|
538 | |
---|
539 | ../ relative to the parent directory of the external |
---|
540 | ^/ relative to the repository root |
---|
541 | // relative to the scheme |
---|
542 | / relative to the server's hostname |
---|
543 | |
---|
544 | The ../ and ^/ relative URLs may use .. to remove path elements up |
---|
545 | to the server root. |
---|
546 | |
---|
547 | The external URL should not be canonicalized otherwise the scheme |
---|
548 | relative URL '//host/some/path' would have been canonicalized to |
---|
549 | '/host/some/path' and we would not be able to match on the leading |
---|
550 | '//'. */ |
---|
551 | static svn_error_t * |
---|
552 | resolve_relative_external_url(svn_wc_external_item2_t *item, |
---|
553 | const char *repos_root_url, |
---|
554 | const char *parent_dir_url, |
---|
555 | apr_pool_t *pool) |
---|
556 | { |
---|
557 | const char *uncanonicalized_url = item->url; |
---|
558 | const char *canonicalized_url; |
---|
559 | apr_uri_t parent_dir_parsed_uri; |
---|
560 | apr_status_t status; |
---|
561 | |
---|
562 | canonicalized_url = svn_path_canonicalize(uncanonicalized_url, pool); |
---|
563 | |
---|
564 | /* If the URL is already absolute, there is nothing to do. */ |
---|
565 | if (svn_path_is_url(canonicalized_url)) |
---|
566 | { |
---|
567 | item->url = canonicalized_url; |
---|
568 | return SVN_NO_ERROR; |
---|
569 | } |
---|
570 | |
---|
571 | /* Parse the parent directory URL into its parts. */ |
---|
572 | status = apr_uri_parse(pool, parent_dir_url, &parent_dir_parsed_uri); |
---|
573 | if (status) |
---|
574 | return svn_error_createf(SVN_ERR_BAD_URL, 0, |
---|
575 | _("Illegal parent directory URL '%s'"), |
---|
576 | parent_dir_url); |
---|
577 | |
---|
578 | /* If the parent directory URL is at the server root, then the URL |
---|
579 | may have no / after the hostname so apr_uri_parse() will leave |
---|
580 | the URL's path as NULL. */ |
---|
581 | if (! parent_dir_parsed_uri.path) |
---|
582 | parent_dir_parsed_uri.path = apr_pstrmemdup(pool, "/", 1); |
---|
583 | |
---|
584 | /* Handle URLs relative to the current directory or to the |
---|
585 | repository root. The backpaths may only remove path elements, |
---|
586 | not the hostname. This allows an external to refer to another |
---|
587 | repository in the same server relative to the location of this |
---|
588 | repository, say using SVNParentPath. */ |
---|
589 | if ((0 == strncmp("../", uncanonicalized_url, 3)) || |
---|
590 | (0 == strncmp("^/", uncanonicalized_url, 2))) |
---|
591 | { |
---|
592 | apr_array_header_t *base_components; |
---|
593 | apr_array_header_t *relative_components; |
---|
594 | int i; |
---|
595 | |
---|
596 | /* Decompose either the parent directory's URL path or the |
---|
597 | repository root's URL path into components. */ |
---|
598 | if (0 == strncmp("../", uncanonicalized_url, 3)) |
---|
599 | { |
---|
600 | base_components = svn_path_decompose(parent_dir_parsed_uri.path, |
---|
601 | pool); |
---|
602 | relative_components = svn_path_decompose(canonicalized_url, pool); |
---|
603 | } |
---|
604 | else |
---|
605 | { |
---|
606 | apr_uri_t repos_root_parsed_uri; |
---|
607 | |
---|
608 | status = apr_uri_parse(pool, repos_root_url, &repos_root_parsed_uri); |
---|
609 | if (status) |
---|
610 | return svn_error_createf(SVN_ERR_BAD_URL, 0, |
---|
611 | _("Illegal repository root URL '%s'"), |
---|
612 | repos_root_url); |
---|
613 | |
---|
614 | /* If the repository root URL is at the server root, then |
---|
615 | the URL may have no / after the hostname so |
---|
616 | apr_uri_parse() will leave the URL's path as NULL. */ |
---|
617 | if (! repos_root_parsed_uri.path) |
---|
618 | repos_root_parsed_uri.path = apr_pstrmemdup(pool, "/", 1); |
---|
619 | |
---|
620 | base_components = svn_path_decompose(repos_root_parsed_uri.path, |
---|
621 | pool); |
---|
622 | relative_components = svn_path_decompose(canonicalized_url + 2, |
---|
623 | pool); |
---|
624 | } |
---|
625 | |
---|
626 | for (i = 0; i < relative_components->nelts; ++i) |
---|
627 | { |
---|
628 | const char *component = APR_ARRAY_IDX(relative_components, |
---|
629 | i, |
---|
630 | const char *); |
---|
631 | if (0 == strcmp("..", component)) |
---|
632 | { |
---|
633 | /* Constructing the final absolute URL together with |
---|
634 | apr_uri_unparse() requires that the path be absolute, |
---|
635 | so only pop a component if the component being popped |
---|
636 | is not the component for the root directory. */ |
---|
637 | if (base_components->nelts > 1) |
---|
638 | apr_array_pop(base_components); |
---|
639 | } |
---|
640 | else |
---|
641 | APR_ARRAY_PUSH(base_components, const char *) = component; |
---|
642 | } |
---|
643 | |
---|
644 | parent_dir_parsed_uri.path = (char *)svn_path_compose(base_components, |
---|
645 | pool); |
---|
646 | parent_dir_parsed_uri.query = NULL; |
---|
647 | parent_dir_parsed_uri.fragment = NULL; |
---|
648 | |
---|
649 | item->url = apr_uri_unparse(pool, &parent_dir_parsed_uri, 0); |
---|
650 | |
---|
651 | return SVN_NO_ERROR; |
---|
652 | } |
---|
653 | |
---|
654 | /* The remaining URLs are relative to the either the scheme or |
---|
655 | server root and can only refer to locations inside that scope, so |
---|
656 | backpaths are not allowed. */ |
---|
657 | if (svn_path_is_backpath_present(canonicalized_url + 2)) |
---|
658 | return svn_error_createf(SVN_ERR_BAD_URL, 0, |
---|
659 | _("The external relative URL '%s' cannot have " |
---|
660 | "backpaths, i.e. '..'"), |
---|
661 | uncanonicalized_url); |
---|
662 | |
---|
663 | /* Relative to the scheme. */ |
---|
664 | if (0 == strncmp("//", uncanonicalized_url, 2)) |
---|
665 | { |
---|
666 | const char *scheme; |
---|
667 | |
---|
668 | SVN_ERR(uri_scheme(&scheme, repos_root_url, pool)); |
---|
669 | item->url = svn_path_canonicalize(apr_pstrcat(pool, |
---|
670 | scheme, |
---|
671 | ":", |
---|
672 | uncanonicalized_url, |
---|
673 | NULL), |
---|
674 | pool); |
---|
675 | return SVN_NO_ERROR; |
---|
676 | } |
---|
677 | |
---|
678 | /* Relative to the server root. */ |
---|
679 | if (uncanonicalized_url[0] == '/') |
---|
680 | { |
---|
681 | parent_dir_parsed_uri.path = (char *)canonicalized_url; |
---|
682 | parent_dir_parsed_uri.query = NULL; |
---|
683 | parent_dir_parsed_uri.fragment = NULL; |
---|
684 | |
---|
685 | item->url = apr_uri_unparse(pool, &parent_dir_parsed_uri, 0); |
---|
686 | |
---|
687 | return SVN_NO_ERROR; |
---|
688 | } |
---|
689 | |
---|
690 | return svn_error_createf(SVN_ERR_BAD_URL, 0, |
---|
691 | _("Unrecognized format for the relative external " |
---|
692 | "URL '%s'"), |
---|
693 | uncanonicalized_url); |
---|
694 | } |
---|
695 | |
---|
696 | /* This implements the 'svn_hash_diff_func_t' interface. |
---|
697 | BATON is of type 'struct handle_external_item_change_baton *'. */ |
---|
698 | static svn_error_t * |
---|
699 | handle_external_item_change(const void *key, apr_ssize_t klen, |
---|
700 | enum svn_hash_diff_key_status status, |
---|
701 | void *baton) |
---|
702 | { |
---|
703 | struct handle_external_item_change_baton *ib = baton; |
---|
704 | svn_wc_external_item2_t *old_item, *new_item; |
---|
705 | const char *parent; |
---|
706 | const char *path = svn_path_join(ib->parent_dir, |
---|
707 | (const char *) key, ib->iter_pool); |
---|
708 | svn_ra_session_t *ra_session; |
---|
709 | svn_node_kind_t kind; |
---|
710 | svn_client__ra_session_from_path_results ra_cache = { 0 }; |
---|
711 | |
---|
712 | /* Don't bother to check status, since we'll get that for free by |
---|
713 | attempting to retrieve the hash values anyway. */ |
---|
714 | |
---|
715 | /* When creating the absolute URL, use the pool and not the |
---|
716 | iterpool, since the hash table values outlive the iterpool and |
---|
717 | any pointers they have should also outlive the iterpool. */ |
---|
718 | if ((ib->old_desc) && (! ib->is_export)) |
---|
719 | { |
---|
720 | old_item = apr_hash_get(ib->old_desc, key, klen); |
---|
721 | if (old_item) |
---|
722 | SVN_ERR(resolve_relative_external_url(old_item, ib->repos_root_url, |
---|
723 | ib->parent_dir_url, |
---|
724 | ib->pool)); |
---|
725 | } |
---|
726 | else |
---|
727 | old_item = NULL; |
---|
728 | |
---|
729 | if (ib->new_desc) |
---|
730 | { |
---|
731 | new_item = apr_hash_get(ib->new_desc, key, klen); |
---|
732 | if (new_item) |
---|
733 | SVN_ERR(resolve_relative_external_url(new_item, ib->repos_root_url, |
---|
734 | ib->parent_dir_url, |
---|
735 | ib->pool)); |
---|
736 | } |
---|
737 | else |
---|
738 | new_item = NULL; |
---|
739 | |
---|
740 | /* We couldn't possibly be here if both values were null, right? */ |
---|
741 | SVN_ERR_ASSERT(old_item || new_item); |
---|
742 | |
---|
743 | /* There's one potential ugliness. If a target subdir changed, but |
---|
744 | its URL did not, then ideally we'd just rename the subdir, rather |
---|
745 | than remove the old subdir only to do a new checkout into the new |
---|
746 | subdir. |
---|
747 | |
---|
748 | We could solve this by "sneaking around the back" and looking in |
---|
749 | ib->new_desc, ib->old_desc to check if anything else in this |
---|
750 | parent_dir has the same URL. Of course, if an external gets |
---|
751 | moved into some other directory, then we'd lose anyway. The only |
---|
752 | way to fully handle this would be to harvest a global list based |
---|
753 | on urls/revs, and consult the list every time we're about to |
---|
754 | delete an external subdir: whenever a deletion is really part of |
---|
755 | a rename, then we'd do the rename on the spot. |
---|
756 | |
---|
757 | IMHO, renames aren't going to be frequent enough to make the |
---|
758 | extra bookkeeping worthwhile. |
---|
759 | */ |
---|
760 | |
---|
761 | /* If the external is being checked out, exported or updated, |
---|
762 | determine if the external is a file or directory. */ |
---|
763 | if (new_item) |
---|
764 | { |
---|
765 | /* Get the RA connection. */ |
---|
766 | SVN_ERR(svn_client__ra_session_from_path(&ra_session, |
---|
767 | &ra_cache.ra_revnum, |
---|
768 | &ra_cache.ra_session_url, |
---|
769 | new_item->url, NULL, |
---|
770 | &(new_item->peg_revision), |
---|
771 | &(new_item->revision), ib->ctx, |
---|
772 | ib->pool)); |
---|
773 | |
---|
774 | SVN_ERR(svn_ra_get_uuid2(ra_session, &ra_cache.repos_uuid, ib->pool)); |
---|
775 | SVN_ERR(svn_ra_get_repos_root2(ra_session, &ra_cache.repos_root_url, |
---|
776 | ib->pool)); |
---|
777 | SVN_ERR(svn_ra_check_path(ra_session, "", ra_cache.ra_revnum, &kind, |
---|
778 | ib->pool)); |
---|
779 | |
---|
780 | if (svn_node_none == kind) |
---|
781 | return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
---|
782 | _("URL '%s' at revision %ld doesn't exist"), |
---|
783 | ra_cache.ra_session_url, |
---|
784 | ra_cache.ra_revnum); |
---|
785 | |
---|
786 | if (svn_node_dir != kind && svn_node_file != kind) |
---|
787 | return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
---|
788 | _("URL '%s' at revision %ld is not a file " |
---|
789 | "or a directory"), |
---|
790 | ra_cache.ra_session_url, |
---|
791 | ra_cache.ra_revnum); |
---|
792 | |
---|
793 | ra_cache.kind_p = &kind; |
---|
794 | } |
---|
795 | |
---|
796 | /* Not protecting against recursive externals. Detecting them in |
---|
797 | the global case is hard, and it should be pretty obvious to a |
---|
798 | user when it happens. Worst case: your disk fills up :-). */ |
---|
799 | |
---|
800 | if (! old_item) |
---|
801 | { |
---|
802 | /* This branch is only used during a checkout or an export. */ |
---|
803 | |
---|
804 | /* First notify that we're about to handle an external. */ |
---|
805 | if (ib->ctx->notify_func2) |
---|
806 | (*ib->ctx->notify_func2) |
---|
807 | (ib->ctx->notify_baton2, |
---|
808 | svn_wc_create_notify(path, svn_wc_notify_update_external, |
---|
809 | ib->iter_pool), ib->iter_pool); |
---|
810 | |
---|
811 | switch (*ra_cache.kind_p) |
---|
812 | { |
---|
813 | case svn_node_dir: |
---|
814 | /* The target dir might have multiple components. Guarantee |
---|
815 | the path leading down to the last component. */ |
---|
816 | svn_path_split(path, &parent, NULL, ib->iter_pool); |
---|
817 | SVN_ERR(svn_io_make_dir_recursively(parent, ib->iter_pool)); |
---|
818 | |
---|
819 | /* If we were handling renames the fancy way, then before |
---|
820 | checking out a new subdir here, we would somehow learn if |
---|
821 | it's really just a rename of an old one. That would work in |
---|
822 | tandem with the next case -- this case would do nothing, |
---|
823 | knowing that the next case either already has, or soon will, |
---|
824 | rename the external subdirectory. */ |
---|
825 | |
---|
826 | if (ib->is_export) |
---|
827 | /* ### It should be okay to "force" this export. Externals |
---|
828 | only get created in subdirectories of versioned |
---|
829 | directories, so an external directory couldn't already |
---|
830 | exist before the parent export process unless a versioned |
---|
831 | directory above it did, which means the user would have |
---|
832 | already had to force these creations to occur. */ |
---|
833 | SVN_ERR(svn_client_export4(NULL, new_item->url, path, |
---|
834 | &(new_item->peg_revision), |
---|
835 | &(new_item->revision), |
---|
836 | TRUE, FALSE, svn_depth_infinity, NULL, |
---|
837 | ib->ctx, ib->iter_pool)); |
---|
838 | else |
---|
839 | SVN_ERR(svn_client__checkout_internal |
---|
840 | (NULL, new_item->url, path, |
---|
841 | &(new_item->peg_revision), &(new_item->revision), |
---|
842 | &ra_cache, |
---|
843 | SVN_DEPTH_INFINITY_OR_FILES(TRUE), |
---|
844 | FALSE, FALSE, ib->timestamp_sleep, ib->ctx, |
---|
845 | ib->iter_pool)); |
---|
846 | break; |
---|
847 | case svn_node_file: |
---|
848 | if (ib->is_export) |
---|
849 | /* Do not overwrite an existing file with this file |
---|
850 | external. */ |
---|
851 | SVN_ERR(svn_client_export4(NULL, new_item->url, path, |
---|
852 | &(new_item->peg_revision), |
---|
853 | &(new_item->revision), |
---|
854 | FALSE, TRUE, svn_depth_infinity, NULL, |
---|
855 | ib->ctx, ib->iter_pool)); |
---|
856 | else |
---|
857 | SVN_ERR(switch_file_external(path, |
---|
858 | new_item->url, |
---|
859 | &new_item->peg_revision, |
---|
860 | &new_item->revision, |
---|
861 | ib->adm_access, |
---|
862 | ra_session, |
---|
863 | ra_cache.ra_session_url, |
---|
864 | ra_cache.ra_revnum, |
---|
865 | ra_cache.repos_root_url, |
---|
866 | ib->timestamp_sleep, ib->ctx, |
---|
867 | ib->iter_pool)); |
---|
868 | break; |
---|
869 | default: |
---|
870 | SVN_ERR_MALFUNCTION(); |
---|
871 | break; |
---|
872 | } |
---|
873 | } |
---|
874 | else if (! new_item) |
---|
875 | { |
---|
876 | /* This branch is only used when an external is deleted from the |
---|
877 | repository and the working copy is updated. */ |
---|
878 | |
---|
879 | /* See comment in above case about fancy rename handling. Here, |
---|
880 | before removing an old subdir, we would see if it wants to |
---|
881 | just be renamed to a new one. */ |
---|
882 | |
---|
883 | svn_error_t *err; |
---|
884 | svn_wc_adm_access_t *adm_access; |
---|
885 | svn_boolean_t close_access_baton_when_done; |
---|
886 | |
---|
887 | const char *what_to_remove; |
---|
888 | |
---|
889 | /* Determine if a directory or file external is being removed. |
---|
890 | Try to handle the case when the user deletes the external by |
---|
891 | hand or it is missing. First try to open the directory for a |
---|
892 | directory external. If that doesn't work, get the access |
---|
893 | baton for the parent directory and remove the entry for the |
---|
894 | external. */ |
---|
895 | err = svn_wc_adm_open3(&adm_access, NULL, path, TRUE, -1, |
---|
896 | ib->ctx->cancel_func, ib->ctx->cancel_baton, |
---|
897 | ib->iter_pool); |
---|
898 | if (err) |
---|
899 | { |
---|
900 | const char *anchor; |
---|
901 | const char *target; |
---|
902 | svn_error_t *err2; |
---|
903 | |
---|
904 | SVN_ERR(svn_wc_get_actual_target(path, &anchor, &target, |
---|
905 | ib->iter_pool)); |
---|
906 | |
---|
907 | err2 = svn_wc_adm_retrieve(&adm_access, ib->adm_access, anchor, |
---|
908 | ib->iter_pool); |
---|
909 | if (err2) |
---|
910 | { |
---|
911 | svn_error_clear(err2); |
---|
912 | return err; |
---|
913 | } |
---|
914 | else |
---|
915 | { |
---|
916 | svn_error_clear(err); |
---|
917 | } |
---|
918 | close_access_baton_when_done = FALSE; |
---|
919 | what_to_remove = target; |
---|
920 | } |
---|
921 | else |
---|
922 | { |
---|
923 | close_access_baton_when_done = TRUE; |
---|
924 | what_to_remove = SVN_WC_ENTRY_THIS_DIR; |
---|
925 | } |
---|
926 | |
---|
927 | /* We don't use relegate_dir_external() here, because we know that |
---|
928 | nothing else in this externals description (at least) is |
---|
929 | going to need this directory, and therefore it's better to |
---|
930 | leave stuff where the user expects it. */ |
---|
931 | err = svn_wc_remove_from_revision_control |
---|
932 | (adm_access, what_to_remove, TRUE, FALSE, |
---|
933 | ib->ctx->cancel_func, ib->ctx->cancel_baton, ib->iter_pool); |
---|
934 | |
---|
935 | /* ### Ugly. Unlock only if not going to return an error. Revisit */ |
---|
936 | if (close_access_baton_when_done && |
---|
937 | (!err || err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
---|
938 | { |
---|
939 | svn_error_t *err2 = svn_wc_adm_close2(adm_access, ib->iter_pool); |
---|
940 | if (err2) |
---|
941 | { |
---|
942 | if (!err) |
---|
943 | err = err2; |
---|
944 | else |
---|
945 | svn_error_clear(err2); |
---|
946 | } |
---|
947 | } |
---|
948 | |
---|
949 | if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD)) |
---|
950 | return err; |
---|
951 | svn_error_clear(err); |
---|
952 | |
---|
953 | /* ### If there were multiple path components leading down to |
---|
954 | that wc, we could try to remove them too. */ |
---|
955 | } |
---|
956 | else |
---|
957 | { |
---|
958 | /* This branch handles all other changes. */ |
---|
959 | |
---|
960 | /* First notify that we're about to handle an external. */ |
---|
961 | if (ib->ctx->notify_func2) |
---|
962 | (*ib->ctx->notify_func2) |
---|
963 | (ib->ctx->notify_baton2, |
---|
964 | svn_wc_create_notify(path, svn_wc_notify_update_external, |
---|
965 | ib->iter_pool), ib->iter_pool); |
---|
966 | |
---|
967 | /* Either the URL changed, or the exact same item is present in |
---|
968 | both hashes, and caller wants to update such unchanged items. |
---|
969 | In the latter case, the call below will try to make sure that |
---|
970 | the external really is a WC pointing to the correct |
---|
971 | URL/revision. */ |
---|
972 | switch (*ra_cache.kind_p) |
---|
973 | { |
---|
974 | case svn_node_dir: |
---|
975 | SVN_ERR(switch_dir_external(path, new_item->url, |
---|
976 | &(new_item->revision), |
---|
977 | &(new_item->peg_revision), |
---|
978 | ib->timestamp_sleep, ib->ctx, |
---|
979 | ib->iter_pool)); |
---|
980 | break; |
---|
981 | case svn_node_file: |
---|
982 | SVN_ERR(switch_file_external(path, |
---|
983 | new_item->url, |
---|
984 | &new_item->peg_revision, |
---|
985 | &new_item->revision, |
---|
986 | ib->adm_access, |
---|
987 | ra_session, |
---|
988 | ra_cache.ra_session_url, |
---|
989 | ra_cache.ra_revnum, |
---|
990 | ra_cache.repos_root_url, |
---|
991 | ib->timestamp_sleep, ib->ctx, |
---|
992 | ib->iter_pool)); |
---|
993 | break; |
---|
994 | default: |
---|
995 | SVN_ERR_MALFUNCTION(); |
---|
996 | break; |
---|
997 | } |
---|
998 | } |
---|
999 | |
---|
1000 | /* Clear ib->iter_pool -- we only use it for scratchwork (and this will |
---|
1001 | close any RA sessions still open in this pool). */ |
---|
1002 | svn_pool_clear(ib->iter_pool); |
---|
1003 | |
---|
1004 | return SVN_NO_ERROR; |
---|
1005 | } |
---|
1006 | |
---|
1007 | |
---|
1008 | static svn_error_t * |
---|
1009 | handle_external_item_change_wrapper(const void *key, apr_ssize_t klen, |
---|
1010 | enum svn_hash_diff_key_status status, |
---|
1011 | void *baton) |
---|
1012 | { |
---|
1013 | struct handle_external_item_change_baton *ib = baton; |
---|
1014 | svn_error_t *err = handle_external_item_change(key, klen, status, baton); |
---|
1015 | if (err && ib->ctx->notify_func2) |
---|
1016 | { |
---|
1017 | const char *path = svn_path_join(ib->parent_dir, key, ib->iter_pool); |
---|
1018 | svn_wc_notify_t *notifier = |
---|
1019 | svn_wc_create_notify(path, |
---|
1020 | svn_wc_notify_failed_external, |
---|
1021 | ib->pool); |
---|
1022 | notifier->err = err; |
---|
1023 | ib->ctx->notify_func2(ib->ctx->notify_baton2, notifier, ib->pool); |
---|
1024 | } |
---|
1025 | svn_error_clear(err); |
---|
1026 | return SVN_NO_ERROR; |
---|
1027 | } |
---|
1028 | |
---|
1029 | |
---|
1030 | /* Closure for handle_externals_change. */ |
---|
1031 | struct handle_externals_desc_change_baton |
---|
1032 | { |
---|
1033 | /* As returned by svn_wc_edited_externals(). */ |
---|
1034 | apr_hash_t *externals_new; |
---|
1035 | apr_hash_t *externals_old; |
---|
1036 | |
---|
1037 | /* The requested depth of the driving operation (e.g., update, switch). */ |
---|
1038 | svn_depth_t requested_depth; |
---|
1039 | |
---|
1040 | /* As returned by svn_wc_traversed_depths(). NULL means no ambient |
---|
1041 | depths available (e.g., svn export). */ |
---|
1042 | apr_hash_t *ambient_depths; |
---|
1043 | |
---|
1044 | /* These two map a URL to a path where the URL is either checked out |
---|
1045 | to or exported to. The to_path must be a substring of the |
---|
1046 | external item parent directory path. */ |
---|
1047 | const char *from_url; |
---|
1048 | const char *to_path; |
---|
1049 | |
---|
1050 | /* Passed through to handle_external_item_change_baton. */ |
---|
1051 | svn_wc_adm_access_t *adm_access; |
---|
1052 | svn_client_ctx_t *ctx; |
---|
1053 | const char *repos_root_url; |
---|
1054 | svn_boolean_t *timestamp_sleep; |
---|
1055 | svn_boolean_t is_export; |
---|
1056 | |
---|
1057 | apr_pool_t *pool; |
---|
1058 | }; |
---|
1059 | |
---|
1060 | |
---|
1061 | /* This implements the 'svn_hash_diff_func_t' interface. |
---|
1062 | BATON is of type 'struct handle_externals_desc_change_baton *'. |
---|
1063 | */ |
---|
1064 | static svn_error_t * |
---|
1065 | handle_externals_desc_change(const void *key, apr_ssize_t klen, |
---|
1066 | enum svn_hash_diff_key_status status, |
---|
1067 | void *baton) |
---|
1068 | { |
---|
1069 | struct handle_externals_desc_change_baton *cb = baton; |
---|
1070 | struct handle_external_item_change_baton ib = { 0 }; |
---|
1071 | const char *old_desc_text, *new_desc_text; |
---|
1072 | apr_array_header_t *old_desc, *new_desc; |
---|
1073 | apr_hash_t *old_desc_hash, *new_desc_hash; |
---|
1074 | apr_size_t len; |
---|
1075 | int i; |
---|
1076 | svn_wc_external_item2_t *item; |
---|
1077 | const char *ambient_depth_w; |
---|
1078 | svn_depth_t ambient_depth; |
---|
1079 | |
---|
1080 | if (cb->is_export) |
---|
1081 | SVN_ERR_ASSERT(!cb->adm_access); |
---|
1082 | else |
---|
1083 | SVN_ERR_ASSERT(cb->adm_access); |
---|
1084 | |
---|
1085 | if (cb->ambient_depths) |
---|
1086 | { |
---|
1087 | ambient_depth_w = apr_hash_get(cb->ambient_depths, key, klen); |
---|
1088 | if (ambient_depth_w == NULL) |
---|
1089 | { |
---|
1090 | return svn_error_createf |
---|
1091 | (SVN_ERR_WC_CORRUPT, NULL, |
---|
1092 | _("Traversal of '%s' found no ambient depth"), |
---|
1093 | (const char *) key); |
---|
1094 | } |
---|
1095 | else |
---|
1096 | { |
---|
1097 | ambient_depth = svn_depth_from_word(ambient_depth_w); |
---|
1098 | } |
---|
1099 | } |
---|
1100 | else |
---|
1101 | { |
---|
1102 | ambient_depth = svn_depth_infinity; |
---|
1103 | } |
---|
1104 | |
---|
1105 | /* Bag out if the depth here is too shallow for externals action. */ |
---|
1106 | if ((cb->requested_depth < svn_depth_infinity |
---|
1107 | && cb->requested_depth != svn_depth_unknown) |
---|
1108 | || (ambient_depth < svn_depth_infinity |
---|
1109 | && cb->requested_depth < svn_depth_infinity)) |
---|
1110 | return SVN_NO_ERROR; |
---|
1111 | |
---|
1112 | if ((old_desc_text = apr_hash_get(cb->externals_old, key, klen))) |
---|
1113 | SVN_ERR(svn_wc_parse_externals_description3(&old_desc, key, old_desc_text, |
---|
1114 | FALSE, cb->pool)); |
---|
1115 | else |
---|
1116 | old_desc = NULL; |
---|
1117 | |
---|
1118 | if ((new_desc_text = apr_hash_get(cb->externals_new, key, klen))) |
---|
1119 | SVN_ERR(svn_wc_parse_externals_description3(&new_desc, key, new_desc_text, |
---|
1120 | FALSE, cb->pool)); |
---|
1121 | else |
---|
1122 | new_desc = NULL; |
---|
1123 | |
---|
1124 | old_desc_hash = apr_hash_make(cb->pool); |
---|
1125 | new_desc_hash = apr_hash_make(cb->pool); |
---|
1126 | |
---|
1127 | /* Create hashes of our two externals arrays so that we can |
---|
1128 | efficiently generate a diff for them. */ |
---|
1129 | for (i = 0; old_desc && (i < old_desc->nelts); i++) |
---|
1130 | { |
---|
1131 | item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *); |
---|
1132 | |
---|
1133 | apr_hash_set(old_desc_hash, item->target_dir, |
---|
1134 | APR_HASH_KEY_STRING, item); |
---|
1135 | } |
---|
1136 | |
---|
1137 | for (i = 0; new_desc && (i < new_desc->nelts); i++) |
---|
1138 | { |
---|
1139 | item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); |
---|
1140 | |
---|
1141 | apr_hash_set(new_desc_hash, item->target_dir, |
---|
1142 | APR_HASH_KEY_STRING, item); |
---|
1143 | } |
---|
1144 | |
---|
1145 | ib.old_desc = old_desc_hash; |
---|
1146 | ib.new_desc = new_desc_hash; |
---|
1147 | ib.parent_dir = (const char *) key; |
---|
1148 | ib.repos_root_url = cb->repos_root_url; |
---|
1149 | ib.adm_access = cb->adm_access; |
---|
1150 | ib.ctx = cb->ctx; |
---|
1151 | ib.is_export = cb->is_export; |
---|
1152 | ib.timestamp_sleep = cb->timestamp_sleep; |
---|
1153 | ib.pool = cb->pool; |
---|
1154 | ib.iter_pool = svn_pool_create(cb->pool); |
---|
1155 | |
---|
1156 | /* Get the URL of the parent directory by appending a portion of |
---|
1157 | parent_dir to from_url. from_url is the URL for to_path and |
---|
1158 | to_path is a substring of parent_dir, so append any characters in |
---|
1159 | parent_dir past strlen(to_path) to from_url (making sure to move |
---|
1160 | past a '/' in parent_dir, otherwise svn_path_url_add_component() |
---|
1161 | will error. */ |
---|
1162 | len = strlen(cb->to_path); |
---|
1163 | if (ib.parent_dir[len] == '/') |
---|
1164 | ++len; |
---|
1165 | ib.parent_dir_url = svn_path_url_add_component2(cb->from_url, |
---|
1166 | ib.parent_dir + len, |
---|
1167 | cb->pool); |
---|
1168 | |
---|
1169 | /* We must use a custom version of svn_hash_diff so that the diff |
---|
1170 | entries are processed in the order they were originally specified |
---|
1171 | in the svn:externals properties. */ |
---|
1172 | |
---|
1173 | for (i = 0; old_desc && (i < old_desc->nelts); i++) |
---|
1174 | { |
---|
1175 | item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *); |
---|
1176 | |
---|
1177 | if (apr_hash_get(new_desc_hash, item->target_dir, APR_HASH_KEY_STRING)) |
---|
1178 | SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
---|
1179 | APR_HASH_KEY_STRING, |
---|
1180 | svn_hash_diff_key_both, |
---|
1181 | &ib)); |
---|
1182 | else |
---|
1183 | SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
---|
1184 | APR_HASH_KEY_STRING, |
---|
1185 | svn_hash_diff_key_a, |
---|
1186 | &ib)); |
---|
1187 | } |
---|
1188 | for (i = 0; new_desc && (i < new_desc->nelts); i++) |
---|
1189 | { |
---|
1190 | item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); |
---|
1191 | if (! apr_hash_get(old_desc_hash, item->target_dir, APR_HASH_KEY_STRING)) |
---|
1192 | SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
---|
1193 | APR_HASH_KEY_STRING, |
---|
1194 | svn_hash_diff_key_b, |
---|
1195 | &ib)); |
---|
1196 | } |
---|
1197 | |
---|
1198 | /* Now destroy the subpool we pass to the hash differ. This will |
---|
1199 | close any remaining RA sessions used by the hash diff callback. */ |
---|
1200 | svn_pool_destroy(ib.iter_pool); |
---|
1201 | |
---|
1202 | return SVN_NO_ERROR; |
---|
1203 | } |
---|
1204 | |
---|
1205 | |
---|
1206 | svn_error_t * |
---|
1207 | svn_client__handle_externals(svn_wc_adm_access_t *adm_access, |
---|
1208 | svn_wc_traversal_info_t *traversal_info, |
---|
1209 | const char *from_url, |
---|
1210 | const char *to_path, |
---|
1211 | const char *repos_root_url, |
---|
1212 | svn_depth_t requested_depth, |
---|
1213 | svn_boolean_t *timestamp_sleep, |
---|
1214 | svn_client_ctx_t *ctx, |
---|
1215 | apr_pool_t *pool) |
---|
1216 | { |
---|
1217 | apr_hash_t *externals_old, *externals_new, *ambient_depths; |
---|
1218 | struct handle_externals_desc_change_baton cb = { 0 }; |
---|
1219 | |
---|
1220 | svn_wc_edited_externals(&externals_old, &externals_new, traversal_info); |
---|
1221 | svn_wc_traversed_depths(&ambient_depths, traversal_info); |
---|
1222 | |
---|
1223 | /* Sanity check; see r30124. */ |
---|
1224 | if (! svn_path_is_url(from_url)) |
---|
1225 | return svn_error_createf |
---|
1226 | (SVN_ERR_BAD_URL, NULL, _("'%s' is not a URL"), from_url); |
---|
1227 | |
---|
1228 | cb.externals_new = externals_new; |
---|
1229 | cb.externals_old = externals_old; |
---|
1230 | cb.requested_depth = requested_depth; |
---|
1231 | cb.ambient_depths = ambient_depths; |
---|
1232 | cb.from_url = from_url; |
---|
1233 | cb.to_path = to_path; |
---|
1234 | cb.repos_root_url = repos_root_url; |
---|
1235 | cb.adm_access = adm_access; |
---|
1236 | cb.ctx = ctx; |
---|
1237 | cb.timestamp_sleep = timestamp_sleep; |
---|
1238 | cb.is_export = FALSE; |
---|
1239 | cb.pool = pool; |
---|
1240 | |
---|
1241 | return svn_hash_diff(cb.externals_old, cb.externals_new, |
---|
1242 | handle_externals_desc_change, &cb, pool); |
---|
1243 | } |
---|
1244 | |
---|
1245 | |
---|
1246 | svn_error_t * |
---|
1247 | svn_client__fetch_externals(apr_hash_t *externals, |
---|
1248 | const char *from_url, |
---|
1249 | const char *to_path, |
---|
1250 | const char *repos_root_url, |
---|
1251 | svn_depth_t requested_depth, |
---|
1252 | svn_boolean_t is_export, |
---|
1253 | svn_boolean_t *timestamp_sleep, |
---|
1254 | svn_client_ctx_t *ctx, |
---|
1255 | apr_pool_t *pool) |
---|
1256 | { |
---|
1257 | struct handle_externals_desc_change_baton cb = { 0 }; |
---|
1258 | |
---|
1259 | cb.externals_new = externals; |
---|
1260 | cb.externals_old = apr_hash_make(pool); |
---|
1261 | cb.requested_depth = requested_depth; |
---|
1262 | cb.ambient_depths = NULL; |
---|
1263 | cb.adm_access = NULL; |
---|
1264 | cb.ctx = ctx; |
---|
1265 | cb.from_url = from_url; |
---|
1266 | cb.to_path = to_path; |
---|
1267 | cb.repos_root_url = repos_root_url; |
---|
1268 | cb.timestamp_sleep = timestamp_sleep; |
---|
1269 | cb.is_export = is_export; |
---|
1270 | cb.pool = pool; |
---|
1271 | |
---|
1272 | return svn_hash_diff(cb.externals_old, cb.externals_new, |
---|
1273 | handle_externals_desc_change, &cb, pool); |
---|
1274 | } |
---|
1275 | |
---|
1276 | |
---|
1277 | svn_error_t * |
---|
1278 | svn_client__do_external_status(svn_wc_traversal_info_t *traversal_info, |
---|
1279 | svn_wc_status_func3_t status_func, |
---|
1280 | void *status_baton, |
---|
1281 | svn_depth_t depth, |
---|
1282 | svn_boolean_t get_all, |
---|
1283 | svn_boolean_t update, |
---|
1284 | svn_boolean_t no_ignore, |
---|
1285 | svn_client_ctx_t *ctx, |
---|
1286 | apr_pool_t *pool) |
---|
1287 | { |
---|
1288 | apr_hash_t *externals_old, *externals_new; |
---|
1289 | apr_hash_index_t *hi; |
---|
1290 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1291 | |
---|
1292 | /* Get the values of the svn:externals properties. */ |
---|
1293 | svn_wc_edited_externals(&externals_old, &externals_new, traversal_info); |
---|
1294 | |
---|
1295 | /* Loop over the hash of new values (we don't care about the old |
---|
1296 | ones). This is a mapping of versioned directories to property |
---|
1297 | values. */ |
---|
1298 | for (hi = apr_hash_first(pool, externals_new); |
---|
1299 | hi; |
---|
1300 | hi = apr_hash_next(hi)) |
---|
1301 | { |
---|
1302 | apr_array_header_t *exts; |
---|
1303 | const void *key; |
---|
1304 | void *val; |
---|
1305 | const char *path; |
---|
1306 | const char *propval; |
---|
1307 | apr_pool_t *iterpool; |
---|
1308 | int i; |
---|
1309 | |
---|
1310 | /* Clear the subpool. */ |
---|
1311 | svn_pool_clear(subpool); |
---|
1312 | |
---|
1313 | apr_hash_this(hi, &key, NULL, &val); |
---|
1314 | path = key; |
---|
1315 | propval = val; |
---|
1316 | |
---|
1317 | /* Parse the svn:externals property value. This results in a |
---|
1318 | hash mapping subdirectories to externals structures. */ |
---|
1319 | SVN_ERR(svn_wc_parse_externals_description3(&exts, path, propval, |
---|
1320 | FALSE, subpool)); |
---|
1321 | |
---|
1322 | /* Make a sub-pool of SUBPOOL. */ |
---|
1323 | iterpool = svn_pool_create(subpool); |
---|
1324 | |
---|
1325 | /* Loop over the subdir array. */ |
---|
1326 | for (i = 0; exts && (i < exts->nelts); i++) |
---|
1327 | { |
---|
1328 | const char *fullpath; |
---|
1329 | svn_wc_external_item2_t *external; |
---|
1330 | svn_node_kind_t kind; |
---|
1331 | |
---|
1332 | svn_pool_clear(iterpool); |
---|
1333 | |
---|
1334 | external = APR_ARRAY_IDX(exts, i, svn_wc_external_item2_t *); |
---|
1335 | fullpath = svn_path_join(path, external->target_dir, iterpool); |
---|
1336 | |
---|
1337 | /* If the external target directory doesn't exist on disk, |
---|
1338 | just skip it. */ |
---|
1339 | SVN_ERR(svn_io_check_path(fullpath, &kind, iterpool)); |
---|
1340 | if (kind != svn_node_dir) |
---|
1341 | continue; |
---|
1342 | |
---|
1343 | /* Tell the client we're staring an external status set. */ |
---|
1344 | if (ctx->notify_func2) |
---|
1345 | (ctx->notify_func2) |
---|
1346 | (ctx->notify_baton2, |
---|
1347 | svn_wc_create_notify(fullpath, svn_wc_notify_status_external, |
---|
1348 | iterpool), iterpool); |
---|
1349 | |
---|
1350 | /* And then do the status. */ |
---|
1351 | SVN_ERR(svn_client_status4(NULL, fullpath, |
---|
1352 | &(external->revision), |
---|
1353 | status_func, status_baton, |
---|
1354 | depth, get_all, update, |
---|
1355 | no_ignore, FALSE, NULL, ctx, iterpool)); |
---|
1356 | } |
---|
1357 | } |
---|
1358 | |
---|
1359 | /* Destroy SUBPOOL and (implicitly) ITERPOOL. */ |
---|
1360 | svn_pool_destroy(subpool); |
---|
1361 | |
---|
1362 | return SVN_NO_ERROR; |
---|
1363 | } |
---|