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 48struct 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 58struct 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 */ 74static svn_error_t * 75edit_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 */ 97static svn_error_t * 98edit_close(void *edit_baton, 99 apr_pool_t *scratch_pool) 100{ 101 return SVN_NO_ERROR; 102} 103 104static svn_error_t * 105dir_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 141static svn_error_t * 142dir_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 */ 185static svn_error_t * 186maybe_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 203static svn_error_t * 204ensure_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 227static svn_error_t * 228dir_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 241struct 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 257static svn_error_t * 258file_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 291static svn_error_t * 292file_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 321static svn_error_t * 322file_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 348static svn_error_t * 349file_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 390static svn_error_t * 391copy_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 448svn_error_t * 449svn_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