1/* 2 * mtcc.c -- Multi Command Context implementation. This allows 3 * performing many operations without a working copy. 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include "svn_dirent_uri.h" 26#include "svn_hash.h" 27#include "svn_path.h" 28#include "svn_props.h" 29#include "svn_pools.h" 30#include "svn_subst.h" 31 32#include "private/svn_client_mtcc.h" 33 34 35#include "svn_private_config.h" 36 37#include "client.h" 38 39#include <assert.h> 40 41#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') 42 43/* The kind of operation to perform in an mtcc_op_t */ 44typedef enum mtcc_kind_t 45{ 46 OP_OPEN_DIR, 47 OP_OPEN_FILE, 48 OP_ADD_DIR, 49 OP_ADD_FILE, 50 OP_DELETE 51} mtcc_kind_t; 52 53typedef struct mtcc_op_t 54{ 55 const char *name; /* basename of operation */ 56 mtcc_kind_t kind; /* editor operation */ 57 58 apr_array_header_t *children; /* List of mtcc_op_t * */ 59 60 const char *src_relpath; /* For ADD_DIR, ADD_FILE */ 61 svn_revnum_t src_rev; /* For ADD_DIR, ADD_FILE */ 62 svn_stream_t *src_stream; /* For ADD_FILE, OPEN_FILE */ 63 svn_checksum_t *src_checksum; /* For ADD_FILE, OPEN_FILE */ 64 svn_stream_t *base_stream; /* For ADD_FILE, OPEN_FILE */ 65 const svn_checksum_t *base_checksum; /* For ADD_FILE, OPEN_FILE */ 66 67 apr_array_header_t *prop_mods; /* For all except DELETE 68 List of svn_prop_t */ 69 70 svn_boolean_t performed_stat; /* Verified kind with repository */ 71} mtcc_op_t; 72 73/* Check if the mtcc doesn't contain any modifications yet */ 74#define MTCC_UNMODIFIED(mtcc) \ 75 ((mtcc->root_op->kind == OP_OPEN_DIR \ 76 || mtcc->root_op->kind == OP_OPEN_FILE) \ 77 && (mtcc->root_op->prop_mods == NULL \ 78 || !mtcc->root_op->prop_mods->nelts) \ 79 && (mtcc->root_op->children == NULL \ 80 || !mtcc->root_op->children->nelts)) 81 82struct svn_client__mtcc_t 83{ 84 apr_pool_t *pool; 85 svn_revnum_t head_revision; 86 svn_revnum_t base_revision; 87 88 svn_ra_session_t *ra_session; 89 svn_client_ctx_t *ctx; 90 91 mtcc_op_t *root_op; 92}; 93 94static mtcc_op_t * 95mtcc_op_create(const char *name, 96 svn_boolean_t add, 97 svn_boolean_t directory, 98 apr_pool_t *result_pool) 99{ 100 mtcc_op_t *op; 101 102 op = apr_pcalloc(result_pool, sizeof(*op)); 103 op->name = name ? apr_pstrdup(result_pool, name) : ""; 104 105 if (add) 106 op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE; 107 else 108 op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE; 109 110 if (directory) 111 op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *)); 112 113 op->src_rev = SVN_INVALID_REVNUM; 114 115 return op; 116} 117 118static svn_error_t * 119mtcc_op_find(mtcc_op_t **op, 120 svn_boolean_t *created, 121 const char *relpath, 122 mtcc_op_t *base_op, 123 svn_boolean_t find_existing, 124 svn_boolean_t find_deletes, 125 svn_boolean_t create_file, 126 apr_pool_t *result_pool, 127 apr_pool_t *scratch_pool) 128{ 129 const char *name; 130 const char *child; 131 int i; 132 133 assert(svn_relpath_is_canonical(relpath)); 134 if (created) 135 *created = FALSE; 136 137 if (SVN_PATH_IS_EMPTY(relpath)) 138 { 139 if (find_existing) 140 *op = base_op; 141 else 142 *op = NULL; 143 144 return SVN_NO_ERROR; 145 } 146 147 child = strchr(relpath, '/'); 148 149 if (child) 150 { 151 name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath)); 152 child++; /* Skip '/' */ 153 } 154 else 155 name = relpath; 156 157 if (!base_op->children) 158 { 159 if (!created) 160 { 161 *op = NULL; 162 return SVN_NO_ERROR; 163 } 164 else 165 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 166 _("Can't operate on '%s' because '%s' is not a " 167 "directory"), 168 name, base_op->name); 169 } 170 171 for (i = base_op->children->nelts-1; i >= 0 ; i--) 172 { 173 mtcc_op_t *cop; 174 175 cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *); 176 177 if (! strcmp(cop->name, name) 178 && (find_deletes || cop->kind != OP_DELETE)) 179 { 180 return svn_error_trace( 181 mtcc_op_find(op, created, child ? child : "", cop, 182 find_existing, find_deletes, create_file, 183 result_pool, scratch_pool)); 184 } 185 } 186 187 if (!created) 188 { 189 *op = NULL; 190 return SVN_NO_ERROR; 191 } 192 193 { 194 mtcc_op_t *cop; 195 196 cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool); 197 198 APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop; 199 200 if (!child) 201 { 202 *op = cop; 203 *created = TRUE; 204 return SVN_NO_ERROR; 205 } 206 207 return svn_error_trace( 208 mtcc_op_find(op, created, child, cop, find_existing, 209 find_deletes, create_file, 210 result_pool, scratch_pool)); 211 } 212} 213 214/* Gets the original repository location of RELPATH, checking things 215 like copies, moves, etc. */ 216static svn_error_t * 217get_origin(svn_boolean_t *done, 218 const char **origin_relpath, 219 svn_revnum_t *rev, 220 mtcc_op_t *op, 221 const char *relpath, 222 apr_pool_t *result_pool, 223 apr_pool_t *scratch_pool) 224{ 225 const char *child; 226 const char *name; 227 if (SVN_PATH_IS_EMPTY(relpath)) 228 { 229 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) 230 *done = TRUE; 231 *origin_relpath = op->src_relpath 232 ? apr_pstrdup(result_pool, op->src_relpath) 233 : NULL; 234 *rev = op->src_rev; 235 return SVN_NO_ERROR; 236 } 237 238 child = strchr(relpath, '/'); 239 if (child) 240 { 241 name = apr_pstrmemdup(scratch_pool, relpath, child-relpath); 242 child++; /* Skip '/' */ 243 } 244 else 245 name = relpath; 246 247 if (op->children && op->children->nelts) 248 { 249 int i; 250 251 for (i = op->children->nelts-1; i >= 0; i--) 252 { 253 mtcc_op_t *cop; 254 255 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 256 257 if (! strcmp(cop->name, name)) 258 { 259 if (cop->kind == OP_DELETE) 260 { 261 *done = TRUE; 262 return SVN_NO_ERROR; 263 } 264 265 SVN_ERR(get_origin(done, origin_relpath, rev, 266 cop, child ? child : "", 267 result_pool, scratch_pool)); 268 269 if (*origin_relpath || *done) 270 return SVN_NO_ERROR; 271 272 break; 273 } 274 } 275 } 276 277 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) 278 { 279 *done = TRUE; 280 if (op->src_relpath) 281 { 282 *origin_relpath = svn_relpath_join(op->src_relpath, relpath, 283 result_pool); 284 *rev = op->src_rev; 285 } 286 } 287 288 return SVN_NO_ERROR; 289} 290 291/* Obtains the original repository location for an mtcc relpath as 292 *ORIGIN_RELPATH @ *REV, if it has one. If it has not and IGNORE_ENOENT 293 is TRUE report *ORIGIN_RELPATH as NULL, otherwise return an error */ 294static svn_error_t * 295mtcc_get_origin(const char **origin_relpath, 296 svn_revnum_t *rev, 297 const char *relpath, 298 svn_boolean_t ignore_enoent, 299 svn_client__mtcc_t *mtcc, 300 apr_pool_t *result_pool, 301 apr_pool_t *scratch_pool) 302{ 303 svn_boolean_t done = FALSE; 304 305 *origin_relpath = NULL; 306 *rev = SVN_INVALID_REVNUM; 307 308 SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath, 309 result_pool, scratch_pool)); 310 311 if (!*origin_relpath && !done) 312 { 313 *origin_relpath = apr_pstrdup(result_pool, relpath); 314 *rev = mtcc->base_revision; 315 } 316 else if (!ignore_enoent) 317 { 318 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 319 _("No origin found for node at '%s'"), 320 relpath); 321 } 322 323 return SVN_NO_ERROR; 324} 325 326svn_error_t * 327svn_client__mtcc_create(svn_client__mtcc_t **mtcc, 328 const char *anchor_url, 329 svn_revnum_t base_revision, 330 svn_client_ctx_t *ctx, 331 apr_pool_t *result_pool, 332 apr_pool_t *scratch_pool) 333{ 334 apr_pool_t *mtcc_pool; 335 336 mtcc_pool = svn_pool_create(result_pool); 337 338 *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc)); 339 (*mtcc)->pool = mtcc_pool; 340 341 (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool); 342 343 (*mtcc)->ctx = ctx; 344 345 SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url, 346 NULL /* wri_abspath */, ctx, 347 mtcc_pool, scratch_pool)); 348 349 SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision, 350 scratch_pool)); 351 352 if (SVN_IS_VALID_REVNUM(base_revision)) 353 (*mtcc)->base_revision = base_revision; 354 else 355 (*mtcc)->base_revision = (*mtcc)->head_revision; 356 357 if ((*mtcc)->base_revision > (*mtcc)->head_revision) 358 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 359 _("No such revision %ld (HEAD is %ld)"), 360 base_revision, (*mtcc)->head_revision); 361 362 return SVN_NO_ERROR; 363} 364 365static svn_error_t * 366update_copy_src(mtcc_op_t *op, 367 const char *add_relpath, 368 apr_pool_t *result_pool) 369{ 370 int i; 371 372 if (op->src_relpath) 373 op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath, 374 result_pool); 375 376 if (!op->children) 377 return SVN_NO_ERROR; 378 379 for (i = 0; i < op->children->nelts; i++) 380 { 381 mtcc_op_t *cop; 382 383 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 384 385 SVN_ERR(update_copy_src(cop, add_relpath, result_pool)); 386 } 387 388 return SVN_NO_ERROR; 389} 390 391static svn_error_t * 392mtcc_reparent(const char *new_anchor_url, 393 svn_client__mtcc_t *mtcc, 394 apr_pool_t *scratch_pool) 395{ 396 const char *session_url; 397 const char *up; 398 399 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, 400 scratch_pool)); 401 402 up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool); 403 404 if (! up) 405 { 406 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 407 _("'%s' is not an ancestor of '%s'"), 408 new_anchor_url, session_url); 409 } 410 else if (!*up) 411 { 412 return SVN_NO_ERROR; /* Same url */ 413 } 414 415 /* Update copy origins recursively...:( */ 416 SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool)); 417 418 SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool)); 419 420 /* Create directory open operations for new ancestors */ 421 while (*up) 422 { 423 mtcc_op_t *root_op; 424 425 mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool); 426 up = svn_relpath_dirname(up, scratch_pool); 427 428 root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool); 429 430 APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op; 431 432 mtcc->root_op = root_op; 433 } 434 435 return SVN_NO_ERROR; 436} 437 438/* Check if it is safe to create a new node at NEW_RELPATH. Return a proper 439 error if it is not */ 440static svn_error_t * 441mtcc_verify_create(svn_client__mtcc_t *mtcc, 442 const char *new_relpath, 443 apr_pool_t *scratch_pool) 444{ 445 svn_node_kind_t kind; 446 447 if (*new_relpath || !MTCC_UNMODIFIED(mtcc)) 448 { 449 mtcc_op_t *op; 450 451 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE, 452 FALSE, mtcc->pool, scratch_pool)); 453 454 if (op) 455 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 456 _("Path '%s' already exists, or was created " 457 "by an earlier operation"), 458 new_relpath); 459 460 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE, 461 FALSE, mtcc->pool, scratch_pool)); 462 463 if (op) 464 return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */ 465 } 466 467 /* mod_dav_svn used to allow overwriting existing directories. Let's hide 468 that for users of this api */ 469 SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE, 470 mtcc, scratch_pool)); 471 472 if (kind != svn_node_none) 473 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 474 _("Path '%s' already exists"), 475 new_relpath); 476 477 return SVN_NO_ERROR; 478} 479 480 481svn_error_t * 482svn_client__mtcc_add_add_file(const char *relpath, 483 svn_stream_t *src_stream, 484 const svn_checksum_t *src_checksum, 485 svn_client__mtcc_t *mtcc, 486 apr_pool_t *scratch_pool) 487{ 488 mtcc_op_t *op; 489 svn_boolean_t created; 490 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); 491 492 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); 493 494 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 495 { 496 /* Turn the root operation into a file addition */ 497 op = mtcc->root_op; 498 } 499 else 500 { 501 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, 502 TRUE, mtcc->pool, scratch_pool)); 503 504 if (!op || !created) 505 { 506 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 507 _("Can't add file at '%s'"), 508 relpath); 509 } 510 } 511 512 op->kind = OP_ADD_FILE; 513 op->src_stream = src_stream; 514 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) 515 : NULL; 516 517 return SVN_NO_ERROR; 518} 519 520svn_error_t * 521svn_client__mtcc_add_copy(const char *src_relpath, 522 svn_revnum_t revision, 523 const char *dst_relpath, 524 svn_client__mtcc_t *mtcc, 525 apr_pool_t *scratch_pool) 526{ 527 mtcc_op_t *op; 528 svn_boolean_t created; 529 svn_node_kind_t kind; 530 531 SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath) 532 && svn_relpath_is_canonical(dst_relpath)); 533 534 if (! SVN_IS_VALID_REVNUM(revision)) 535 revision = mtcc->head_revision; 536 else if (revision > mtcc->head_revision) 537 { 538 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 539 _("No such revision %ld"), revision); 540 } 541 542 SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool)); 543 544 /* Subversion requires the kind of a copy */ 545 SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind, 546 scratch_pool)); 547 548 if (kind != svn_node_dir && kind != svn_node_file) 549 { 550 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 551 _("Path '%s' not found in revision %ld"), 552 src_relpath, revision); 553 } 554 555 SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE, 556 (kind == svn_node_file), mtcc->pool, scratch_pool)); 557 558 if (!op || !created) 559 { 560 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 561 _("Can't add node at '%s'"), 562 dst_relpath); 563 } 564 565 op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR; 566 op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath); 567 op->src_rev = revision; 568 569 return SVN_NO_ERROR; 570} 571 572/* Check if this operation contains at least one change that is not a 573 plain delete */ 574static svn_boolean_t 575mtcc_op_contains_non_delete(const mtcc_op_t *op) 576{ 577 if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE 578 && op->kind != OP_DELETE) 579 { 580 return TRUE; 581 } 582 583 if (op->prop_mods && op->prop_mods->nelts) 584 return TRUE; 585 586 if (op->src_stream) 587 return TRUE; 588 589 if (op->children) 590 { 591 int i; 592 593 for (i = 0; i < op->children->nelts; i++) 594 { 595 const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i, 596 const mtcc_op_t *); 597 598 if (mtcc_op_contains_non_delete(c_op)) 599 return TRUE; 600 } 601 } 602 return FALSE; 603} 604 605static svn_error_t * 606mtcc_add_delete(const char *relpath, 607 svn_boolean_t for_move, 608 svn_client__mtcc_t *mtcc, 609 apr_pool_t *scratch_pool) 610{ 611 mtcc_op_t *op; 612 svn_boolean_t created; 613 svn_node_kind_t kind; 614 615 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 616 617 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 618 mtcc, scratch_pool)); 619 620 if (kind == svn_node_none) 621 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 622 _("Can't delete node at '%s' as it " 623 "does not exist"), 624 relpath); 625 626 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 627 { 628 /* Turn root operation into delete */ 629 op = mtcc->root_op; 630 } 631 else 632 { 633 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE, 634 TRUE, mtcc->pool, scratch_pool)); 635 636 if (!for_move && !op && !created) 637 { 638 /* Allow deleting directories, that are unmodified except for 639 one or more deleted descendants */ 640 641 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, 642 FALSE, FALSE, mtcc->pool, scratch_pool)); 643 644 if (op && mtcc_op_contains_non_delete(op)) 645 op = NULL; 646 else 647 created = TRUE; 648 } 649 650 if (!op || !created) 651 { 652 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 653 _("Can't delete node at '%s'"), 654 relpath); 655 } 656 } 657 658 op->kind = OP_DELETE; 659 op->children = NULL; 660 op->prop_mods = NULL; 661 662 return SVN_NO_ERROR; 663} 664 665svn_error_t * 666svn_client__mtcc_add_delete(const char *relpath, 667 svn_client__mtcc_t *mtcc, 668 apr_pool_t *scratch_pool) 669{ 670 return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool)); 671} 672 673svn_error_t * 674svn_client__mtcc_add_mkdir(const char *relpath, 675 svn_client__mtcc_t *mtcc, 676 apr_pool_t *scratch_pool) 677{ 678 mtcc_op_t *op; 679 svn_boolean_t created; 680 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 681 682 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); 683 684 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 685 { 686 /* Turn the root of the operation in an MKDIR */ 687 mtcc->root_op->kind = OP_ADD_DIR; 688 689 return SVN_NO_ERROR; 690 } 691 692 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, 693 FALSE, mtcc->pool, scratch_pool)); 694 695 if (!op || !created) 696 { 697 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 698 _("Can't create directory at '%s'"), 699 relpath); 700 } 701 702 op->kind = OP_ADD_DIR; 703 704 return SVN_NO_ERROR; 705} 706 707svn_error_t * 708svn_client__mtcc_add_move(const char *src_relpath, 709 const char *dst_relpath, 710 svn_client__mtcc_t *mtcc, 711 apr_pool_t *scratch_pool) 712{ 713 const char *origin_relpath; 714 svn_revnum_t origin_rev; 715 716 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, 717 src_relpath, FALSE, mtcc, 718 scratch_pool, scratch_pool)); 719 720 SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision, 721 dst_relpath, mtcc, scratch_pool)); 722 SVN_ERR(mtcc_add_delete(src_relpath, TRUE, mtcc, scratch_pool)); 723 724 return SVN_NO_ERROR; 725} 726 727/* Baton for mtcc_prop_getter */ 728struct mtcc_prop_get_baton 729{ 730 svn_client__mtcc_t *mtcc; 731 const char *relpath; 732 svn_cancel_func_t cancel_func; 733 void *cancel_baton; 734}; 735 736/* Implements svn_wc_canonicalize_svn_prop_get_file_t */ 737static svn_error_t * 738mtcc_prop_getter(const svn_string_t **mime_type, 739 svn_stream_t *stream, 740 void *baton, 741 apr_pool_t *pool) 742{ 743 struct mtcc_prop_get_baton *mpgb = baton; 744 const char *origin_relpath; 745 svn_revnum_t origin_rev; 746 apr_hash_t *props = NULL; 747 748 mtcc_op_t *op; 749 750 if (mime_type) 751 *mime_type = NULL; 752 753 /* Check if we have the information locally */ 754 SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE, 755 FALSE, FALSE, pool, pool)); 756 757 if (op) 758 { 759 if (mime_type) 760 { 761 int i; 762 763 for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++) 764 { 765 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, 766 svn_prop_t); 767 768 if (! strcmp(mod->name, SVN_PROP_MIME_TYPE)) 769 { 770 *mime_type = svn_string_dup(mod->value, pool); 771 mime_type = NULL; 772 break; 773 } 774 } 775 } 776 777 if (stream && op->src_stream) 778 { 779 svn_stream_mark_t *mark; 780 svn_error_t *err; 781 782 /* Is the source stream capable of being read multiple times? */ 783 err = svn_stream_mark(op->src_stream, &mark, pool); 784 785 if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED) 786 return svn_error_trace(err); 787 svn_error_clear(err); 788 789 if (!err) 790 { 791 err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool), 792 svn_stream_disown(stream, pool), 793 mpgb->cancel_func, mpgb->cancel_baton, 794 pool); 795 796 SVN_ERR(svn_error_compose_create( 797 err, 798 svn_stream_seek(op->src_stream, mark))); 799 } 800 /* else: ### Create tempfile? */ 801 802 stream = NULL; /* Stream is handled */ 803 } 804 } 805 806 if (!stream && !mime_type) 807 return SVN_NO_ERROR; 808 809 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE, 810 mpgb->mtcc, pool, pool)); 811 812 if (!origin_relpath) 813 return SVN_NO_ERROR; /* Nothing to fetch at repository */ 814 815 SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev, 816 stream, NULL, mime_type ? &props : NULL, pool)); 817 818 if (mime_type && props) 819 *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE); 820 821 return SVN_NO_ERROR; 822} 823 824svn_error_t * 825svn_client__mtcc_add_propset(const char *relpath, 826 const char *propname, 827 const svn_string_t *propval, 828 svn_boolean_t skip_checks, 829 svn_client__mtcc_t *mtcc, 830 apr_pool_t *scratch_pool) 831{ 832 mtcc_op_t *op; 833 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 834 835 if (! svn_prop_name_is_valid(propname)) 836 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 837 _("Bad property name: '%s'"), propname); 838 839 if (svn_prop_is_known_svn_rev_prop(propname)) 840 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 841 _("Revision property '%s' not allowed " 842 "in this context"), propname); 843 844 if (svn_property_kind2(propname) == svn_prop_wc_kind) 845 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 846 _("'%s' is a wcprop, thus not accessible " 847 "to clients"), propname); 848 849 if (!skip_checks && svn_prop_needs_translation(propname)) 850 { 851 svn_string_t *translated_value; 852 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL, 853 NULL, propval, 854 NULL, FALSE, 855 scratch_pool, scratch_pool), 856 _("Error normalizing property value")); 857 858 propval = translated_value; 859 } 860 861 if (propval && svn_prop_is_svn_prop(propname)) 862 { 863 struct mtcc_prop_get_baton mpbg; 864 svn_node_kind_t kind; 865 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc, 866 scratch_pool)); 867 868 mpbg.mtcc = mtcc; 869 mpbg.relpath = relpath; 870 mpbg.cancel_func = mtcc->ctx->cancel_func; 871 mpbg.cancel_baton = mtcc->ctx->cancel_baton; 872 873 SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval, 874 relpath, kind, skip_checks, 875 mtcc_prop_getter, &mpbg, 876 scratch_pool)); 877 } 878 879 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 880 { 881 svn_node_kind_t kind; 882 883 /* Probing the node for an unmodified root will fix the node type to 884 a file if necessary */ 885 886 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 887 mtcc, scratch_pool)); 888 889 if (kind == svn_node_none) 890 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 891 _("Can't set properties at not existing '%s'"), 892 relpath); 893 894 op = mtcc->root_op; 895 } 896 else 897 { 898 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, 899 FALSE, mtcc->pool, scratch_pool)); 900 901 if (!op) 902 { 903 svn_node_kind_t kind; 904 svn_boolean_t created; 905 906 /* ### TODO: Check if this node is within a newly copied directory, 907 and update origin values accordingly */ 908 909 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 910 mtcc, scratch_pool)); 911 912 if (kind == svn_node_none) 913 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 914 _("Can't set properties at not existing '%s'"), 915 relpath); 916 917 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, 918 (kind != svn_node_dir), 919 mtcc->pool, scratch_pool)); 920 921 SVN_ERR_ASSERT(op != NULL); 922 } 923 } 924 925 if (!op->prop_mods) 926 op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t)); 927 928 { 929 svn_prop_t propchange; 930 propchange.name = apr_pstrdup(mtcc->pool, propname); 931 932 if (propval) 933 propchange.value = svn_string_dup(propval, mtcc->pool); 934 else 935 propchange.value = NULL; 936 937 APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange; 938 } 939 940 return SVN_NO_ERROR; 941} 942 943svn_error_t * 944svn_client__mtcc_add_update_file(const char *relpath, 945 svn_stream_t *src_stream, 946 const svn_checksum_t *src_checksum, 947 svn_stream_t *base_stream, 948 const svn_checksum_t *base_checksum, 949 svn_client__mtcc_t *mtcc, 950 apr_pool_t *scratch_pool) 951{ 952 mtcc_op_t *op; 953 svn_boolean_t created; 954 svn_node_kind_t kind; 955 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); 956 957 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 958 mtcc, scratch_pool)); 959 960 if (kind != svn_node_file) 961 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 962 _("Can't update '%s' because it is not a file"), 963 relpath); 964 965 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, 966 TRUE, mtcc->pool, scratch_pool)); 967 968 if (!op 969 || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE) 970 || (op->src_stream != NULL)) 971 { 972 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 973 _("Can't update file at '%s'"), relpath); 974 } 975 976 op->src_stream = src_stream; 977 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) 978 : NULL; 979 980 op->base_stream = base_stream; 981 op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum, 982 mtcc->pool) 983 : NULL; 984 985 return SVN_NO_ERROR; 986} 987 988svn_error_t * 989svn_client__mtcc_check_path(svn_node_kind_t *kind, 990 const char *relpath, 991 svn_boolean_t check_repository, 992 svn_client__mtcc_t *mtcc, 993 apr_pool_t *scratch_pool) 994{ 995 const char *origin_relpath; 996 svn_revnum_t origin_rev; 997 mtcc_op_t *op; 998 999 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 1000 1001 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc) 1002 && !mtcc->root_op->performed_stat) 1003 { 1004 /* We know nothing about the root. Perhaps it is a file? */ 1005 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, 1006 kind, scratch_pool)); 1007 1008 mtcc->root_op->performed_stat = TRUE; 1009 if (*kind == svn_node_file) 1010 { 1011 mtcc->root_op->kind = OP_OPEN_FILE; 1012 mtcc->root_op->children = NULL; 1013 } 1014 return SVN_NO_ERROR; 1015 } 1016 1017 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, 1018 FALSE, mtcc->pool, scratch_pool)); 1019 1020 if (!op || (check_repository && !op->performed_stat)) 1021 { 1022 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, 1023 relpath, TRUE, mtcc, 1024 scratch_pool, scratch_pool)); 1025 1026 if (!origin_relpath) 1027 *kind = svn_node_none; 1028 else 1029 SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath, 1030 origin_rev, kind, scratch_pool)); 1031 1032 if (op && *kind == svn_node_dir) 1033 { 1034 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1035 op->performed_stat = TRUE; 1036 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1037 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 1038 _("Can't perform file operation " 1039 "on '%s' as it is not a file"), 1040 relpath); 1041 } 1042 else if (op && *kind == svn_node_file) 1043 { 1044 if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1045 op->performed_stat = TRUE; 1046 else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1047 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1048 _("Can't perform directory operation " 1049 "on '%s' as it is not a directory"), 1050 relpath); 1051 } 1052 else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE)) 1053 { 1054 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1055 _("Can't open '%s' as it does not exist"), 1056 relpath); 1057 } 1058 1059 return SVN_NO_ERROR; 1060 } 1061 1062 /* op != NULL */ 1063 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1064 { 1065 *kind = svn_node_dir; 1066 return SVN_NO_ERROR; 1067 } 1068 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1069 { 1070 *kind = svn_node_file; 1071 return SVN_NO_ERROR; 1072 } 1073 SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */ 1074} 1075 1076static svn_error_t * 1077commit_properties(const svn_delta_editor_t *editor, 1078 const mtcc_op_t *op, 1079 void *node_baton, 1080 apr_pool_t *scratch_pool) 1081{ 1082 int i; 1083 apr_pool_t *iterpool; 1084 1085 if (!op->prop_mods || op->prop_mods->nelts == 0) 1086 return SVN_NO_ERROR; 1087 1088 iterpool = svn_pool_create(scratch_pool); 1089 for (i = 0; i < op->prop_mods->nelts; i++) 1090 { 1091 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t); 1092 1093 svn_pool_clear(iterpool); 1094 1095 if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR) 1096 SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value, 1097 iterpool)); 1098 else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE) 1099 SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value, 1100 iterpool)); 1101 } 1102 1103 svn_pool_destroy(iterpool); 1104 return SVN_NO_ERROR; 1105} 1106 1107/* Handles updating a file to a delta editor and then closes it */ 1108static svn_error_t * 1109commit_file(const svn_delta_editor_t *editor, 1110 mtcc_op_t *op, 1111 void *file_baton, 1112 const char *session_url, 1113 const char *relpath, 1114 svn_client_ctx_t *ctx, 1115 apr_pool_t *scratch_pool) 1116{ 1117 const char *text_checksum = NULL; 1118 svn_checksum_t *src_checksum = op->src_checksum; 1119 SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool)); 1120 1121 if (op->src_stream) 1122 { 1123 const char *base_checksum = NULL; 1124 apr_pool_t *txdelta_pool = scratch_pool; 1125 svn_txdelta_window_handler_t window_handler; 1126 void *handler_baton; 1127 svn_stream_t *src_stream = op->src_stream; 1128 1129 if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5) 1130 base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool); 1131 1132 /* ### TODO: Future enhancement: Allocate in special pool and send 1133 files after the true edit operation, like a wc commit */ 1134 SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool, 1135 &window_handler, &handler_baton)); 1136 1137 if (ctx->notify_func2) 1138 { 1139 svn_wc_notify_t *notify; 1140 1141 notify = svn_wc_create_notify_url( 1142 svn_path_url_add_component2(session_url, relpath, 1143 scratch_pool), 1144 svn_wc_notify_commit_postfix_txdelta, 1145 scratch_pool); 1146 1147 notify->path = relpath; 1148 notify->kind = svn_node_file; 1149 1150 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 1151 } 1152 1153 if (window_handler != svn_delta_noop_window_handler) 1154 { 1155 if (!src_checksum || src_checksum->kind != svn_checksum_md5) 1156 src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL, 1157 svn_checksum_md5, 1158 TRUE, scratch_pool); 1159 1160 if (!op->base_stream) 1161 SVN_ERR(svn_txdelta_send_stream(src_stream, 1162 window_handler, handler_baton, NULL, 1163 scratch_pool)); 1164 else 1165 SVN_ERR(svn_txdelta_run(op->base_stream, src_stream, 1166 window_handler, handler_baton, 1167 svn_checksum_md5, NULL, 1168 ctx->cancel_func, ctx->cancel_baton, 1169 scratch_pool, scratch_pool)); 1170 } 1171 1172 SVN_ERR(svn_stream_close(src_stream)); 1173 if (op->base_stream) 1174 SVN_ERR(svn_stream_close(op->base_stream)); 1175 } 1176 1177 if (src_checksum && src_checksum->kind == svn_checksum_md5) 1178 text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool); 1179 1180 return svn_error_trace(editor->close_file(file_baton, text_checksum, 1181 scratch_pool)); 1182} 1183 1184/* Handles updating a directory to a delta editor and then closes it */ 1185static svn_error_t * 1186commit_directory(const svn_delta_editor_t *editor, 1187 mtcc_op_t *op, 1188 const char *relpath, 1189 svn_revnum_t base_rev, 1190 void *dir_baton, 1191 const char *session_url, 1192 svn_client_ctx_t *ctx, 1193 apr_pool_t *scratch_pool) 1194{ 1195 SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool)); 1196 1197 if (op->children && op->children->nelts > 0) 1198 { 1199 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1200 int i; 1201 1202 for (i = 0; i < op->children->nelts; i++) 1203 { 1204 mtcc_op_t *cop; 1205 const char * child_relpath; 1206 void *child_baton; 1207 1208 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 1209 1210 svn_pool_clear(iterpool); 1211 1212 if (ctx->cancel_func) 1213 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1214 1215 child_relpath = svn_relpath_join(relpath, cop->name, iterpool); 1216 1217 switch (cop->kind) 1218 { 1219 case OP_DELETE: 1220 SVN_ERR(editor->delete_entry(child_relpath, base_rev, 1221 dir_baton, iterpool)); 1222 break; 1223 1224 case OP_ADD_DIR: 1225 SVN_ERR(editor->add_directory(child_relpath, dir_baton, 1226 cop->src_relpath 1227 ? svn_path_url_add_component2( 1228 session_url, 1229 cop->src_relpath, 1230 iterpool) 1231 : NULL, 1232 cop->src_rev, 1233 iterpool, &child_baton)); 1234 SVN_ERR(commit_directory(editor, cop, child_relpath, 1235 SVN_INVALID_REVNUM, child_baton, 1236 session_url, ctx, iterpool)); 1237 break; 1238 case OP_OPEN_DIR: 1239 SVN_ERR(editor->open_directory(child_relpath, dir_baton, 1240 base_rev, iterpool, &child_baton)); 1241 SVN_ERR(commit_directory(editor, cop, child_relpath, 1242 base_rev, child_baton, 1243 session_url, ctx, iterpool)); 1244 break; 1245 1246 case OP_ADD_FILE: 1247 SVN_ERR(editor->add_file(child_relpath, dir_baton, 1248 cop->src_relpath 1249 ? svn_path_url_add_component2( 1250 session_url, 1251 cop->src_relpath, 1252 iterpool) 1253 : NULL, 1254 cop->src_rev, 1255 iterpool, &child_baton)); 1256 SVN_ERR(commit_file(editor, cop, child_baton, 1257 session_url, child_relpath, ctx, iterpool)); 1258 break; 1259 case OP_OPEN_FILE: 1260 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev, 1261 iterpool, &child_baton)); 1262 SVN_ERR(commit_file(editor, cop, child_baton, 1263 session_url, child_relpath, ctx, iterpool)); 1264 break; 1265 1266 default: 1267 SVN_ERR_MALFUNCTION(); 1268 } 1269 } 1270 } 1271 1272 return svn_error_trace(editor->close_directory(dir_baton, scratch_pool)); 1273} 1274 1275 1276/* Helper function to recursively create svn_client_commit_item3_t items 1277 to provide to the log message callback */ 1278static svn_error_t * 1279add_commit_items(mtcc_op_t *op, 1280 const char *session_url, 1281 const char *url, 1282 apr_array_header_t *commit_items, 1283 apr_pool_t *result_pool, 1284 apr_pool_t *scratch_pool) 1285{ 1286 if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE) 1287 || (op->prop_mods && op->prop_mods->nelts) 1288 || (op->src_stream)) 1289 { 1290 svn_client_commit_item3_t *item; 1291 1292 item = svn_client_commit_item3_create(result_pool); 1293 1294 item->path = NULL; 1295 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1296 item->kind = svn_node_dir; 1297 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1298 item->kind = svn_node_file; 1299 else 1300 item->kind = svn_node_unknown; 1301 1302 item->url = apr_pstrdup(result_pool, url); 1303 item->session_relpath = svn_uri_skip_ancestor(session_url, item->url, 1304 result_pool); 1305 1306 if (op->src_relpath) 1307 { 1308 item->copyfrom_url = svn_path_url_add_component2(session_url, 1309 op->src_relpath, 1310 result_pool); 1311 item->copyfrom_rev = op->src_rev; 1312 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; 1313 } 1314 else 1315 item->copyfrom_rev = SVN_INVALID_REVNUM; 1316 1317 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) 1318 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1319 else if (op->kind == OP_DELETE) 1320 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1321 /* else item->state_flags = 0; */ 1322 1323 if (op->prop_mods && op->prop_mods->nelts) 1324 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1325 1326 if (op->src_stream) 1327 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; 1328 1329 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1330 } 1331 1332 if (op->children && op->children->nelts) 1333 { 1334 int i; 1335 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1336 1337 for (i = 0; i < op->children->nelts; i++) 1338 { 1339 mtcc_op_t *cop; 1340 const char * child_url; 1341 1342 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 1343 1344 svn_pool_clear(iterpool); 1345 1346 child_url = svn_path_url_add_component2(url, cop->name, iterpool); 1347 1348 SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items, 1349 result_pool, iterpool)); 1350 } 1351 1352 svn_pool_destroy(iterpool); 1353 } 1354 1355 return SVN_NO_ERROR; 1356} 1357 1358svn_error_t * 1359svn_client__mtcc_commit(apr_hash_t *revprop_table, 1360 svn_commit_callback2_t commit_callback, 1361 void *commit_baton, 1362 svn_client__mtcc_t *mtcc, 1363 apr_pool_t *scratch_pool) 1364{ 1365 const svn_delta_editor_t *editor; 1366 void *edit_baton; 1367 void *root_baton; 1368 apr_hash_t *commit_revprops; 1369 svn_node_kind_t kind; 1370 svn_error_t *err; 1371 const char *session_url; 1372 const char *log_msg; 1373 1374 if (MTCC_UNMODIFIED(mtcc)) 1375 { 1376 /* No changes -> no revision. Easy out */ 1377 svn_pool_destroy(mtcc->pool); 1378 return SVN_NO_ERROR; 1379 } 1380 1381 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool)); 1382 1383 if (mtcc->root_op->kind != OP_OPEN_DIR) 1384 { 1385 const char *name; 1386 1387 svn_uri_split(&session_url, &name, session_url, scratch_pool); 1388 1389 if (*name) 1390 { 1391 SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool)); 1392 1393 SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool)); 1394 } 1395 } 1396 1397 /* Create new commit items and add them to the array. */ 1398 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx)) 1399 { 1400 svn_client_commit_item3_t *item; 1401 const char *tmp_file; 1402 apr_array_header_t *commit_items 1403 = apr_array_make(scratch_pool, 32, sizeof(item)); 1404 1405 SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url, 1406 commit_items, scratch_pool, scratch_pool)); 1407 1408 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 1409 mtcc->ctx, scratch_pool)); 1410 1411 if (! log_msg) 1412 return SVN_NO_ERROR; 1413 } 1414 else 1415 log_msg = ""; 1416 1417 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1418 log_msg, mtcc->ctx, scratch_pool)); 1419 1420 /* Ugly corner case: The ra session might have died while we were waiting 1421 for the callback */ 1422 1423 err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind, 1424 scratch_pool); 1425 1426 if (err) 1427 { 1428 svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session, 1429 session_url, 1430 NULL, mtcc->ctx, 1431 mtcc->pool, 1432 scratch_pool); 1433 1434 if (err2) 1435 { 1436 svn_pool_destroy(mtcc->pool); 1437 return svn_error_trace(svn_error_compose_create(err, err2)); 1438 } 1439 svn_error_clear(err); 1440 1441 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", 1442 mtcc->base_revision, &kind, scratch_pool)); 1443 } 1444 1445 if (kind != svn_node_dir) 1446 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1447 _("Can't commit to '%s' because it " 1448 "is not a directory"), 1449 session_url); 1450 1451 /* Beware that the editor object must not live longer than the MTCC. 1452 Otherwise, txn objects etc. in EDITOR may live longer than their 1453 respective FS objects. So, we can't use SCRATCH_POOL here. */ 1454 SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton, 1455 commit_revprops, 1456 commit_callback, commit_baton, 1457 NULL /* lock_tokens */, 1458 FALSE /* keep_locks */, 1459 mtcc->pool)); 1460 1461 err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton); 1462 1463 if (!err) 1464 err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision, 1465 root_baton, session_url, mtcc->ctx, scratch_pool); 1466 1467 if (!err) 1468 { 1469 if (mtcc->ctx->notify_func2) 1470 { 1471 svn_wc_notify_t *notify; 1472 notify = svn_wc_create_notify_url(session_url, 1473 svn_wc_notify_commit_finalizing, 1474 scratch_pool); 1475 mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify, 1476 scratch_pool); 1477 } 1478 SVN_ERR(editor->close_edit(edit_baton, scratch_pool)); 1479 } 1480 else 1481 err = svn_error_compose_create(err, 1482 editor->abort_edit(edit_baton, scratch_pool)); 1483 1484 svn_pool_destroy(mtcc->pool); 1485 1486 return svn_error_trace(err); 1487} 1488