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_disk2() 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, /* Cancelation */ 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_disk2(db->eb->wc_ctx, 217 db->local_abspath, 218 db->properties, 219 db->eb->notify_func, 220 db->eb->notify_baton, 221 scratch_pool)); 222 223 return SVN_NO_ERROR; 224} 225 226static svn_error_t * 227dir_close(void *dir_baton, 228 apr_pool_t *scratch_pool) 229{ 230 struct dir_baton_t *db = dir_baton; 231 /*struct edit_baton_t *eb = db->eb;*/ 232 233 SVN_ERR(ensure_added(db, scratch_pool)); 234 235 SVN_ERR(maybe_done(db)); 236 237 return SVN_NO_ERROR; 238} 239 240struct file_baton_t 241{ 242 apr_pool_t *pool; 243 244 struct dir_baton_t *pb; 245 struct edit_baton_t *eb; 246 247 const char *local_abspath; 248 apr_hash_t *properties; 249 250 svn_boolean_t writing; 251 unsigned char digest[APR_MD5_DIGESTSIZE]; 252 253 const char *tmp_path; 254}; 255 256static svn_error_t * 257file_add(const char *path, 258 void *parent_baton, 259 const char *copyfrom_path, 260 svn_revnum_t copyfrom_revision, 261 apr_pool_t *result_pool, 262 void **file_baton) 263{ 264 struct dir_baton_t *pb = parent_baton; 265 struct edit_baton_t *eb = pb->eb; 266 apr_pool_t *file_pool = svn_pool_create(pb->pool); 267 struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); 268 svn_boolean_t under_root; 269 270 pb->users++; 271 272 fb->pool = file_pool; 273 fb->eb = eb; 274 fb->pb = pb; 275 276 SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath, 277 eb->anchor_abspath, path, fb->pool)); 278 if (! under_root) 279 { 280 return svn_error_createf( 281 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 282 _("Path '%s' is not in the working copy"), 283 svn_dirent_local_style(path, fb->pool)); 284 } 285 286 *file_baton = fb; 287 return SVN_NO_ERROR; 288} 289 290static svn_error_t * 291file_change_prop(void *file_baton, 292 const char *name, 293 const svn_string_t *value, 294 apr_pool_t *scratch_pool) 295{ 296 struct file_baton_t *fb = file_baton; 297 svn_prop_kind_t prop_kind; 298 299 prop_kind = svn_property_kind2(name); 300 301 if (prop_kind != svn_prop_regular_kind 302 || ! strcmp(name, SVN_PROP_MERGEINFO)) 303 { 304 /* We can't handle DAV, ENTRY and merge specific props here */ 305 return SVN_NO_ERROR; 306 } 307 308 /* We store all properties in the hash for immediate addition 309 with the svn_wc_add_from_disk2() call */ 310 if (! fb->properties) 311 fb->properties = apr_hash_make(fb->pool); 312 313 if (value != NULL) 314 svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name), 315 svn_string_dup(value, fb->pool)); 316 317 return SVN_NO_ERROR; 318} 319 320static svn_error_t * 321file_textdelta(void *file_baton, 322 const char *base_checksum, 323 apr_pool_t *result_pool, 324 svn_txdelta_window_handler_t *handler, 325 void **handler_baton) 326{ 327 struct file_baton_t *fb = file_baton; 328 svn_stream_t *target; 329 330 SVN_ERR_ASSERT(! fb->writing); 331 332 SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool, 333 fb->pool)); 334 335 fb->writing = TRUE; 336 svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */, 337 target, 338 fb->digest, 339 fb->local_abspath, 340 fb->pool, 341 /* Provide the handler directly */ 342 handler, handler_baton); 343 344 return SVN_NO_ERROR; 345} 346 347static svn_error_t * 348file_close(void *file_baton, 349 const char *text_checksum, 350 apr_pool_t *scratch_pool) 351{ 352 struct file_baton_t *fb = file_baton; 353 struct edit_baton_t *eb = fb->eb; 354 struct dir_baton_t *pb = fb->pb; 355 356 SVN_ERR(ensure_added(pb, fb->pool)); 357 358 if (text_checksum) 359 { 360 svn_checksum_t *expected_checksum; 361 svn_checksum_t *actual_checksum; 362 363 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 364 text_checksum, fb->pool)); 365 actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool); 366 367 if (! svn_checksum_match(expected_checksum, actual_checksum)) 368 return svn_error_trace( 369 svn_checksum_mismatch_err(expected_checksum, 370 actual_checksum, 371 fb->pool, 372 _("Checksum mismatch for '%s'"), 373 svn_dirent_local_style( 374 fb->local_abspath, 375 fb->pool))); 376 } 377 378 SVN_ERR(svn_wc_add_from_disk2(eb->wc_ctx, fb->local_abspath, fb->properties, 379 eb->notify_func, eb->notify_baton, 380 fb->pool)); 381 382 svn_pool_destroy(fb->pool); 383 SVN_ERR(maybe_done(pb)); 384 385 return SVN_NO_ERROR; 386} 387 388static svn_error_t * 389copy_foreign_dir(svn_ra_session_t *ra_session, 390 svn_client__pathrev_t *location, 391 svn_wc_context_t *wc_ctx, 392 const char *dst_abspath, 393 svn_depth_t depth, 394 svn_wc_notify_func2_t notify_func, 395 void *notify_baton, 396 svn_cancel_func_t cancel_func, 397 void *cancel_baton, 398 apr_pool_t *scratch_pool) 399{ 400 struct edit_baton_t eb; 401 svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool); 402 const svn_delta_editor_t *wrapped_editor; 403 void *wrapped_baton; 404 const svn_ra_reporter3_t *reporter; 405 void *reporter_baton; 406 407 eb.pool = scratch_pool; 408 eb.anchor_abspath = dst_abspath; 409 410 eb.wc_ctx = wc_ctx; 411 eb.notify_func = notify_func; 412 eb.notify_baton = notify_baton; 413 414 editor->open_root = edit_open; 415 editor->close_edit = edit_close; 416 417 editor->add_directory = dir_add; 418 editor->change_dir_prop = dir_change_prop; 419 editor->close_directory = dir_close; 420 421 editor->add_file = file_add; 422 editor->change_file_prop = file_change_prop; 423 editor->apply_textdelta = file_textdelta; 424 editor->close_file = file_close; 425 426 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 427 editor, &eb, 428 &wrapped_editor, &wrapped_baton, 429 scratch_pool)); 430 431 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton, 432 location->rev, "", svn_depth_infinity, 433 FALSE, FALSE, wrapped_editor, wrapped_baton, 434 scratch_pool, scratch_pool)); 435 436 SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth, 437 TRUE /* incomplete */, 438 NULL, scratch_pool)); 439 440 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); 441 442 return SVN_NO_ERROR; 443} 444 445 446svn_error_t * 447svn_client__copy_foreign(const char *url, 448 const char *dst_abspath, 449 svn_opt_revision_t *peg_revision, 450 svn_opt_revision_t *revision, 451 svn_depth_t depth, 452 svn_boolean_t make_parents, 453 svn_boolean_t already_locked, 454 svn_client_ctx_t *ctx, 455 apr_pool_t *scratch_pool) 456{ 457 svn_ra_session_t *ra_session; 458 svn_client__pathrev_t *loc; 459 svn_node_kind_t kind; 460 svn_node_kind_t wc_kind; 461 const char *dir_abspath; 462 463 SVN_ERR_ASSERT(svn_path_is_url(url)); 464 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 465 466 /* Do we need to validate/update revisions? */ 467 468 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, 469 url, NULL, 470 peg_revision, 471 revision, ctx, 472 scratch_pool)); 473 474 SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool)); 475 476 if (kind != svn_node_file && kind != svn_node_dir) 477 return svn_error_createf( 478 SVN_ERR_ILLEGAL_TARGET, NULL, 479 _("'%s' is not a valid location inside a repository"), 480 url); 481 482 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE, 483 scratch_pool)); 484 485 if (wc_kind != svn_node_none) 486 { 487 return svn_error_createf( 488 SVN_ERR_ENTRY_EXISTS, NULL, 489 _("'%s' is already under version control"), 490 svn_dirent_local_style(dst_abspath, scratch_pool)); 491 } 492 493 dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); 494 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, 495 FALSE, FALSE, scratch_pool)); 496 497 if (wc_kind == svn_node_none) 498 { 499 if (make_parents) 500 SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx, 501 scratch_pool)); 502 503 SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, 504 FALSE, FALSE, scratch_pool)); 505 } 506 507 if (wc_kind != svn_node_dir) 508 return svn_error_createf( 509 SVN_ERR_ENTRY_NOT_FOUND, NULL, 510 _("Can't add '%s', because no parent directory is found"), 511 svn_dirent_local_style(dst_abspath, scratch_pool)); 512 513 514 if (kind == svn_node_file) 515 { 516 svn_stream_t *target; 517 apr_hash_t *props; 518 apr_hash_index_t *hi; 519 SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool, 520 scratch_pool)); 521 522 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props, 523 scratch_pool)); 524 525 if (props != NULL) 526 for (hi = apr_hash_first(scratch_pool, props); hi; 527 hi = apr_hash_next(hi)) 528 { 529 const char *name = svn__apr_hash_index_key(hi); 530 531 if (svn_property_kind2(name) != svn_prop_regular_kind 532 || ! strcmp(name, SVN_PROP_MERGEINFO)) 533 { 534 /* We can't handle DAV, ENTRY and merge specific props here */ 535 svn_hash_sets(props, name, NULL); 536 } 537 } 538 539 if (!already_locked) 540 SVN_WC__CALL_WITH_WRITE_LOCK( 541 svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props, 542 ctx->notify_func2, ctx->notify_baton2, 543 scratch_pool), 544 ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); 545 else 546 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props, 547 ctx->notify_func2, ctx->notify_baton2, 548 scratch_pool)); 549 } 550 else 551 { 552 if (!already_locked) 553 SVN_WC__CALL_WITH_WRITE_LOCK( 554 copy_foreign_dir(ra_session, loc, 555 ctx->wc_ctx, dst_abspath, 556 depth, 557 ctx->notify_func2, ctx->notify_baton2, 558 ctx->cancel_func, ctx->cancel_baton, 559 scratch_pool), 560 ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); 561 else 562 SVN_ERR(copy_foreign_dir(ra_session, loc, 563 ctx->wc_ctx, dst_abspath, 564 depth, 565 ctx->notify_func2, ctx->notify_baton2, 566 ctx->cancel_func, ctx->cancel_baton, 567 scratch_pool)); 568 } 569 570 return SVN_NO_ERROR; 571} 572