mtcc.c revision 299742
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"), 457 new_relpath); 458 459 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE, 460 FALSE, mtcc->pool, scratch_pool)); 461 462 if (op) 463 return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */ 464 } 465 466 /* mod_dav_svn used to allow overwriting existing directories. Let's hide 467 that for users of this api */ 468 SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE, 469 mtcc, scratch_pool)); 470 471 if (kind != svn_node_none) 472 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 473 _("Path '%s' already exists"), 474 new_relpath); 475 476 return SVN_NO_ERROR; 477} 478 479 480svn_error_t * 481svn_client__mtcc_add_add_file(const char *relpath, 482 svn_stream_t *src_stream, 483 const svn_checksum_t *src_checksum, 484 svn_client__mtcc_t *mtcc, 485 apr_pool_t *scratch_pool) 486{ 487 mtcc_op_t *op; 488 svn_boolean_t created; 489 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); 490 491 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); 492 493 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 494 { 495 /* Turn the root operation into a file addition */ 496 op = mtcc->root_op; 497 } 498 else 499 { 500 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, 501 TRUE, mtcc->pool, scratch_pool)); 502 503 if (!op || !created) 504 { 505 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 506 _("Can't add file at '%s'"), 507 relpath); 508 } 509 } 510 511 op->kind = OP_ADD_FILE; 512 op->src_stream = src_stream; 513 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) 514 : NULL; 515 516 return SVN_NO_ERROR; 517} 518 519svn_error_t * 520svn_client__mtcc_add_copy(const char *src_relpath, 521 svn_revnum_t revision, 522 const char *dst_relpath, 523 svn_client__mtcc_t *mtcc, 524 apr_pool_t *scratch_pool) 525{ 526 mtcc_op_t *op; 527 svn_boolean_t created; 528 svn_node_kind_t kind; 529 530 SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath) 531 && svn_relpath_is_canonical(dst_relpath)); 532 533 if (! SVN_IS_VALID_REVNUM(revision)) 534 revision = mtcc->head_revision; 535 else if (revision > mtcc->head_revision) 536 { 537 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 538 _("No such revision %ld"), revision); 539 } 540 541 SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool)); 542 543 /* Subversion requires the kind of a copy */ 544 SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind, 545 scratch_pool)); 546 547 if (kind != svn_node_dir && kind != svn_node_file) 548 { 549 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 550 _("Path '%s' not found in revision %ld"), 551 src_relpath, revision); 552 } 553 554 SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE, 555 (kind == svn_node_file), mtcc->pool, scratch_pool)); 556 557 if (!op || !created) 558 { 559 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 560 _("Can't add node at '%s'"), 561 dst_relpath); 562 } 563 564 op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR; 565 op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath); 566 op->src_rev = revision; 567 568 return SVN_NO_ERROR; 569} 570 571svn_error_t * 572svn_client__mtcc_add_delete(const char *relpath, 573 svn_client__mtcc_t *mtcc, 574 apr_pool_t *scratch_pool) 575{ 576 mtcc_op_t *op; 577 svn_boolean_t created; 578 svn_node_kind_t kind; 579 580 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 581 582 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 583 mtcc, scratch_pool)); 584 585 if (kind == svn_node_none) 586 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 587 _("Can't delete node at '%s' as it " 588 "does not exist"), 589 relpath); 590 591 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 592 { 593 /* Turn root operation into delete */ 594 op = mtcc->root_op; 595 } 596 else 597 { 598 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE, 599 TRUE, mtcc->pool, scratch_pool)); 600 601 if (!op || !created) 602 { 603 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 604 _("Can't delete node at '%s'"), 605 relpath); 606 } 607 } 608 609 op->kind = OP_DELETE; 610 op->children = NULL; 611 op->prop_mods = NULL; 612 613 return SVN_NO_ERROR; 614} 615 616svn_error_t * 617svn_client__mtcc_add_mkdir(const char *relpath, 618 svn_client__mtcc_t *mtcc, 619 apr_pool_t *scratch_pool) 620{ 621 mtcc_op_t *op; 622 svn_boolean_t created; 623 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 624 625 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); 626 627 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 628 { 629 /* Turn the root of the operation in an MKDIR */ 630 mtcc->root_op->kind = OP_ADD_DIR; 631 632 return SVN_NO_ERROR; 633 } 634 635 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, 636 FALSE, mtcc->pool, scratch_pool)); 637 638 if (!op || !created) 639 { 640 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 641 _("Can't create directory at '%s'"), 642 relpath); 643 } 644 645 op->kind = OP_ADD_DIR; 646 647 return SVN_NO_ERROR; 648} 649 650svn_error_t * 651svn_client__mtcc_add_move(const char *src_relpath, 652 const char *dst_relpath, 653 svn_client__mtcc_t *mtcc, 654 apr_pool_t *scratch_pool) 655{ 656 const char *origin_relpath; 657 svn_revnum_t origin_rev; 658 659 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, 660 src_relpath, FALSE, mtcc, 661 scratch_pool, scratch_pool)); 662 663 SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision, 664 dst_relpath, mtcc, scratch_pool)); 665 SVN_ERR(svn_client__mtcc_add_delete(src_relpath, mtcc, scratch_pool)); 666 667 return SVN_NO_ERROR; 668} 669 670/* Baton for mtcc_prop_getter */ 671struct mtcc_prop_get_baton 672{ 673 svn_client__mtcc_t *mtcc; 674 const char *relpath; 675 svn_cancel_func_t cancel_func; 676 void *cancel_baton; 677}; 678 679/* Implements svn_wc_canonicalize_svn_prop_get_file_t */ 680static svn_error_t * 681mtcc_prop_getter(const svn_string_t **mime_type, 682 svn_stream_t *stream, 683 void *baton, 684 apr_pool_t *pool) 685{ 686 struct mtcc_prop_get_baton *mpgb = baton; 687 const char *origin_relpath; 688 svn_revnum_t origin_rev; 689 apr_hash_t *props = NULL; 690 691 mtcc_op_t *op; 692 693 if (mime_type) 694 *mime_type = NULL; 695 696 /* Check if we have the information locally */ 697 SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE, 698 FALSE, FALSE, pool, pool)); 699 700 if (op) 701 { 702 if (mime_type) 703 { 704 int i; 705 706 for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++) 707 { 708 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, 709 svn_prop_t); 710 711 if (! strcmp(mod->name, SVN_PROP_MIME_TYPE)) 712 { 713 *mime_type = svn_string_dup(mod->value, pool); 714 mime_type = NULL; 715 } 716 } 717 } 718 719 if (stream && op->src_stream) 720 { 721 svn_stream_mark_t *mark; 722 svn_error_t *err; 723 724 /* Is the source stream capable of being read multiple times? */ 725 err = svn_stream_mark(op->src_stream, &mark, pool); 726 727 if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED) 728 return svn_error_trace(err); 729 svn_error_clear(err); 730 731 if (!err) 732 { 733 err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool), 734 svn_stream_disown(stream, pool), 735 mpgb->cancel_func, mpgb->cancel_baton, 736 pool); 737 738 SVN_ERR(svn_error_compose_create( 739 err, 740 svn_stream_seek(op->src_stream, mark))); 741 } 742 /* else: ### Create tempfile? */ 743 744 stream = NULL; /* Stream is handled */ 745 } 746 } 747 748 if (!stream && !mime_type) 749 return SVN_NO_ERROR; 750 751 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE, 752 mpgb->mtcc, pool, pool)); 753 754 if (!origin_relpath) 755 return SVN_NO_ERROR; /* Nothing to fetch at repository */ 756 757 SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev, 758 stream, NULL, mime_type ? &props : NULL, pool)); 759 760 if (mime_type && props) 761 *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE); 762 763 return SVN_NO_ERROR; 764} 765 766svn_error_t * 767svn_client__mtcc_add_propset(const char *relpath, 768 const char *propname, 769 const svn_string_t *propval, 770 svn_boolean_t skip_checks, 771 svn_client__mtcc_t *mtcc, 772 apr_pool_t *scratch_pool) 773{ 774 mtcc_op_t *op; 775 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 776 777 if (! svn_prop_name_is_valid(propname)) 778 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 779 _("Bad property name: '%s'"), propname); 780 781 if (svn_prop_is_known_svn_rev_prop(propname)) 782 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 783 _("Revision property '%s' not allowed " 784 "in this context"), propname); 785 786 if (svn_property_kind2(propname) == svn_prop_wc_kind) 787 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 788 _("'%s' is a wcprop, thus not accessible " 789 "to clients"), propname); 790 791 if (!skip_checks && svn_prop_needs_translation(propname)) 792 { 793 svn_string_t *translated_value; 794 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL, 795 NULL, propval, 796 NULL, FALSE, 797 scratch_pool, scratch_pool), 798 _("Error normalizing property value")); 799 800 propval = translated_value; 801 } 802 803 if (propval && svn_prop_is_svn_prop(propname)) 804 { 805 struct mtcc_prop_get_baton mpbg; 806 svn_node_kind_t kind; 807 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc, 808 scratch_pool)); 809 810 mpbg.mtcc = mtcc; 811 mpbg.relpath = relpath; 812 mpbg.cancel_func = mtcc->ctx->cancel_func; 813 mpbg.cancel_baton = mtcc->ctx->cancel_baton; 814 815 SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval, 816 relpath, kind, skip_checks, 817 mtcc_prop_getter, &mpbg, 818 scratch_pool)); 819 } 820 821 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) 822 { 823 svn_node_kind_t kind; 824 825 /* Probing the node for an unmodified root will fix the node type to 826 a file if necessary */ 827 828 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 829 mtcc, scratch_pool)); 830 831 if (kind == svn_node_none) 832 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 833 _("Can't set properties at not existing '%s'"), 834 relpath); 835 836 op = mtcc->root_op; 837 } 838 else 839 { 840 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, 841 FALSE, mtcc->pool, scratch_pool)); 842 843 if (!op) 844 { 845 svn_node_kind_t kind; 846 svn_boolean_t created; 847 848 /* ### TODO: Check if this node is within a newly copied directory, 849 and update origin values accordingly */ 850 851 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 852 mtcc, scratch_pool)); 853 854 if (kind == svn_node_none) 855 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 856 _("Can't set properties at not existing '%s'"), 857 relpath); 858 859 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, 860 (kind != svn_node_dir), 861 mtcc->pool, scratch_pool)); 862 863 SVN_ERR_ASSERT(op != NULL); 864 } 865 } 866 867 if (!op->prop_mods) 868 op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t)); 869 870 { 871 svn_prop_t propchange; 872 propchange.name = apr_pstrdup(mtcc->pool, propname); 873 874 if (propval) 875 propchange.value = svn_string_dup(propval, mtcc->pool); 876 else 877 propchange.value = NULL; 878 879 APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange; 880 } 881 882 return SVN_NO_ERROR; 883} 884 885svn_error_t * 886svn_client__mtcc_add_update_file(const char *relpath, 887 svn_stream_t *src_stream, 888 const svn_checksum_t *src_checksum, 889 svn_stream_t *base_stream, 890 const svn_checksum_t *base_checksum, 891 svn_client__mtcc_t *mtcc, 892 apr_pool_t *scratch_pool) 893{ 894 mtcc_op_t *op; 895 svn_boolean_t created; 896 svn_node_kind_t kind; 897 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); 898 899 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, 900 mtcc, scratch_pool)); 901 902 if (kind != svn_node_file) 903 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 904 _("Can't update '%s' because it is not a file"), 905 relpath); 906 907 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, 908 TRUE, mtcc->pool, scratch_pool)); 909 910 if (!op 911 || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE) 912 || (op->src_stream != NULL)) 913 { 914 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 915 _("Can't update file at '%s'"), relpath); 916 } 917 918 op->src_stream = src_stream; 919 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) 920 : NULL; 921 922 op->base_stream = base_stream; 923 op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum, 924 mtcc->pool) 925 : NULL; 926 927 return SVN_NO_ERROR; 928} 929 930svn_error_t * 931svn_client__mtcc_check_path(svn_node_kind_t *kind, 932 const char *relpath, 933 svn_boolean_t check_repository, 934 svn_client__mtcc_t *mtcc, 935 apr_pool_t *scratch_pool) 936{ 937 const char *origin_relpath; 938 svn_revnum_t origin_rev; 939 mtcc_op_t *op; 940 941 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 942 943 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc) 944 && !mtcc->root_op->performed_stat) 945 { 946 /* We know nothing about the root. Perhaps it is a file? */ 947 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, 948 kind, scratch_pool)); 949 950 mtcc->root_op->performed_stat = TRUE; 951 if (*kind == svn_node_file) 952 { 953 mtcc->root_op->kind = OP_OPEN_FILE; 954 mtcc->root_op->children = NULL; 955 } 956 return SVN_NO_ERROR; 957 } 958 959 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, 960 FALSE, mtcc->pool, scratch_pool)); 961 962 if (!op || (check_repository && !op->performed_stat)) 963 { 964 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, 965 relpath, TRUE, mtcc, 966 scratch_pool, scratch_pool)); 967 968 if (!origin_relpath) 969 *kind = svn_node_none; 970 else 971 SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath, 972 origin_rev, kind, scratch_pool)); 973 974 if (op && *kind == svn_node_dir) 975 { 976 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 977 op->performed_stat = TRUE; 978 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 979 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 980 _("Can't perform file operation " 981 "on '%s' as it is not a file"), 982 relpath); 983 } 984 else if (op && *kind == svn_node_file) 985 { 986 if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 987 op->performed_stat = TRUE; 988 else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 989 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 990 _("Can't perform directory operation " 991 "on '%s' as it is not a directory"), 992 relpath); 993 } 994 else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE)) 995 { 996 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 997 _("Can't open '%s' as it does not exist"), 998 relpath); 999 } 1000 1001 return SVN_NO_ERROR; 1002 } 1003 1004 /* op != NULL */ 1005 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1006 { 1007 *kind = svn_node_dir; 1008 return SVN_NO_ERROR; 1009 } 1010 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1011 { 1012 *kind = svn_node_file; 1013 return SVN_NO_ERROR; 1014 } 1015 SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */ 1016} 1017 1018static svn_error_t * 1019commit_properties(const svn_delta_editor_t *editor, 1020 const mtcc_op_t *op, 1021 void *node_baton, 1022 apr_pool_t *scratch_pool) 1023{ 1024 int i; 1025 apr_pool_t *iterpool; 1026 1027 if (!op->prop_mods || op->prop_mods->nelts == 0) 1028 return SVN_NO_ERROR; 1029 1030 iterpool = svn_pool_create(scratch_pool); 1031 for (i = 0; i < op->prop_mods->nelts; i++) 1032 { 1033 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t); 1034 1035 svn_pool_clear(iterpool); 1036 1037 if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR) 1038 SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value, 1039 iterpool)); 1040 else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE) 1041 SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value, 1042 iterpool)); 1043 } 1044 1045 svn_pool_destroy(iterpool); 1046 return SVN_NO_ERROR; 1047} 1048 1049/* Handles updating a file to a delta editor and then closes it */ 1050static svn_error_t * 1051commit_file(const svn_delta_editor_t *editor, 1052 mtcc_op_t *op, 1053 void *file_baton, 1054 const char *session_url, 1055 const char *relpath, 1056 svn_client_ctx_t *ctx, 1057 apr_pool_t *scratch_pool) 1058{ 1059 const char *text_checksum = NULL; 1060 svn_checksum_t *src_checksum = op->src_checksum; 1061 SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool)); 1062 1063 if (op->src_stream) 1064 { 1065 const char *base_checksum = NULL; 1066 apr_pool_t *txdelta_pool = scratch_pool; 1067 svn_txdelta_window_handler_t window_handler; 1068 void *handler_baton; 1069 svn_stream_t *src_stream = op->src_stream; 1070 1071 if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5) 1072 base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool); 1073 1074 /* ### TODO: Future enhancement: Allocate in special pool and send 1075 files after the true edit operation, like a wc commit */ 1076 SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool, 1077 &window_handler, &handler_baton)); 1078 1079 if (ctx->notify_func2) 1080 { 1081 svn_wc_notify_t *notify; 1082 1083 notify = svn_wc_create_notify_url( 1084 svn_path_url_add_component2(session_url, relpath, 1085 scratch_pool), 1086 svn_wc_notify_commit_postfix_txdelta, 1087 scratch_pool); 1088 1089 notify->path = relpath; 1090 notify->kind = svn_node_file; 1091 1092 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 1093 } 1094 1095 if (window_handler != svn_delta_noop_window_handler) 1096 { 1097 if (!src_checksum || src_checksum->kind != svn_checksum_md5) 1098 src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL, 1099 svn_checksum_md5, 1100 TRUE, scratch_pool); 1101 1102 if (!op->base_stream) 1103 SVN_ERR(svn_txdelta_send_stream(src_stream, 1104 window_handler, handler_baton, NULL, 1105 scratch_pool)); 1106 else 1107 SVN_ERR(svn_txdelta_run(op->base_stream, src_stream, 1108 window_handler, handler_baton, 1109 svn_checksum_md5, NULL, 1110 ctx->cancel_func, ctx->cancel_baton, 1111 scratch_pool, scratch_pool)); 1112 } 1113 1114 SVN_ERR(svn_stream_close(src_stream)); 1115 if (op->base_stream) 1116 SVN_ERR(svn_stream_close(op->base_stream)); 1117 } 1118 1119 if (src_checksum && src_checksum->kind == svn_checksum_md5) 1120 text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool); 1121 1122 return svn_error_trace(editor->close_file(file_baton, text_checksum, 1123 scratch_pool)); 1124} 1125 1126/* Handles updating a directory to a delta editor and then closes it */ 1127static svn_error_t * 1128commit_directory(const svn_delta_editor_t *editor, 1129 mtcc_op_t *op, 1130 const char *relpath, 1131 svn_revnum_t base_rev, 1132 void *dir_baton, 1133 const char *session_url, 1134 svn_client_ctx_t *ctx, 1135 apr_pool_t *scratch_pool) 1136{ 1137 SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool)); 1138 1139 if (op->children && op->children->nelts > 0) 1140 { 1141 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1142 int i; 1143 1144 for (i = 0; i < op->children->nelts; i++) 1145 { 1146 mtcc_op_t *cop; 1147 const char * child_relpath; 1148 void *child_baton; 1149 1150 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 1151 1152 svn_pool_clear(iterpool); 1153 1154 if (ctx->cancel_func) 1155 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1156 1157 child_relpath = svn_relpath_join(relpath, cop->name, iterpool); 1158 1159 switch (cop->kind) 1160 { 1161 case OP_DELETE: 1162 SVN_ERR(editor->delete_entry(child_relpath, base_rev, 1163 dir_baton, iterpool)); 1164 break; 1165 1166 case OP_ADD_DIR: 1167 SVN_ERR(editor->add_directory(child_relpath, dir_baton, 1168 cop->src_relpath 1169 ? svn_path_url_add_component2( 1170 session_url, 1171 cop->src_relpath, 1172 iterpool) 1173 : NULL, 1174 cop->src_rev, 1175 iterpool, &child_baton)); 1176 SVN_ERR(commit_directory(editor, cop, child_relpath, 1177 SVN_INVALID_REVNUM, child_baton, 1178 session_url, ctx, iterpool)); 1179 break; 1180 case OP_OPEN_DIR: 1181 SVN_ERR(editor->open_directory(child_relpath, dir_baton, 1182 base_rev, iterpool, &child_baton)); 1183 SVN_ERR(commit_directory(editor, cop, child_relpath, 1184 base_rev, child_baton, 1185 session_url, ctx, iterpool)); 1186 break; 1187 1188 case OP_ADD_FILE: 1189 SVN_ERR(editor->add_file(child_relpath, dir_baton, 1190 cop->src_relpath 1191 ? svn_path_url_add_component2( 1192 session_url, 1193 cop->src_relpath, 1194 iterpool) 1195 : NULL, 1196 cop->src_rev, 1197 iterpool, &child_baton)); 1198 SVN_ERR(commit_file(editor, cop, child_baton, 1199 session_url, child_relpath, ctx, iterpool)); 1200 break; 1201 case OP_OPEN_FILE: 1202 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev, 1203 iterpool, &child_baton)); 1204 SVN_ERR(commit_file(editor, cop, child_baton, 1205 session_url, child_relpath, ctx, iterpool)); 1206 break; 1207 1208 default: 1209 SVN_ERR_MALFUNCTION(); 1210 } 1211 } 1212 } 1213 1214 return svn_error_trace(editor->close_directory(dir_baton, scratch_pool)); 1215} 1216 1217 1218/* Helper function to recursively create svn_client_commit_item3_t items 1219 to provide to the log message callback */ 1220static svn_error_t * 1221add_commit_items(mtcc_op_t *op, 1222 const char *session_url, 1223 const char *url, 1224 apr_array_header_t *commit_items, 1225 apr_pool_t *result_pool, 1226 apr_pool_t *scratch_pool) 1227{ 1228 if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE) 1229 || (op->prop_mods && op->prop_mods->nelts) 1230 || (op->src_stream)) 1231 { 1232 svn_client_commit_item3_t *item; 1233 1234 item = svn_client_commit_item3_create(result_pool); 1235 1236 item->path = NULL; 1237 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) 1238 item->kind = svn_node_dir; 1239 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) 1240 item->kind = svn_node_file; 1241 else 1242 item->kind = svn_node_unknown; 1243 1244 item->url = apr_pstrdup(result_pool, url); 1245 item->session_relpath = svn_uri_skip_ancestor(session_url, item->url, 1246 result_pool); 1247 1248 if (op->src_relpath) 1249 { 1250 item->copyfrom_url = svn_path_url_add_component2(session_url, 1251 op->src_relpath, 1252 result_pool); 1253 item->copyfrom_rev = op->src_rev; 1254 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; 1255 } 1256 else 1257 item->copyfrom_rev = SVN_INVALID_REVNUM; 1258 1259 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) 1260 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1261 else if (op->kind == OP_DELETE) 1262 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1263 /* else item->state_flags = 0; */ 1264 1265 if (op->prop_mods && op->prop_mods->nelts) 1266 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1267 1268 if (op->src_stream) 1269 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; 1270 1271 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1272 } 1273 1274 if (op->children && op->children->nelts) 1275 { 1276 int i; 1277 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1278 1279 for (i = 0; i < op->children->nelts; i++) 1280 { 1281 mtcc_op_t *cop; 1282 const char * child_url; 1283 1284 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); 1285 1286 svn_pool_clear(iterpool); 1287 1288 child_url = svn_path_url_add_component2(url, cop->name, iterpool); 1289 1290 SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items, 1291 result_pool, iterpool)); 1292 } 1293 1294 svn_pool_destroy(iterpool); 1295 } 1296 1297 return SVN_NO_ERROR; 1298} 1299 1300svn_error_t * 1301svn_client__mtcc_commit(apr_hash_t *revprop_table, 1302 svn_commit_callback2_t commit_callback, 1303 void *commit_baton, 1304 svn_client__mtcc_t *mtcc, 1305 apr_pool_t *scratch_pool) 1306{ 1307 const svn_delta_editor_t *editor; 1308 void *edit_baton; 1309 void *root_baton; 1310 apr_hash_t *commit_revprops; 1311 svn_node_kind_t kind; 1312 svn_error_t *err; 1313 const char *session_url; 1314 const char *log_msg; 1315 1316 if (MTCC_UNMODIFIED(mtcc)) 1317 { 1318 /* No changes -> no revision. Easy out */ 1319 svn_pool_destroy(mtcc->pool); 1320 return SVN_NO_ERROR; 1321 } 1322 1323 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool)); 1324 1325 if (mtcc->root_op->kind != OP_OPEN_DIR) 1326 { 1327 const char *name; 1328 1329 svn_uri_split(&session_url, &name, session_url, scratch_pool); 1330 1331 if (*name) 1332 { 1333 SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool)); 1334 1335 SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool)); 1336 } 1337 } 1338 1339 /* Create new commit items and add them to the array. */ 1340 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx)) 1341 { 1342 svn_client_commit_item3_t *item; 1343 const char *tmp_file; 1344 apr_array_header_t *commit_items 1345 = apr_array_make(scratch_pool, 32, sizeof(item)); 1346 1347 SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url, 1348 commit_items, scratch_pool, scratch_pool)); 1349 1350 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 1351 mtcc->ctx, scratch_pool)); 1352 1353 if (! log_msg) 1354 return SVN_NO_ERROR; 1355 } 1356 else 1357 log_msg = ""; 1358 1359 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1360 log_msg, mtcc->ctx, scratch_pool)); 1361 1362 /* Ugly corner case: The ra session might have died while we were waiting 1363 for the callback */ 1364 1365 err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind, 1366 scratch_pool); 1367 1368 if (err) 1369 { 1370 svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session, 1371 session_url, 1372 NULL, mtcc->ctx, 1373 mtcc->pool, 1374 scratch_pool); 1375 1376 if (err2) 1377 { 1378 svn_pool_destroy(mtcc->pool); 1379 return svn_error_trace(svn_error_compose_create(err, err2)); 1380 } 1381 svn_error_clear(err); 1382 1383 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", 1384 mtcc->base_revision, &kind, scratch_pool)); 1385 } 1386 1387 if (kind != svn_node_dir) 1388 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1389 _("Can't commit to '%s' because it " 1390 "is not a directory"), 1391 session_url); 1392 1393 /* Beware that the editor object must not live longer than the MTCC. 1394 Otherwise, txn objects etc. in EDITOR may live longer than their 1395 respective FS objects. So, we can't use SCRATCH_POOL here. */ 1396 SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton, 1397 commit_revprops, 1398 commit_callback, commit_baton, 1399 NULL /* lock_tokens */, 1400 FALSE /* keep_locks */, 1401 mtcc->pool)); 1402 1403 err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton); 1404 1405 if (!err) 1406 err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision, 1407 root_baton, session_url, mtcc->ctx, scratch_pool); 1408 1409 if (!err) 1410 { 1411 if (mtcc->ctx->notify_func2) 1412 { 1413 svn_wc_notify_t *notify; 1414 notify = svn_wc_create_notify_url(session_url, 1415 svn_wc_notify_commit_finalizing, 1416 scratch_pool); 1417 mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify, 1418 scratch_pool); 1419 } 1420 SVN_ERR(editor->close_edit(edit_baton, scratch_pool)); 1421 } 1422 else 1423 err = svn_error_compose_create(err, 1424 editor->abort_edit(edit_baton, scratch_pool)); 1425 1426 svn_pool_destroy(mtcc->pool); 1427 1428 return svn_error_trace(err); 1429} 1430