1/* 2 * wc_editor.c: editing the local modifications in the WC. 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_pools.h" 36#include "svn_props.h" 37#include "svn_wc.h" 38 39#include <apr_md5.h> 40 41#include "client.h" 42#include "private/svn_subr_private.h" 43#include "private/svn_wc_private.h" 44#include "svn_private_config.h" 45 46 47/* ------------------------------------------------------------------ */ 48 49/* WC Modifications Editor. 50 * 51 * This editor applies incoming modifications onto the current working state 52 * of the working copy, to produce a new working state. 53 * 54 * Currently, it assumes the working state matches what the edit driver 55 * expects to find, and may throw an error if not. 56 * 57 * For simplicity, we apply incoming edits as they arrive, rather than 58 * queueing them up to apply in a batch. 59 * 60 * TODO: 61 * - tests 62 * - use for all existing scenarios ('svn add', 'svn propset', etc.) 63 * - Instead of 'root_dir_add' option, probably the driver should anchor 64 * at the parent dir. 65 * - Instead of 'ignore_mergeinfo' option, implement that as a wrapper. 66 * - Option to quietly accept changes that seem to be already applied 67 * in the versioned state and/or on disk. 68 * Consider 'svn add' which assumes items to be added are found on disk. 69 * - Notification. 70 */ 71 72/* Everything we need to know about the edit session. 73 */ 74struct edit_baton_t 75{ 76 const char *anchor_abspath; 77 svn_boolean_t manage_wc_write_lock; 78 const char *lock_root_abspath; /* the path locked, when locked */ 79 80 /* True => 'open_root' method will act as 'add_directory' */ 81 svn_boolean_t root_dir_add; 82 /* True => filter out any incoming svn:mergeinfo property changes */ 83 svn_boolean_t ignore_mergeinfo_changes; 84 85 svn_ra_session_t *ra_session; 86 87 svn_wc_context_t *wc_ctx; 88 svn_client_ctx_t *ctx; 89 svn_wc_notify_func2_t notify_func; 90 void *notify_baton; 91}; 92 93/* Everything we need to know about a directory that's open for edits. 94 */ 95struct dir_baton_t 96{ 97 apr_pool_t *pool; 98 99 struct edit_baton_t *eb; 100 101 const char *local_abspath; 102}; 103 104/* Join PATH onto ANCHOR_ABSPATH. 105 * Throw an error if the result is outside ANCHOR_ABSPATH. 106 */ 107static svn_error_t * 108get_path(const char **local_abspath_p, 109 const char *anchor_abspath, 110 const char *path, 111 apr_pool_t *result_pool) 112{ 113 svn_boolean_t under_root; 114 115 SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p, 116 anchor_abspath, path, result_pool)); 117 if (! under_root) 118 { 119 return svn_error_createf( 120 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 121 _("Path '%s' is not in the working copy"), 122 svn_dirent_local_style(path, result_pool)); 123 } 124 return SVN_NO_ERROR; 125} 126 127/* Create a directory on disk and add it to version control, 128 * with no properties. 129 */ 130static svn_error_t * 131mkdir(const char *abspath, 132 struct edit_baton_t *eb, 133 apr_pool_t *scratch_pool) 134{ 135 SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool)); 136 SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, 137 NULL /*properties*/, 138 TRUE /* skip checks */, 139 eb->notify_func, eb->notify_baton, 140 scratch_pool)); 141 return SVN_NO_ERROR; 142} 143 144/* Prepare to open or add a directory: initialize a new dir baton. 145 * 146 * If PATH is "" and PB is null, it represents the root directory of 147 * the edit; otherwise PATH is not "" and PB is not null. 148 */ 149static svn_error_t * 150dir_open_or_add(struct dir_baton_t **child_dir_baton, 151 const char *path, 152 struct dir_baton_t *pb, 153 struct edit_baton_t *eb, 154 apr_pool_t *dir_pool) 155{ 156 struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); 157 158 db->pool = dir_pool; 159 db->eb = eb; 160 161 SVN_ERR(get_path(&db->local_abspath, 162 eb->anchor_abspath, path, dir_pool)); 163 164 *child_dir_baton = db; 165 return SVN_NO_ERROR; 166} 167 168/* */ 169static svn_error_t * 170release_write_lock(struct edit_baton_t *eb, 171 apr_pool_t *scratch_pool) 172{ 173 if (eb->lock_root_abspath) 174 { 175 SVN_ERR(svn_wc__release_write_lock( 176 eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool)); 177 eb->lock_root_abspath = NULL; 178 } 179 return SVN_NO_ERROR; 180} 181 182/* */ 183static apr_status_t 184pool_cleanup_handler(void *root_baton) 185{ 186 struct dir_baton_t *db = root_baton; 187 struct edit_baton_t *eb = db->eb; 188 189 svn_error_clear(release_write_lock(eb, db->pool)); 190 return APR_SUCCESS; 191} 192 193/* svn_delta_editor_t function */ 194static svn_error_t * 195edit_open(void *edit_baton, 196 svn_revnum_t base_revision, 197 apr_pool_t *result_pool, 198 void **root_baton) 199{ 200 struct edit_baton_t *eb = edit_baton; 201 struct dir_baton_t *db; 202 203 SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool)); 204 205 /* Acquire a WC write lock */ 206 if (eb->manage_wc_write_lock) 207 { 208 apr_pool_cleanup_register(db->pool, db, 209 pool_cleanup_handler, 210 apr_pool_cleanup_null); 211 SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath, 212 eb->ctx->wc_ctx, 213 eb->anchor_abspath, 214 FALSE /*lock_anchor*/, 215 db->pool, db->pool)); 216 } 217 218 if (eb->root_dir_add) 219 { 220 SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); 221 } 222 223 *root_baton = db; 224 return SVN_NO_ERROR; 225} 226 227/* svn_delta_editor_t function */ 228static svn_error_t * 229edit_close_or_abort(void *edit_baton, 230 apr_pool_t *scratch_pool) 231{ 232 SVN_ERR(release_write_lock(edit_baton, scratch_pool)); 233 return SVN_NO_ERROR; 234} 235 236/* */ 237static svn_error_t * 238delete_entry(const char *path, 239 svn_revnum_t revision, 240 void *parent_baton, 241 apr_pool_t *scratch_pool) 242{ 243 struct dir_baton_t *pb = parent_baton; 244 struct edit_baton_t *eb = pb->eb; 245 const char *local_abspath; 246 247 SVN_ERR(get_path(&local_abspath, 248 eb->anchor_abspath, path, scratch_pool)); 249 SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath, 250 FALSE /*keep_local*/, 251 TRUE /*delete_unversioned*/, 252 NULL, NULL, /*cancellation*/ 253 eb->notify_func, eb->notify_baton, scratch_pool)); 254 255 return SVN_NO_ERROR; 256} 257 258/* An svn_delta_editor_t function. */ 259static svn_error_t * 260dir_open(const char *path, 261 void *parent_baton, 262 svn_revnum_t base_revision, 263 apr_pool_t *result_pool, 264 void **child_baton) 265{ 266 struct dir_baton_t *pb = parent_baton; 267 struct edit_baton_t *eb = pb->eb; 268 struct dir_baton_t *db; 269 270 SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); 271 272 *child_baton = db; 273 return SVN_NO_ERROR; 274} 275 276static svn_error_t * 277dir_add(const char *path, 278 void *parent_baton, 279 const char *copyfrom_path, 280 svn_revnum_t copyfrom_revision, 281 apr_pool_t *result_pool, 282 void **child_baton) 283{ 284 struct dir_baton_t *pb = parent_baton; 285 struct edit_baton_t *eb = pb->eb; 286 struct dir_baton_t *db; 287 /* ### Our caller should be providing a scratch pool */ 288 apr_pool_t *scratch_pool = svn_pool_create(result_pool); 289 290 SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); 291 292 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) 293 { 294 SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, 295 svn_node_dir, 296 copyfrom_path, 297 copyfrom_revision, 298 db->local_abspath, 299 db->eb->ra_session, 300 db->eb->ctx, 301 scratch_pool)); 302 } 303 else 304 { 305 SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); 306 } 307 308 *child_baton = db; 309 svn_pool_destroy(scratch_pool); 310 return SVN_NO_ERROR; 311} 312 313static svn_error_t * 314dir_change_prop(void *dir_baton, 315 const char *name, 316 const svn_string_t *value, 317 apr_pool_t *scratch_pool) 318{ 319 struct dir_baton_t *db = dir_baton; 320 struct edit_baton_t *eb = db->eb; 321 322 if (svn_property_kind2(name) != svn_prop_regular_kind 323 || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) 324 { 325 /* We can't handle DAV, ENTRY and merge specific props here */ 326 return SVN_NO_ERROR; 327 } 328 329 SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value, 330 svn_depth_empty, FALSE, NULL, 331 NULL, NULL, /* Cancellation */ 332 NULL, NULL, /* Notification */ 333 scratch_pool)); 334 335 return SVN_NO_ERROR; 336} 337 338static svn_error_t * 339dir_close(void *dir_baton, 340 apr_pool_t *scratch_pool) 341{ 342 return SVN_NO_ERROR; 343} 344 345/* Everything we need to know about a file that's open for edits. 346 */ 347struct file_baton_t 348{ 349 apr_pool_t *pool; 350 351 struct edit_baton_t *eb; 352 353 const char *local_abspath; 354 355 /* fields for the transfer of text changes */ 356 const char *writing_file; 357 unsigned char digest[APR_MD5_DIGESTSIZE]; /* MD5 digest of new fulltext */ 358 svn_stream_t *wc_file_read_stream, *tmp_file_write_stream; 359 const char *tmp_path; 360}; 361 362/* Create a new file on disk and add it to version control. 363 * 364 * The file is empty and has no properties. 365 */ 366static svn_error_t * 367mkfile(const char *abspath, 368 struct edit_baton_t *eb, 369 apr_pool_t *scratch_pool) 370{ 371 SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool)); 372 SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, 373 NULL /*properties*/, 374 TRUE /* skip checks */, 375 eb->notify_func, eb->notify_baton, 376 scratch_pool)); 377 return SVN_NO_ERROR; 378} 379 380/* */ 381static svn_error_t * 382file_open_or_add(const char *path, 383 void *parent_baton, 384 struct file_baton_t **file_baton, 385 apr_pool_t *file_pool) 386{ 387 struct dir_baton_t *pb = parent_baton; 388 struct edit_baton_t *eb = pb->eb; 389 struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); 390 391 fb->pool = file_pool; 392 fb->eb = eb; 393 SVN_ERR(get_path(&fb->local_abspath, 394 eb->anchor_abspath, path, fb->pool)); 395 396 *file_baton = fb; 397 return SVN_NO_ERROR; 398} 399 400static svn_error_t * 401file_open(const char *path, 402 void *parent_baton, 403 svn_revnum_t base_revision, 404 apr_pool_t *result_pool, 405 void **file_baton) 406{ 407 struct file_baton_t *fb; 408 409 SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); 410 411 *file_baton = fb; 412 return SVN_NO_ERROR; 413} 414 415static svn_error_t * 416file_add(const char *path, 417 void *parent_baton, 418 const char *copyfrom_path, 419 svn_revnum_t copyfrom_revision, 420 apr_pool_t *result_pool, 421 void **file_baton) 422{ 423 struct file_baton_t *fb; 424 425 SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); 426 427 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) 428 { 429 SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, 430 svn_node_file, 431 copyfrom_path, 432 copyfrom_revision, 433 fb->local_abspath, 434 fb->eb->ra_session, 435 fb->eb->ctx, fb->pool)); 436 } 437 else 438 { 439 SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_pool)); 440 } 441 442 *file_baton = fb; 443 return SVN_NO_ERROR; 444} 445 446static svn_error_t * 447file_change_prop(void *file_baton, 448 const char *name, 449 const svn_string_t *value, 450 apr_pool_t *scratch_pool) 451{ 452 struct file_baton_t *fb = file_baton; 453 struct edit_baton_t *eb = fb->eb; 454 455 if (svn_property_kind2(name) != svn_prop_regular_kind 456 || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) 457 { 458 /* We can't handle DAV, ENTRY and merge specific props here */ 459 return SVN_NO_ERROR; 460 } 461 462 SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value, 463 svn_depth_empty, FALSE, NULL, 464 NULL, NULL, /* Cancellation */ 465 NULL, NULL, /* Notification */ 466 scratch_pool)); 467 468 return SVN_NO_ERROR; 469} 470 471static svn_error_t * 472file_textdelta(void *file_baton, 473 const char *base_checksum, 474 apr_pool_t *result_pool, 475 svn_txdelta_window_handler_t *handler, 476 void **handler_baton) 477{ 478 struct file_baton_t *fb = file_baton; 479 const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool); 480 svn_error_t *err; 481 482 SVN_ERR_ASSERT(! fb->writing_file); 483 484 err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath, 485 fb->pool, fb->pool); 486 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 487 { 488 svn_error_clear(err); 489 fb->wc_file_read_stream = svn_stream_empty(fb->pool); 490 } 491 else 492 SVN_ERR(err); 493 494 SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file, 495 target_dir, svn_io_file_del_none, 496 fb->pool, fb->pool)); 497 498 svn_txdelta_apply(fb->wc_file_read_stream, 499 fb->tmp_file_write_stream, 500 fb->digest, 501 fb->local_abspath, 502 fb->pool, 503 /* Provide the handler directly */ 504 handler, handler_baton); 505 506 return SVN_NO_ERROR; 507} 508 509static svn_error_t * 510file_close(void *file_baton, 511 const char *text_checksum, 512 apr_pool_t *scratch_pool) 513{ 514 struct file_baton_t *fb = file_baton; 515 516 /* If we have text changes, write them to disk */ 517 if (fb->writing_file) 518 { 519 SVN_ERR(svn_stream_close(fb->wc_file_read_stream)); 520 SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath, 521 FALSE /*flush*/, scratch_pool)); 522 } 523 524 if (text_checksum) 525 { 526 svn_checksum_t *expected_checksum; 527 svn_checksum_t *actual_checksum; 528 529 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 530 text_checksum, fb->pool)); 531 actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool); 532 533 if (! svn_checksum_match(expected_checksum, actual_checksum)) 534 return svn_error_trace( 535 svn_checksum_mismatch_err(expected_checksum, 536 actual_checksum, 537 fb->pool, 538 _("Checksum mismatch for '%s'"), 539 svn_dirent_local_style( 540 fb->local_abspath, 541 fb->pool))); 542 } 543 544 return SVN_NO_ERROR; 545} 546 547svn_error_t * 548svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p, 549 void **edit_baton_p, 550 const char *dst_abspath, 551 svn_boolean_t root_dir_add, 552 svn_boolean_t ignore_mergeinfo_changes, 553 svn_boolean_t manage_wc_write_lock, 554 svn_wc_notify_func2_t notify_func, 555 void *notify_baton, 556 svn_ra_session_t *ra_session, 557 svn_client_ctx_t *ctx, 558 apr_pool_t *result_pool) 559{ 560 svn_delta_editor_t *editor = svn_delta_default_editor(result_pool); 561 struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb)); 562 563 eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath); 564 eb->manage_wc_write_lock = manage_wc_write_lock; 565 eb->lock_root_abspath = NULL; 566 eb->root_dir_add = root_dir_add; 567 eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes; 568 569 eb->ra_session = ra_session; 570 eb->wc_ctx = ctx->wc_ctx; 571 eb->ctx = ctx; 572 eb->notify_func = notify_func; 573 eb->notify_baton = notify_baton; 574 575 editor->open_root = edit_open; 576 editor->close_edit = edit_close_or_abort; 577 editor->abort_edit = edit_close_or_abort; 578 579 editor->delete_entry = delete_entry; 580 581 editor->open_directory = dir_open; 582 editor->add_directory = dir_add; 583 editor->change_dir_prop = dir_change_prop; 584 editor->close_directory = dir_close; 585 586 editor->open_file = file_open; 587 editor->add_file = file_add; 588 editor->change_file_prop = file_change_prop; 589 editor->apply_textdelta = file_textdelta; 590 editor->close_file = file_close; 591 592 *editor_p = editor; 593 *edit_baton_p = eb; 594 return SVN_NO_ERROR; 595} 596 597svn_error_t * 598svn_client__wc_editor(const svn_delta_editor_t **editor_p, 599 void **edit_baton_p, 600 const char *dst_abspath, 601 svn_wc_notify_func2_t notify_func, 602 void *notify_baton, 603 svn_ra_session_t *ra_session, 604 svn_client_ctx_t *ctx, 605 apr_pool_t *result_pool) 606{ 607 SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p, 608 dst_abspath, 609 FALSE /*root_dir_add*/, 610 FALSE /*ignore_mergeinfo_changes*/, 611 TRUE /*manage_wc_write_lock*/, 612 notify_func, notify_baton, 613 ra_session, 614 ctx, result_pool)); 615 return SVN_NO_ERROR; 616} 617 618svn_error_t * 619svn_client__wc_copy_mods(const char *src_wc_abspath, 620 const char *dst_wc_abspath, 621 svn_wc_notify_func2_t notify_func, 622 void *notify_baton, 623 svn_client_ctx_t *ctx, 624 apr_pool_t *scratch_pool) 625{ 626 svn_client__pathrev_t *base; 627 const char *dst_wc_url; 628 svn_ra_session_t *ra_session; 629 const svn_delta_editor_t *editor; 630 void *edit_baton; 631 apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1, 632 sizeof(char *)); 633 634 /* We'll need an RA session to obtain the base of any copies */ 635 SVN_ERR(svn_client__wc_node_get_base(&base, 636 src_wc_abspath, ctx->wc_ctx, 637 scratch_pool, scratch_pool)); 638 dst_wc_url = base->url; 639 SVN_ERR(svn_client_open_ra_session2(&ra_session, 640 dst_wc_url, dst_wc_abspath, 641 ctx, scratch_pool, scratch_pool)); 642 SVN_ERR(svn_client__wc_editor(&editor, &edit_baton, 643 dst_wc_abspath, 644 NULL, NULL, /*notification*/ 645 ra_session, ctx, scratch_pool)); 646 647 APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath; 648 SVN_ERR(svn_client__wc_replay(src_wc_abspath, 649 src_targets, svn_depth_infinity, NULL, 650 editor, edit_baton, 651 notify_func, notify_baton, 652 ctx, scratch_pool)); 653 654 return SVN_NO_ERROR; 655} 656