1 /*
2 * copy_foreign.c: copy from other repository support.
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26 /*** Includes. ***/
27
28 #include <string.h>
29 #include "svn_hash.h"
30 #include "svn_client.h"
31 #include "svn_delta.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_path.h"
36 #include "svn_pools.h"
37 #include "svn_props.h"
38 #include "svn_ra.h"
39 #include "svn_wc.h"
40
41 #include <apr_md5.h>
42
43 #include "client.h"
44 #include "private/svn_subr_private.h"
45 #include "private/svn_wc_private.h"
46 #include "svn_private_config.h"
47
48 struct edit_baton_t
49 {
50 apr_pool_t *pool;
51 const char *anchor_abspath;
52
53 svn_wc_context_t *wc_ctx;
54 svn_wc_notify_func2_t notify_func;
55 void *notify_baton;
56 };
57
58 struct dir_baton_t
59 {
60 apr_pool_t *pool;
61
62 struct dir_baton_t *pb;
63 struct edit_baton_t *eb;
64
65 const char *local_abspath;
66
67 svn_boolean_t created;
68 apr_hash_t *properties;
69
70 int users;
71 };
72
73 /* svn_delta_editor_t function */
74 static svn_error_t *
edit_open(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** root_baton)75 edit_open(void *edit_baton,
76 svn_revnum_t base_revision,
77 apr_pool_t *result_pool,
78 void **root_baton)
79 {
80 struct edit_baton_t *eb = edit_baton;
81 apr_pool_t *dir_pool = svn_pool_create(eb->pool);
82 struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
83
84 db->pool = dir_pool;
85 db->eb = eb;
86 db->users = 1;
87 db->local_abspath = eb->anchor_abspath;
88
89 SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool));
90
91 *root_baton = db;
92
93 return SVN_NO_ERROR;
94 }
95
96 /* svn_delta_editor_t function */
97 static svn_error_t *
edit_close(void * edit_baton,apr_pool_t * scratch_pool)98 edit_close(void *edit_baton,
99 apr_pool_t *scratch_pool)
100 {
101 return SVN_NO_ERROR;
102 }
103
104 static svn_error_t *
dir_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** child_baton)105 dir_add(const char *path,
106 void *parent_baton,
107 const char *copyfrom_path,
108 svn_revnum_t copyfrom_revision,
109 apr_pool_t *result_pool,
110 void **child_baton)
111 {
112 struct dir_baton_t *pb = parent_baton;
113 struct edit_baton_t *eb = pb->eb;
114 apr_pool_t *dir_pool = svn_pool_create(pb->pool);
115 struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
116 svn_boolean_t under_root;
117
118 pb->users++;
119
120 db->pb = pb;
121 db->eb = pb->eb;
122 db->pool = dir_pool;
123 db->users = 1;
124
125 SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath,
126 eb->anchor_abspath, path, db->pool));
127 if (! under_root)
128 {
129 return svn_error_createf(
130 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
131 _("Path '%s' is not in the working copy"),
132 svn_dirent_local_style(path, db->pool));
133 }
134
135 SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool));
136
137 *child_baton = db;
138 return SVN_NO_ERROR;
139 }
140
141 static svn_error_t *
dir_change_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)142 dir_change_prop(void *dir_baton,
143 const char *name,
144 const svn_string_t *value,
145 apr_pool_t *scratch_pool)
146 {
147 struct dir_baton_t *db = dir_baton;
148 struct edit_baton_t *eb = db->eb;
149 svn_prop_kind_t prop_kind;
150
151 prop_kind = svn_property_kind2(name);
152
153 if (prop_kind != svn_prop_regular_kind
154 || ! strcmp(name, SVN_PROP_MERGEINFO))
155 {
156 /* We can't handle DAV, ENTRY and merge specific props here */
157 return SVN_NO_ERROR;
158 }
159
160 if (! db->created)
161 {
162 /* We can still store them in the hash for immediate addition
163 with the svn_wc_add_from_disk3() call */
164 if (! db->properties)
165 db->properties = apr_hash_make(db->pool);
166
167 if (value != NULL)
168 svn_hash_sets(db->properties, apr_pstrdup(db->pool, name),
169 svn_string_dup(value, db->pool));
170 }
171 else
172 {
173 /* We have already notified for this directory, so don't do that again */
174 SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
175 svn_depth_empty, FALSE, NULL,
176 NULL, NULL, /* Cancellation */
177 NULL, NULL, /* Notification */
178 scratch_pool));
179 }
180
181 return SVN_NO_ERROR;
182 }
183
184 /* Releases the directory baton if there are no more users */
185 static svn_error_t *
maybe_done(struct dir_baton_t * db)186 maybe_done(struct dir_baton_t *db)
187 {
188 db->users--;
189
190 if (db->users == 0)
191 {
192 struct dir_baton_t *pb = db->pb;
193
194 svn_pool_clear(db->pool);
195
196 if (pb)
197 SVN_ERR(maybe_done(pb));
198 }
199
200 return SVN_NO_ERROR;
201 }
202
203 static svn_error_t *
ensure_added(struct dir_baton_t * db,apr_pool_t * scratch_pool)204 ensure_added(struct dir_baton_t *db,
205 apr_pool_t *scratch_pool)
206 {
207 if (db->created)
208 return SVN_NO_ERROR;
209
210 if (db->pb)
211 SVN_ERR(ensure_added(db->pb, scratch_pool));
212
213 db->created = TRUE;
214
215 /* Add the directory with all the already collected properties */
216 SVN_ERR(svn_wc_add_from_disk3(db->eb->wc_ctx,
217 db->local_abspath,
218 db->properties,
219 TRUE /* skip checks */,
220 db->eb->notify_func,
221 db->eb->notify_baton,
222 scratch_pool));
223
224 return SVN_NO_ERROR;
225 }
226
227 static svn_error_t *
dir_close(void * dir_baton,apr_pool_t * scratch_pool)228 dir_close(void *dir_baton,
229 apr_pool_t *scratch_pool)
230 {
231 struct dir_baton_t *db = dir_baton;
232 /*struct edit_baton_t *eb = db->eb;*/
233
234 SVN_ERR(ensure_added(db, scratch_pool));
235
236 SVN_ERR(maybe_done(db));
237
238 return SVN_NO_ERROR;
239 }
240
241 struct file_baton_t
242 {
243 apr_pool_t *pool;
244
245 struct dir_baton_t *pb;
246 struct edit_baton_t *eb;
247
248 const char *local_abspath;
249 apr_hash_t *properties;
250
251 svn_boolean_t writing;
252 unsigned char digest[APR_MD5_DIGESTSIZE];
253
254 const char *tmp_path;
255 };
256
257 static svn_error_t *
file_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** file_baton)258 file_add(const char *path,
259 void *parent_baton,
260 const char *copyfrom_path,
261 svn_revnum_t copyfrom_revision,
262 apr_pool_t *result_pool,
263 void **file_baton)
264 {
265 struct dir_baton_t *pb = parent_baton;
266 struct edit_baton_t *eb = pb->eb;
267 apr_pool_t *file_pool = svn_pool_create(pb->pool);
268 struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
269 svn_boolean_t under_root;
270
271 pb->users++;
272
273 fb->pool = file_pool;
274 fb->eb = eb;
275 fb->pb = pb;
276
277 SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath,
278 eb->anchor_abspath, path, fb->pool));
279 if (! under_root)
280 {
281 return svn_error_createf(
282 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
283 _("Path '%s' is not in the working copy"),
284 svn_dirent_local_style(path, fb->pool));
285 }
286
287 *file_baton = fb;
288 return SVN_NO_ERROR;
289 }
290
291 static svn_error_t *
file_change_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)292 file_change_prop(void *file_baton,
293 const char *name,
294 const svn_string_t *value,
295 apr_pool_t *scratch_pool)
296 {
297 struct file_baton_t *fb = file_baton;
298 svn_prop_kind_t prop_kind;
299
300 prop_kind = svn_property_kind2(name);
301
302 if (prop_kind != svn_prop_regular_kind
303 || ! strcmp(name, SVN_PROP_MERGEINFO))
304 {
305 /* We can't handle DAV, ENTRY and merge specific props here */
306 return SVN_NO_ERROR;
307 }
308
309 /* We store all properties in the hash for immediate addition
310 with the svn_wc_add_from_disk3() call */
311 if (! fb->properties)
312 fb->properties = apr_hash_make(fb->pool);
313
314 if (value != NULL)
315 svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name),
316 svn_string_dup(value, fb->pool));
317
318 return SVN_NO_ERROR;
319 }
320
321 static svn_error_t *
file_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * result_pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)322 file_textdelta(void *file_baton,
323 const char *base_checksum,
324 apr_pool_t *result_pool,
325 svn_txdelta_window_handler_t *handler,
326 void **handler_baton)
327 {
328 struct file_baton_t *fb = file_baton;
329 svn_stream_t *target;
330
331 SVN_ERR_ASSERT(! fb->writing);
332
333 SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool,
334 fb->pool));
335
336 fb->writing = TRUE;
337 svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */,
338 target,
339 fb->digest,
340 fb->local_abspath,
341 fb->pool,
342 /* Provide the handler directly */
343 handler, handler_baton);
344
345 return SVN_NO_ERROR;
346 }
347
348 static svn_error_t *
file_close(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)349 file_close(void *file_baton,
350 const char *text_checksum,
351 apr_pool_t *scratch_pool)
352 {
353 struct file_baton_t *fb = file_baton;
354 struct edit_baton_t *eb = fb->eb;
355 struct dir_baton_t *pb = fb->pb;
356
357 SVN_ERR(ensure_added(pb, fb->pool));
358
359 if (text_checksum)
360 {
361 svn_checksum_t *expected_checksum;
362 svn_checksum_t *actual_checksum;
363
364 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
365 text_checksum, fb->pool));
366 actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
367
368 if (! svn_checksum_match(expected_checksum, actual_checksum))
369 return svn_error_trace(
370 svn_checksum_mismatch_err(expected_checksum,
371 actual_checksum,
372 fb->pool,
373 _("Checksum mismatch for '%s'"),
374 svn_dirent_local_style(
375 fb->local_abspath,
376 fb->pool)));
377 }
378
379 SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, fb->local_abspath, fb->properties,
380 TRUE /* skip checks */,
381 eb->notify_func, eb->notify_baton,
382 fb->pool));
383
384 svn_pool_destroy(fb->pool);
385 SVN_ERR(maybe_done(pb));
386
387 return SVN_NO_ERROR;
388 }
389
390 static svn_error_t *
copy_foreign_dir(svn_ra_session_t * ra_session,svn_client__pathrev_t * location,svn_wc_context_t * wc_ctx,const char * dst_abspath,svn_depth_t depth,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)391 copy_foreign_dir(svn_ra_session_t *ra_session,
392 svn_client__pathrev_t *location,
393 svn_wc_context_t *wc_ctx,
394 const char *dst_abspath,
395 svn_depth_t depth,
396 svn_wc_notify_func2_t notify_func,
397 void *notify_baton,
398 svn_cancel_func_t cancel_func,
399 void *cancel_baton,
400 apr_pool_t *scratch_pool)
401 {
402 struct edit_baton_t eb;
403 svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool);
404 const svn_delta_editor_t *wrapped_editor;
405 void *wrapped_baton;
406 const svn_ra_reporter3_t *reporter;
407 void *reporter_baton;
408
409 eb.pool = scratch_pool;
410 eb.anchor_abspath = dst_abspath;
411
412 eb.wc_ctx = wc_ctx;
413 eb.notify_func = notify_func;
414 eb.notify_baton = notify_baton;
415
416 editor->open_root = edit_open;
417 editor->close_edit = edit_close;
418
419 editor->add_directory = dir_add;
420 editor->change_dir_prop = dir_change_prop;
421 editor->close_directory = dir_close;
422
423 editor->add_file = file_add;
424 editor->change_file_prop = file_change_prop;
425 editor->apply_textdelta = file_textdelta;
426 editor->close_file = file_close;
427
428 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
429 editor, &eb,
430 &wrapped_editor, &wrapped_baton,
431 scratch_pool));
432
433 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
434 location->rev, "", svn_depth_infinity,
435 FALSE, FALSE, wrapped_editor, wrapped_baton,
436 scratch_pool, scratch_pool));
437
438 SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth,
439 TRUE /* incomplete */,
440 NULL, scratch_pool));
441
442 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
443
444 return SVN_NO_ERROR;
445 }
446
447
448 svn_error_t *
svn_client__copy_foreign(const char * url,const char * dst_abspath,svn_opt_revision_t * peg_revision,svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t make_parents,svn_boolean_t already_locked,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)449 svn_client__copy_foreign(const char *url,
450 const char *dst_abspath,
451 svn_opt_revision_t *peg_revision,
452 svn_opt_revision_t *revision,
453 svn_depth_t depth,
454 svn_boolean_t make_parents,
455 svn_boolean_t already_locked,
456 svn_client_ctx_t *ctx,
457 apr_pool_t *scratch_pool)
458 {
459 svn_ra_session_t *ra_session;
460 svn_client__pathrev_t *loc;
461 svn_node_kind_t kind;
462 svn_node_kind_t wc_kind;
463 const char *dir_abspath;
464
465 SVN_ERR_ASSERT(svn_path_is_url(url));
466 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
467
468 /* Do we need to validate/update revisions? */
469
470 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
471 url, NULL,
472 peg_revision,
473 revision, ctx,
474 scratch_pool));
475
476 SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
477
478 if (kind != svn_node_file && kind != svn_node_dir)
479 return svn_error_createf(
480 SVN_ERR_ILLEGAL_TARGET, NULL,
481 _("'%s' is not a valid location inside a repository"),
482 url);
483
484 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE,
485 scratch_pool));
486
487 if (wc_kind != svn_node_none)
488 {
489 return svn_error_createf(
490 SVN_ERR_ENTRY_EXISTS, NULL,
491 _("'%s' is already under version control"),
492 svn_dirent_local_style(dst_abspath, scratch_pool));
493 }
494
495 dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
496 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
497 FALSE, FALSE, scratch_pool));
498
499 if (wc_kind == svn_node_none)
500 {
501 if (make_parents)
502 SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx,
503 scratch_pool));
504
505 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
506 FALSE, FALSE, scratch_pool));
507 }
508
509 if (wc_kind != svn_node_dir)
510 return svn_error_createf(
511 SVN_ERR_ENTRY_NOT_FOUND, NULL,
512 _("Can't add '%s', because no parent directory is found"),
513 svn_dirent_local_style(dst_abspath, scratch_pool));
514
515
516 if (kind == svn_node_file)
517 {
518 svn_stream_t *target;
519 apr_hash_t *props;
520 apr_hash_index_t *hi;
521 SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool,
522 scratch_pool));
523
524 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props,
525 scratch_pool));
526
527 if (props != NULL)
528 for (hi = apr_hash_first(scratch_pool, props); hi;
529 hi = apr_hash_next(hi))
530 {
531 const char *name = apr_hash_this_key(hi);
532
533 if (svn_property_kind2(name) != svn_prop_regular_kind
534 || ! strcmp(name, SVN_PROP_MERGEINFO))
535 {
536 /* We can't handle DAV, ENTRY and merge specific props here */
537 svn_hash_sets(props, name, NULL);
538 }
539 }
540
541 if (!already_locked)
542 SVN_WC__CALL_WITH_WRITE_LOCK(
543 svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
544 TRUE /* skip checks */,
545 ctx->notify_func2, ctx->notify_baton2,
546 scratch_pool),
547 ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
548 else
549 SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
550 TRUE /* skip checks */,
551 ctx->notify_func2, ctx->notify_baton2,
552 scratch_pool));
553 }
554 else
555 {
556 if (!already_locked)
557 SVN_WC__CALL_WITH_WRITE_LOCK(
558 copy_foreign_dir(ra_session, loc,
559 ctx->wc_ctx, dst_abspath,
560 depth,
561 ctx->notify_func2, ctx->notify_baton2,
562 ctx->cancel_func, ctx->cancel_baton,
563 scratch_pool),
564 ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
565 else
566 SVN_ERR(copy_foreign_dir(ra_session, loc,
567 ctx->wc_ctx, dst_abspath,
568 depth,
569 ctx->notify_func2, ctx->notify_baton2,
570 ctx->cancel_func, ctx->cancel_baton,
571 scratch_pool));
572 }
573
574 return SVN_NO_ERROR;
575 }
576