1/* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 */ 21 22#include "svn_hash.h" 23#include "svn_cmdline.h" 24#include "svn_config.h" 25#include "svn_pools.h" 26#include "svn_delta.h" 27#include "svn_dirent_uri.h" 28#include "svn_path.h" 29#include "svn_props.h" 30#include "svn_auth.h" 31#include "svn_opt.h" 32#include "svn_ra.h" 33#include "svn_utf.h" 34#include "svn_subst.h" 35#include "svn_string.h" 36#include "svn_version.h" 37 38#include "private/svn_opt_private.h" 39#include "private/svn_ra_private.h" 40#include "private/svn_cmdline_private.h" 41#include "private/svn_subr_private.h" 42 43#include "sync.h" 44 45#include "svn_private_config.h" 46 47#include <apr_signal.h> 48#include <apr_uuid.h> 49 50static svn_opt_subcommand_t initialize_cmd, 51 synchronize_cmd, 52 copy_revprops_cmd, 53 info_cmd, 54 help_cmd; 55 56enum svnsync__opt { 57 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID, 58 svnsync_opt_force_interactive, 59 svnsync_opt_no_auth_cache, 60 svnsync_opt_auth_username, 61 svnsync_opt_auth_password, 62 svnsync_opt_source_username, 63 svnsync_opt_source_password, 64 svnsync_opt_sync_username, 65 svnsync_opt_sync_password, 66 svnsync_opt_config_dir, 67 svnsync_opt_config_options, 68 svnsync_opt_source_prop_encoding, 69 svnsync_opt_disable_locking, 70 svnsync_opt_version, 71 svnsync_opt_trust_server_cert, 72 svnsync_opt_allow_non_empty, 73 svnsync_opt_steal_lock 74}; 75 76#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \ 77 svnsync_opt_force_interactive, \ 78 svnsync_opt_no_auth_cache, \ 79 svnsync_opt_auth_username, \ 80 svnsync_opt_auth_password, \ 81 svnsync_opt_trust_server_cert, \ 82 svnsync_opt_source_username, \ 83 svnsync_opt_source_password, \ 84 svnsync_opt_sync_username, \ 85 svnsync_opt_sync_password, \ 86 svnsync_opt_config_dir, \ 87 svnsync_opt_config_options 88 89static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] = 90 { 91 { "initialize", initialize_cmd, { "init" }, 92 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n" 93 "\n" 94 "Initialize a destination repository for synchronization from\n" 95 "another repository.\n" 96 "\n" 97 "If the source URL is not the root of a repository, only the\n" 98 "specified part of the repository will be synchronized.\n" 99 "\n" 100 "The destination URL must point to the root of a repository which\n" 101 "has been configured to allow revision property changes. In\n" 102 "the general case, the destination repository must contain no\n" 103 "committed revisions. Use --allow-non-empty to override this\n" 104 "restriction, which will cause svnsync to assume that any revisions\n" 105 "already present in the destination repository perfectly mirror\n" 106 "their counterparts in the source repository. (This is useful\n" 107 "when initializing a copy of a repository as a mirror of that same\n" 108 "repository, for example.)\n" 109 "\n" 110 "You should not commit to, or make revision property changes in,\n" 111 "the destination repository by any method other than 'svnsync'.\n" 112 "In other words, the destination repository should be a read-only\n" 113 "mirror of the source repository.\n"), 114 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 115 svnsync_opt_allow_non_empty, svnsync_opt_disable_locking, 116 svnsync_opt_steal_lock } }, 117 { "synchronize", synchronize_cmd, { "sync" }, 118 N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n" 119 "\n" 120 "Transfer all pending revisions to the destination from the source\n" 121 "with which it was initialized.\n" 122 "\n" 123 "If SOURCE_URL is provided, use that as the source repository URL,\n" 124 "ignoring what is recorded in the destination repository as the\n" 125 "source URL. Specifying SOURCE_URL is recommended in particular\n" 126 "if untrusted users/administrators may have write access to the\n" 127 "DEST_URL repository.\n"), 128 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 129 svnsync_opt_disable_locking, svnsync_opt_steal_lock } }, 130 { "copy-revprops", copy_revprops_cmd, { 0 }, 131 N_("usage:\n" 132 "\n" 133 " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n" 134 " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n" 135 "\n" 136 "Copy the revision properties in a given range of revisions to the\n" 137 "destination from the source with which it was initialized. If the\n" 138 "revision range is not specified, it defaults to all revisions in\n" 139 "the DEST_URL repository. Note also that the 'HEAD' revision is the\n" 140 "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n" 141 "\n" 142 "If SOURCE_URL is provided, use that as the source repository URL,\n" 143 "ignoring what is recorded in the destination repository as the\n" 144 "source URL. Specifying SOURCE_URL is recommended in particular\n" 145 "if untrusted users/administrators may have write access to the\n" 146 "DEST_URL repository.\n" 147 "\n" 148 "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"), 149 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r', 150 svnsync_opt_disable_locking, svnsync_opt_steal_lock } }, 151 { "info", info_cmd, { 0 }, 152 N_("usage: svnsync info DEST_URL\n" 153 "\n" 154 "Print information about the synchronization destination repository\n" 155 "located at DEST_URL.\n"), 156 { SVNSYNC_OPTS_DEFAULT } }, 157 { "help", help_cmd, { "?", "h" }, 158 N_("usage: svnsync help [SUBCOMMAND...]\n" 159 "\n" 160 "Describe the usage of this program or its subcommands.\n"), 161 { 0 } }, 162 { NULL, NULL, { 0 }, NULL, { 0 } } 163 }; 164 165static const apr_getopt_option_t svnsync_options[] = 166 { 167 {"quiet", 'q', 0, 168 N_("print as little as possible") }, 169 {"revision", 'r', 1, 170 N_("operate on revision ARG (or range ARG1:ARG2)\n" 171 " " 172 "A revision argument can be one of:\n" 173 " " 174 " NUMBER revision number\n" 175 " " 176 " 'HEAD' latest in repository") }, 177 {"allow-non-empty", svnsync_opt_allow_non_empty, 0, 178 N_("allow a non-empty destination repository") }, 179 {"non-interactive", svnsync_opt_non_interactive, 0, 180 N_("do no interactive prompting (default is to prompt\n" 181 " " 182 "only if standard input is a terminal device)")}, 183 {"force-interactive", svnsync_opt_force_interactive, 0, 184 N_("do interactive prompting even if standard input\n" 185 " " 186 "is not a terminal device")}, 187 {"no-auth-cache", svnsync_opt_no_auth_cache, 0, 188 N_("do not cache authentication tokens") }, 189 {"username", svnsync_opt_auth_username, 1, 190 N_("specify a username ARG (deprecated;\n" 191 " " 192 "see --source-username and --sync-username)") }, 193 {"password", svnsync_opt_auth_password, 1, 194 N_("specify a password ARG (deprecated;\n" 195 " " 196 "see --source-password and --sync-password)") }, 197 {"trust-server-cert", svnsync_opt_trust_server_cert, 0, 198 N_("accept SSL server certificates from unknown\n" 199 " " 200 "certificate authorities without prompting (but only\n" 201 " " 202 "with '--non-interactive')") }, 203 {"source-username", svnsync_opt_source_username, 1, 204 N_("connect to source repository with username ARG") }, 205 {"source-password", svnsync_opt_source_password, 1, 206 N_("connect to source repository with password ARG") }, 207 {"sync-username", svnsync_opt_sync_username, 1, 208 N_("connect to sync repository with username ARG") }, 209 {"sync-password", svnsync_opt_sync_password, 1, 210 N_("connect to sync repository with password ARG") }, 211 {"config-dir", svnsync_opt_config_dir, 1, 212 N_("read user configuration files from directory ARG")}, 213 {"config-option", svnsync_opt_config_options, 1, 214 N_("set user configuration option in the format:\n" 215 " " 216 " FILE:SECTION:OPTION=[VALUE]\n" 217 " " 218 "For example:\n" 219 " " 220 " servers:global:http-library=serf")}, 221 {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1, 222 N_("convert translatable properties from encoding ARG\n" 223 " " 224 "to UTF-8. If not specified, then properties are\n" 225 " " 226 "presumed to be encoded in UTF-8.")}, 227 {"disable-locking", svnsync_opt_disable_locking, 0, 228 N_("Disable built-in locking. Use of this option can\n" 229 " " 230 "corrupt the mirror unless you ensure that no other\n" 231 " " 232 "instance of svnsync is running concurrently.")}, 233 {"steal-lock", svnsync_opt_steal_lock, 0, 234 N_("Steal locks as necessary. Use, with caution,\n" 235 " " 236 "if your mirror repository contains stale locks\n" 237 " " 238 "and is not being concurrently accessed by another\n" 239 " " 240 "svnsync instance.")}, 241 {"version", svnsync_opt_version, 0, 242 N_("show program version information")}, 243 {"help", 'h', 0, 244 N_("show help on a subcommand")}, 245 {NULL, '?', 0, 246 N_("show help on a subcommand")}, 247 { 0, 0, 0, 0 } 248 }; 249 250typedef struct opt_baton_t { 251 svn_boolean_t non_interactive; 252 svn_boolean_t trust_server_cert; 253 svn_boolean_t no_auth_cache; 254 svn_auth_baton_t *source_auth_baton; 255 svn_auth_baton_t *sync_auth_baton; 256 const char *source_username; 257 const char *source_password; 258 const char *sync_username; 259 const char *sync_password; 260 const char *config_dir; 261 apr_hash_t *config; 262 const char *source_prop_encoding; 263 svn_boolean_t disable_locking; 264 svn_boolean_t steal_lock; 265 svn_boolean_t quiet; 266 svn_boolean_t allow_non_empty; 267 svn_boolean_t version; 268 svn_boolean_t help; 269 svn_opt_revision_t start_rev; 270 svn_opt_revision_t end_rev; 271} opt_baton_t; 272 273 274 275 276/*** Helper functions ***/ 277 278 279/* Global record of whether the user has requested cancellation. */ 280static volatile sig_atomic_t cancelled = FALSE; 281 282 283/* Callback function for apr_signal(). */ 284static void 285signal_handler(int signum) 286{ 287 apr_signal(signum, SIG_IGN); 288 cancelled = TRUE; 289} 290 291 292/* Cancellation callback function. */ 293static svn_error_t * 294check_cancel(void *baton) 295{ 296 if (cancelled) 297 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 298 else 299 return SVN_NO_ERROR; 300} 301 302 303/* Check that the version of libraries in use match what we expect. */ 304static svn_error_t * 305check_lib_versions(void) 306{ 307 static const svn_version_checklist_t checklist[] = 308 { 309 { "svn_subr", svn_subr_version }, 310 { "svn_delta", svn_delta_version }, 311 { "svn_ra", svn_ra_version }, 312 { NULL, NULL } 313 }; 314 SVN_VERSION_DEFINE(my_version); 315 316 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 317} 318 319 320/* Implements `svn_ra__lock_retry_func_t'. */ 321static svn_error_t * 322lock_retry_func(void *baton, 323 const svn_string_t *reposlocktoken, 324 apr_pool_t *pool) 325{ 326 return svn_cmdline_printf(pool, 327 _("Failed to get lock on destination " 328 "repos, currently held by '%s'\n"), 329 reposlocktoken->data); 330} 331 332/* Acquire a lock (of sorts) on the repository associated with the 333 * given RA SESSION. This lock is just a revprop change attempt in a 334 * time-delay loop. This function is duplicated by svnrdump in 335 * svnrdump/load_editor.c 336 */ 337static svn_error_t * 338get_lock(const svn_string_t **lock_string_p, 339 svn_ra_session_t *session, 340 svn_boolean_t steal_lock, 341 apr_pool_t *pool) 342{ 343 svn_error_t *err; 344 svn_boolean_t be_atomic; 345 const svn_string_t *stolen_lock; 346 347 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 348 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 349 pool)); 350 if (! be_atomic) 351 { 352 /* Pre-1.7 server. Can't lock without a race condition. 353 See issue #3546. 354 */ 355 err = svn_error_create( 356 SVN_ERR_UNSUPPORTED_FEATURE, NULL, 357 _("Target server does not support atomic revision property " 358 "edits; consider upgrading it to 1.7 or using an external " 359 "locking program")); 360 svn_handle_warning2(stderr, err, "svnsync: "); 361 svn_error_clear(err); 362 } 363 364 err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session, 365 SVNSYNC_PROP_LOCK, steal_lock, 366 10 /* retries */, lock_retry_func, NULL, 367 check_cancel, NULL, pool); 368 if (!err && stolen_lock) 369 { 370 return svn_cmdline_printf(pool, 371 _("Stole lock previously held by '%s'\n"), 372 stolen_lock->data); 373 } 374 return err; 375} 376 377 378/* Baton for the various subcommands to share. */ 379typedef struct subcommand_baton_t { 380 /* common to all subcommands */ 381 apr_hash_t *config; 382 svn_ra_callbacks2_t source_callbacks; 383 svn_ra_callbacks2_t sync_callbacks; 384 svn_boolean_t quiet; 385 svn_boolean_t allow_non_empty; 386 const char *to_url; 387 388 /* initialize, synchronize, and copy-revprops only */ 389 const char *source_prop_encoding; 390 391 /* initialize only */ 392 const char *from_url; 393 394 /* synchronize only */ 395 svn_revnum_t committed_rev; 396 397 /* copy-revprops only */ 398 svn_revnum_t start_rev; 399 svn_revnum_t end_rev; 400 401} subcommand_baton_t; 402 403typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session, 404 subcommand_baton_t *baton, 405 apr_pool_t *pool); 406 407 408/* Lock the repository associated with RA SESSION, then execute the 409 * given FUNC/BATON pair while holding the lock. Finally, drop the 410 * lock once it finishes. 411 */ 412static svn_error_t * 413with_locked(svn_ra_session_t *session, 414 with_locked_func_t func, 415 subcommand_baton_t *baton, 416 svn_boolean_t steal_lock, 417 apr_pool_t *pool) 418{ 419 const svn_string_t *lock_string; 420 svn_error_t *err; 421 422 SVN_ERR(get_lock(&lock_string, session, steal_lock, pool)); 423 424 err = func(session, baton, pool); 425 return svn_error_compose_create(err, 426 svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK, 427 lock_string, pool)); 428} 429 430 431/* Callback function for the RA session's open_tmp_file() 432 * requirements. 433 */ 434static svn_error_t * 435open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool) 436{ 437 return svn_io_open_unique_file3(fp, NULL, NULL, 438 svn_io_file_del_on_pool_cleanup, 439 pool, pool); 440} 441 442 443/* Return SVN_NO_ERROR iff URL identifies the root directory of the 444 * repository associated with RA session SESS. 445 */ 446static svn_error_t * 447check_if_session_is_at_repos_root(svn_ra_session_t *sess, 448 const char *url, 449 apr_pool_t *pool) 450{ 451 const char *sess_root; 452 453 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool)); 454 455 if (strcmp(url, sess_root) == 0) 456 return SVN_NO_ERROR; 457 else 458 return svn_error_createf 459 (APR_EINVAL, NULL, 460 _("Session is rooted at '%s' but the repos root is '%s'"), 461 url, sess_root); 462} 463 464 465/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from 466 * revision REV of the repository associated with RA session SESSION. 467 * 468 * For REV zero, don't remove properties with the "svn:sync-" prefix. 469 * 470 * All allocations will be done in a subpool of POOL. 471 */ 472static svn_error_t * 473remove_props_not_in_source(svn_ra_session_t *session, 474 svn_revnum_t rev, 475 apr_hash_t *source_props, 476 apr_hash_t *target_props, 477 apr_pool_t *pool) 478{ 479 apr_pool_t *subpool = svn_pool_create(pool); 480 apr_hash_index_t *hi; 481 482 for (hi = apr_hash_first(pool, target_props); 483 hi; 484 hi = apr_hash_next(hi)) 485 { 486 const char *propname = svn__apr_hash_index_key(hi); 487 488 svn_pool_clear(subpool); 489 490 if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX, 491 sizeof(SVNSYNC_PROP_PREFIX) - 1)) 492 continue; 493 494 /* Delete property if the name can't be found in SOURCE_PROPS. */ 495 if (! svn_hash_gets(source_props, propname)) 496 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL, 497 NULL, subpool)); 498 } 499 500 svn_pool_destroy(subpool); 501 502 return SVN_NO_ERROR; 503} 504 505/* Filter callback function. 506 * Takes a property name KEY, and is expected to return TRUE if the property 507 * should be filtered out (ie. not be copied to the target list), or FALSE if 508 * not. 509 */ 510typedef svn_boolean_t (*filter_func_t)(const char *key); 511 512/* Make a new set of properties, by copying those properties in PROPS for which 513 * the filter FILTER returns FALSE. 514 * 515 * The number of properties not copied will be stored in FILTERED_COUNT. 516 * 517 * The returned set of properties is allocated from POOL. 518 */ 519static apr_hash_t * 520filter_props(int *filtered_count, apr_hash_t *props, 521 filter_func_t filter, 522 apr_pool_t *pool) 523{ 524 apr_hash_index_t *hi; 525 apr_hash_t *filtered = apr_hash_make(pool); 526 *filtered_count = 0; 527 528 for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi)) 529 { 530 const char *propname = svn__apr_hash_index_key(hi); 531 void *propval = svn__apr_hash_index_val(hi); 532 533 /* Copy all properties: 534 - not matching the exclude pattern if provided OR 535 - matching the include pattern if provided */ 536 if (!filter || !filter(propname)) 537 { 538 svn_hash_sets(filtered, propname, propval); 539 } 540 else 541 { 542 *filtered_count += 1; 543 } 544 } 545 546 return filtered; 547} 548 549 550/* Write the set of revision properties REV_PROPS to revision REV to the 551 * repository associated with RA session SESSION. 552 * Omit any properties whose names are in the svnsync property name space, 553 * and set *FILTERED_COUNT to the number of properties thus omitted. 554 * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval. 555 * 556 * All allocations will be done in a subpool of POOL. 557 */ 558static svn_error_t * 559write_revprops(int *filtered_count, 560 svn_ra_session_t *session, 561 svn_revnum_t rev, 562 apr_hash_t *rev_props, 563 apr_pool_t *pool) 564{ 565 apr_pool_t *subpool = svn_pool_create(pool); 566 apr_hash_index_t *hi; 567 568 *filtered_count = 0; 569 570 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi)) 571 { 572 const char *propname = svn__apr_hash_index_key(hi); 573 const svn_string_t *propval = svn__apr_hash_index_val(hi); 574 575 svn_pool_clear(subpool); 576 577 if (strncmp(propname, SVNSYNC_PROP_PREFIX, 578 sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0) 579 { 580 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL, 581 propval, subpool)); 582 } 583 else 584 { 585 *filtered_count += 1; 586 } 587 } 588 589 svn_pool_destroy(subpool); 590 591 return SVN_NO_ERROR; 592} 593 594 595static svn_error_t * 596log_properties_copied(svn_boolean_t syncprops_found, 597 svn_revnum_t rev, 598 apr_pool_t *pool) 599{ 600 if (syncprops_found) 601 SVN_ERR(svn_cmdline_printf(pool, 602 _("Copied properties for revision %ld " 603 "(%s* properties skipped).\n"), 604 rev, SVNSYNC_PROP_PREFIX)); 605 else 606 SVN_ERR(svn_cmdline_printf(pool, 607 _("Copied properties for revision %ld.\n"), 608 rev)); 609 610 return SVN_NO_ERROR; 611} 612 613/* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and 614 * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line 615 * endings, if either of those numbers is non-zero. */ 616static svn_error_t * 617log_properties_normalized(int normalized_rev_props_count, 618 int normalized_node_props_count, 619 apr_pool_t *pool) 620{ 621 if (normalized_rev_props_count > 0 || normalized_node_props_count > 0) 622 SVN_ERR(svn_cmdline_printf(pool, 623 _("NOTE: Normalized %s* properties " 624 "to LF line endings (%d rev-props, " 625 "%d node-props).\n"), 626 SVN_PROP_PREFIX, 627 normalized_rev_props_count, 628 normalized_node_props_count)); 629 return SVN_NO_ERROR; 630} 631 632 633/* Copy all the revision properties, except for those that have the 634 * "svn:sync-" prefix, from revision REV of the repository associated 635 * with RA session FROM_SESSION, to the repository associated with RA 636 * session TO_SESSION. 637 * 638 * If SYNC is TRUE, then properties on the destination revision that 639 * do not exist on the source revision will be removed. 640 * 641 * If QUIET is FALSE, then log_properties_copied() is called to log that 642 * properties were copied for revision REV. 643 * 644 * Make sure the values of svn:* revision properties use only LF (\n) 645 * line ending style, correcting their values as necessary. The number 646 * of properties that were normalized is returned in *NORMALIZED_COUNT. 647 */ 648static svn_error_t * 649copy_revprops(svn_ra_session_t *from_session, 650 svn_ra_session_t *to_session, 651 svn_revnum_t rev, 652 svn_boolean_t sync, 653 svn_boolean_t quiet, 654 const char *source_prop_encoding, 655 int *normalized_count, 656 apr_pool_t *pool) 657{ 658 apr_pool_t *subpool = svn_pool_create(pool); 659 apr_hash_t *existing_props, *rev_props; 660 int filtered_count = 0; 661 662 /* Get the list of revision properties on REV of TARGET. We're only interested 663 in the property names, but we'll get the values 'for free'. */ 664 if (sync) 665 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool)); 666 else 667 existing_props = NULL; 668 669 /* Get the list of revision properties on REV of SOURCE. */ 670 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool)); 671 672 /* If necessary, normalize encoding and line ending style and return the count 673 of EOL-normalized properties in int *NORMALIZED_COUNT. */ 674 SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count, 675 source_prop_encoding, pool)); 676 677 /* Copy all but the svn:svnsync properties. */ 678 SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool)); 679 680 /* Delete those properties that were in TARGET but not in SOURCE */ 681 if (sync) 682 SVN_ERR(remove_props_not_in_source(to_session, rev, 683 rev_props, existing_props, pool)); 684 685 if (! quiet) 686 SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool)); 687 688 svn_pool_destroy(subpool); 689 690 return SVN_NO_ERROR; 691} 692 693 694/* Return a subcommand baton allocated from POOL and populated with 695 data from the provided parameters, which include the global 696 OPT_BATON options structure and a handful of other options. Not 697 all parameters are used in all subcommands -- see 698 subcommand_baton_t's definition for details. */ 699static subcommand_baton_t * 700make_subcommand_baton(opt_baton_t *opt_baton, 701 const char *to_url, 702 const char *from_url, 703 svn_revnum_t start_rev, 704 svn_revnum_t end_rev, 705 apr_pool_t *pool) 706{ 707 subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b)); 708 b->config = opt_baton->config; 709 b->source_callbacks.open_tmp_file = open_tmp_file; 710 b->source_callbacks.auth_baton = opt_baton->source_auth_baton; 711 b->sync_callbacks.open_tmp_file = open_tmp_file; 712 b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton; 713 b->quiet = opt_baton->quiet; 714 b->allow_non_empty = opt_baton->allow_non_empty; 715 b->to_url = to_url; 716 b->source_prop_encoding = opt_baton->source_prop_encoding; 717 b->from_url = from_url; 718 b->start_rev = start_rev; 719 b->end_rev = end_rev; 720 return b; 721} 722 723static svn_error_t * 724open_target_session(svn_ra_session_t **to_session_p, 725 subcommand_baton_t *baton, 726 apr_pool_t *pool); 727 728 729/*** `svnsync init' ***/ 730 731/* Initialize the repository associated with RA session TO_SESSION, 732 * using information found in BATON, while the repository is 733 * locked. Implements `with_locked_func_t' interface. 734 */ 735static svn_error_t * 736do_initialize(svn_ra_session_t *to_session, 737 subcommand_baton_t *baton, 738 apr_pool_t *pool) 739{ 740 svn_ra_session_t *from_session; 741 svn_string_t *from_url; 742 svn_revnum_t latest, from_latest; 743 const char *uuid, *root_url; 744 int normalized_rev_props_count; 745 746 /* First, sanity check to see that we're copying into a brand new 747 repos. If we aren't, and we aren't being asked to forcibly 748 complete this initialization, that's a bad news. */ 749 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool)); 750 if ((latest != 0) && (! baton->allow_non_empty)) 751 return svn_error_create 752 (APR_EINVAL, NULL, 753 _("Destination repository already contains revision history; consider " 754 "using --allow-non-empty if the repository's revisions are known " 755 "to mirror their respective revisions in the source repository")); 756 757 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, 758 &from_url, pool)); 759 if (from_url && (! baton->allow_non_empty)) 760 return svn_error_createf 761 (APR_EINVAL, NULL, 762 _("Destination repository is already synchronizing from '%s'"), 763 from_url->data); 764 765 /* Now fill in our bookkeeping info in the dest repository. */ 766 767 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL, 768 &(baton->source_callbacks), baton, 769 baton->config, pool)); 770 SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool)); 771 772 /* If we're doing a partial replay, we have to check first if the server 773 supports this. */ 774 if (strcmp(root_url, baton->from_url) != 0) 775 { 776 svn_boolean_t server_supports_partial_replay; 777 svn_error_t *err = svn_ra_has_capability(from_session, 778 &server_supports_partial_replay, 779 SVN_RA_CAPABILITY_PARTIAL_REPLAY, 780 pool); 781 if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY) 782 return svn_error_trace(err); 783 784 if (err || !server_supports_partial_replay) 785 return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err, 786 NULL); 787 } 788 789 /* If we're initializing a non-empty destination, we'll make sure 790 that it at least doesn't have more revisions than the source. */ 791 if (latest != 0) 792 { 793 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); 794 if (from_latest < latest) 795 return svn_error_create 796 (APR_EINVAL, NULL, 797 _("Destination repository has more revisions than source " 798 "repository")); 799 } 800 801 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL, 802 svn_string_create(baton->from_url, pool), 803 pool)); 804 805 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool)); 806 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL, 807 svn_string_create(uuid, pool), pool)); 808 809 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, 810 NULL, svn_string_createf(pool, "%ld", latest), 811 pool)); 812 813 /* Copy all non-svnsync revprops from the LATEST rev in the source 814 repository into the destination, notifying about normalized 815 props, if any. When LATEST is 0, this serves the practical 816 purpose of initializing data that would otherwise be overlooked 817 by the sync process (which is going to begin with r1). When 818 LATEST is not 0, this really serves merely aesthetic and 819 informational purposes, keeping the output of this command 820 consistent while allowing folks to see what the latest revision is. */ 821 SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet, 822 baton->source_prop_encoding, &normalized_rev_props_count, 823 pool)); 824 825 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool)); 826 827 /* TODO: It would be nice if we could set the dest repos UUID to be 828 equal to the UUID of the source repos, at least optionally. That 829 way people could check out/log/diff using a local fast mirror, 830 but switch --relocate to the actual final repository in order to 831 make changes... But at this time, the RA layer doesn't have a 832 way to set a UUID. */ 833 834 return SVN_NO_ERROR; 835} 836 837 838/* SUBCOMMAND: init */ 839static svn_error_t * 840initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 841{ 842 const char *to_url, *from_url; 843 svn_ra_session_t *to_session; 844 opt_baton_t *opt_baton = b; 845 apr_array_header_t *targets; 846 subcommand_baton_t *baton; 847 848 SVN_ERR(svn_opt__args_to_target_array(&targets, os, 849 apr_array_make(pool, 0, 850 sizeof(const char *)), 851 pool)); 852 if (targets->nelts < 2) 853 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 854 if (targets->nelts > 2) 855 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 856 857 to_url = APR_ARRAY_IDX(targets, 0, const char *); 858 from_url = APR_ARRAY_IDX(targets, 1, const char *); 859 860 if (! svn_path_is_url(to_url)) 861 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 862 _("Path '%s' is not a URL"), to_url); 863 if (! svn_path_is_url(from_url)) 864 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 865 _("Path '%s' is not a URL"), from_url); 866 867 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool); 868 SVN_ERR(open_target_session(&to_session, baton, pool)); 869 if (opt_baton->disable_locking) 870 SVN_ERR(do_initialize(to_session, baton, pool)); 871 else 872 SVN_ERR(with_locked(to_session, do_initialize, baton, 873 opt_baton->steal_lock, pool)); 874 875 return SVN_NO_ERROR; 876} 877 878 879 880/*** `svnsync sync' ***/ 881 882/* Implements `svn_commit_callback2_t' interface. */ 883static svn_error_t * 884commit_callback(const svn_commit_info_t *commit_info, 885 void *baton, 886 apr_pool_t *pool) 887{ 888 subcommand_baton_t *sb = baton; 889 890 if (! sb->quiet) 891 { 892 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"), 893 commit_info->revision)); 894 } 895 896 sb->committed_rev = commit_info->revision; 897 898 return SVN_NO_ERROR; 899} 900 901 902/* Set *FROM_SESSION to an RA session associated with the source 903 * repository of the synchronization. If FROM_URL is non-NULL, use it 904 * as the source repository URL; otherwise, determine the source 905 * repository URL by reading svn:sync- properties from the destination 906 * repository (associated with TO_SESSION). Set LAST_MERGED_REV to 907 * the value of the property which records the most recently 908 * synchronized revision. 909 * 910 * CALLBACKS is a vtable of RA callbacks to provide when creating 911 * *FROM_SESSION. CONFIG is a configuration hash. 912 */ 913static svn_error_t * 914open_source_session(svn_ra_session_t **from_session, 915 svn_string_t **last_merged_rev, 916 const char *from_url, 917 svn_ra_session_t *to_session, 918 svn_ra_callbacks2_t *callbacks, 919 apr_hash_t *config, 920 void *baton, 921 apr_pool_t *pool) 922{ 923 apr_hash_t *props; 924 svn_string_t *from_url_str, *from_uuid_str; 925 926 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool)); 927 928 from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL); 929 from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID); 930 *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV); 931 932 if (! from_url_str || ! from_uuid_str || ! *last_merged_rev) 933 return svn_error_create 934 (APR_EINVAL, NULL, 935 _("Destination repository has not been initialized")); 936 937 /* ### TODO: Should we validate that FROM_URL_STR->data matches any 938 provided FROM_URL here? */ 939 if (! from_url) 940 from_url = from_url_str->data; 941 942 /* Open the session to copy the revision data. */ 943 SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data, 944 callbacks, baton, config, pool)); 945 946 return SVN_NO_ERROR; 947} 948 949/* Set *TARGET_SESSION_P to an RA session associated with the target 950 * repository of the synchronization. 951 */ 952static svn_error_t * 953open_target_session(svn_ra_session_t **target_session_p, 954 subcommand_baton_t *baton, 955 apr_pool_t *pool) 956{ 957 svn_ra_session_t *target_session; 958 SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL, 959 &(baton->sync_callbacks), baton, baton->config, pool)); 960 SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool)); 961 962 *target_session_p = target_session; 963 return SVN_NO_ERROR; 964} 965 966/* Replay baton, used during synchronization. */ 967typedef struct replay_baton_t { 968 svn_ra_session_t *from_session; 969 svn_ra_session_t *to_session; 970 /* Extra 'backdoor' session for fetching data *from* the target repo. */ 971 svn_ra_session_t *extra_to_session; 972 svn_revnum_t current_revision; 973 subcommand_baton_t *sb; 974 svn_boolean_t has_commit_revprops_capability; 975 int normalized_rev_props_count; 976 int normalized_node_props_count; 977 const char *to_root; 978} replay_baton_t; 979 980/* Return a replay baton allocated from POOL and populated with 981 data from the provided parameters. */ 982static svn_error_t * 983make_replay_baton(replay_baton_t **baton_p, 984 svn_ra_session_t *from_session, 985 svn_ra_session_t *to_session, 986 subcommand_baton_t *sb, apr_pool_t *pool) 987{ 988 replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb)); 989 rb->from_session = from_session; 990 rb->to_session = to_session; 991 rb->sb = sb; 992 993 SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool)); 994 995#ifdef ENABLE_EV2_SHIMS 996 /* Open up the extra baton. Only needed for Ev2 shims. */ 997 SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool)); 998#endif 999 1000 *baton_p = rb; 1001 return SVN_NO_ERROR; 1002} 1003 1004/* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync 1005 * property. Implements filter_func_t. Use with filter_props() to filter out 1006 * svn:date and svn:author and svnsync properties. 1007 */ 1008static svn_boolean_t 1009filter_exclude_date_author_sync(const char *key) 1010{ 1011 if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0) 1012 return TRUE; 1013 else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0) 1014 return TRUE; 1015 else if (strncmp(key, SVNSYNC_PROP_PREFIX, 1016 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0) 1017 return TRUE; 1018 1019 return FALSE; 1020} 1021 1022/* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync 1023 * property. Implements filter_func_t. Use with filter_props() to filter out 1024 * all properties except svn:date and svn:author and svnsync properties. 1025 */ 1026static svn_boolean_t 1027filter_include_date_author_sync(const char *key) 1028{ 1029 return ! filter_exclude_date_author_sync(key); 1030} 1031 1032 1033/* Return TRUE iff KEY is the name of the svn:log property. 1034 * Implements filter_func_t. Use with filter_props() to only exclude svn:log. 1035 */ 1036static svn_boolean_t 1037filter_exclude_log(const char *key) 1038{ 1039 if (strcmp(key, SVN_PROP_REVISION_LOG) == 0) 1040 return TRUE; 1041 else 1042 return FALSE; 1043} 1044 1045/* Return FALSE iff KEY is the name of the svn:log property. 1046 * Implements filter_func_t. Use with filter_props() to only include svn:log. 1047 */ 1048static svn_boolean_t 1049filter_include_log(const char *key) 1050{ 1051 return ! filter_exclude_log(key); 1052} 1053 1054 1055static svn_error_t * 1056fetch_base_func(const char **filename, 1057 void *baton, 1058 const char *path, 1059 svn_revnum_t base_revision, 1060 apr_pool_t *result_pool, 1061 apr_pool_t *scratch_pool) 1062{ 1063 struct replay_baton_t *rb = baton; 1064 svn_stream_t *fstream; 1065 svn_error_t *err; 1066 1067 if (svn_path_is_url(path)) 1068 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1069 else if (path[0] == '/') 1070 path += 1; 1071 1072 if (! SVN_IS_VALID_REVNUM(base_revision)) 1073 base_revision = rb->current_revision - 1; 1074 1075 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 1076 svn_io_file_del_on_pool_cleanup, 1077 result_pool, scratch_pool)); 1078 1079 err = svn_ra_get_file(rb->extra_to_session, path, base_revision, 1080 fstream, NULL, NULL, scratch_pool); 1081 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1082 { 1083 svn_error_clear(err); 1084 SVN_ERR(svn_stream_close(fstream)); 1085 1086 *filename = NULL; 1087 return SVN_NO_ERROR; 1088 } 1089 else if (err) 1090 return svn_error_trace(err); 1091 1092 SVN_ERR(svn_stream_close(fstream)); 1093 1094 return SVN_NO_ERROR; 1095} 1096 1097static svn_error_t * 1098fetch_props_func(apr_hash_t **props, 1099 void *baton, 1100 const char *path, 1101 svn_revnum_t base_revision, 1102 apr_pool_t *result_pool, 1103 apr_pool_t *scratch_pool) 1104{ 1105 struct replay_baton_t *rb = baton; 1106 svn_node_kind_t node_kind; 1107 1108 if (svn_path_is_url(path)) 1109 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1110 else if (path[0] == '/') 1111 path += 1; 1112 1113 if (! SVN_IS_VALID_REVNUM(base_revision)) 1114 base_revision = rb->current_revision - 1; 1115 1116 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision, 1117 &node_kind, scratch_pool)); 1118 1119 if (node_kind == svn_node_file) 1120 { 1121 SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision, 1122 NULL, NULL, props, result_pool)); 1123 } 1124 else if (node_kind == svn_node_dir) 1125 { 1126 apr_array_header_t *tmp_props; 1127 1128 SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path, 1129 base_revision, 0 /* Dirent fields */, 1130 result_pool)); 1131 tmp_props = svn_prop_hash_to_array(*props, result_pool); 1132 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 1133 result_pool)); 1134 *props = svn_prop_array_to_hash(tmp_props, result_pool); 1135 } 1136 else 1137 { 1138 *props = apr_hash_make(result_pool); 1139 } 1140 1141 return SVN_NO_ERROR; 1142} 1143 1144static svn_error_t * 1145fetch_kind_func(svn_node_kind_t *kind, 1146 void *baton, 1147 const char *path, 1148 svn_revnum_t base_revision, 1149 apr_pool_t *scratch_pool) 1150{ 1151 struct replay_baton_t *rb = baton; 1152 1153 if (svn_path_is_url(path)) 1154 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1155 else if (path[0] == '/') 1156 path += 1; 1157 1158 if (! SVN_IS_VALID_REVNUM(base_revision)) 1159 base_revision = rb->current_revision - 1; 1160 1161 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision, 1162 kind, scratch_pool)); 1163 1164 return SVN_NO_ERROR; 1165} 1166 1167 1168static svn_delta_shim_callbacks_t * 1169get_shim_callbacks(replay_baton_t *rb, 1170 apr_pool_t *result_pool) 1171{ 1172 svn_delta_shim_callbacks_t *callbacks = 1173 svn_delta_shim_callbacks_default(result_pool); 1174 1175 callbacks->fetch_props_func = fetch_props_func; 1176 callbacks->fetch_kind_func = fetch_kind_func; 1177 callbacks->fetch_base_func = fetch_base_func; 1178 callbacks->fetch_baton = rb; 1179 1180 return callbacks; 1181} 1182 1183 1184/* Callback function for svn_ra_replay_range, invoked when starting to parse 1185 * a replay report. 1186 */ 1187static svn_error_t * 1188replay_rev_started(svn_revnum_t revision, 1189 void *replay_baton, 1190 const svn_delta_editor_t **editor, 1191 void **edit_baton, 1192 apr_hash_t *rev_props, 1193 apr_pool_t *pool) 1194{ 1195 const svn_delta_editor_t *commit_editor; 1196 const svn_delta_editor_t *cancel_editor; 1197 const svn_delta_editor_t *sync_editor; 1198 void *commit_baton; 1199 void *cancel_baton; 1200 void *sync_baton; 1201 replay_baton_t *rb = replay_baton; 1202 apr_hash_t *filtered; 1203 int filtered_count; 1204 int normalized_count; 1205 1206 /* We set this property so that if we error out for some reason 1207 we can later determine where we were in the process of 1208 merging a revision. If we had committed the change, but we 1209 hadn't finished copying the revprops we need to know that, so 1210 we can go back and finish the job before we move on. 1211 1212 NOTE: We have to set this before we start the commit editor, 1213 because ra_svn doesn't let you change rev props during a 1214 commit. */ 1215 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0, 1216 SVNSYNC_PROP_CURRENTLY_COPYING, 1217 NULL, 1218 svn_string_createf(pool, "%ld", revision), 1219 pool)); 1220 1221 /* The actual copy is just a replay hooked up to a commit. Include 1222 all the revision properties from the source repositories, except 1223 'svn:author' and 'svn:date', those are not guaranteed to get 1224 through the editor anyway. 1225 If we're syncing to an non-commit-revprops capable server, filter 1226 out all revprops except svn:log and add them later in 1227 revplay_rev_finished. */ 1228 filtered = filter_props(&filtered_count, rev_props, 1229 (rb->has_commit_revprops_capability 1230 ? filter_exclude_date_author_sync 1231 : filter_include_log), 1232 pool); 1233 1234 /* svn_ra_get_commit_editor3 requires the log message to be 1235 set. It's possible that we didn't receive 'svn:log' here, so we 1236 have to set it to at least the empty string. If there's a svn:log 1237 property on this revision, we will write the actual value in the 1238 replay_rev_finished callback. */ 1239 if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG)) 1240 svn_hash_sets(filtered, SVN_PROP_REVISION_LOG, 1241 svn_string_create_empty(pool)); 1242 1243 /* If necessary, normalize encoding and line ending style. Add the number 1244 of properties that required EOL normalization to the overall count 1245 in the replay baton. */ 1246 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count, 1247 rb->sb->source_prop_encoding, pool)); 1248 rb->normalized_rev_props_count += normalized_count; 1249 1250 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session, 1251 get_shim_callbacks(rb, pool))); 1252 SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor, 1253 &commit_baton, 1254 filtered, 1255 commit_callback, rb->sb, 1256 NULL, FALSE, pool)); 1257 1258 /* There's one catch though, the diff shows us props we can't send 1259 over the RA interface, so we need an editor that's smart enough 1260 to filter those out for us. */ 1261 SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1, 1262 rb->sb->to_url, rb->sb->source_prop_encoding, 1263 rb->sb->quiet, &sync_editor, &sync_baton, 1264 &(rb->normalized_node_props_count), pool)); 1265 1266 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL, 1267 sync_editor, sync_baton, 1268 &cancel_editor, 1269 &cancel_baton, 1270 pool)); 1271 *editor = cancel_editor; 1272 *edit_baton = cancel_baton; 1273 1274 rb->current_revision = revision; 1275 return SVN_NO_ERROR; 1276} 1277 1278/* Callback function for svn_ra_replay_range, invoked when finishing parsing 1279 * a replay report. 1280 */ 1281static svn_error_t * 1282replay_rev_finished(svn_revnum_t revision, 1283 void *replay_baton, 1284 const svn_delta_editor_t *editor, 1285 void *edit_baton, 1286 apr_hash_t *rev_props, 1287 apr_pool_t *pool) 1288{ 1289 apr_pool_t *subpool = svn_pool_create(pool); 1290 replay_baton_t *rb = replay_baton; 1291 apr_hash_t *filtered, *existing_props; 1292 int filtered_count; 1293 int normalized_count; 1294 1295 SVN_ERR(editor->close_edit(edit_baton, pool)); 1296 1297 /* Sanity check that we actually committed the revision we meant to. */ 1298 if (rb->sb->committed_rev != revision) 1299 return svn_error_createf 1300 (APR_EINVAL, NULL, 1301 _("Commit created r%ld but should have created r%ld"), 1302 rb->sb->committed_rev, revision); 1303 1304 SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props, 1305 subpool)); 1306 1307 1308 /* Ok, we're done with the data, now we just need to copy the remaining 1309 'svn:date' and 'svn:author' revprops and we're all set. 1310 If the server doesn't support revprops-in-a-commit, we still have to 1311 set all revision properties except svn:log. */ 1312 filtered = filter_props(&filtered_count, rev_props, 1313 (rb->has_commit_revprops_capability 1314 ? filter_include_date_author_sync 1315 : filter_exclude_log), 1316 subpool); 1317 1318 /* If necessary, normalize encoding and line ending style, and add the number 1319 of EOL-normalized properties to the overall count in the replay baton. */ 1320 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count, 1321 rb->sb->source_prop_encoding, pool)); 1322 rb->normalized_rev_props_count += normalized_count; 1323 1324 SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered, 1325 subpool)); 1326 1327 /* Remove all extra properties in TARGET. */ 1328 SVN_ERR(remove_props_not_in_source(rb->to_session, revision, 1329 rev_props, existing_props, subpool)); 1330 1331 svn_pool_clear(subpool); 1332 1333 /* Ok, we're done, bring the last-merged-rev property up to date. */ 1334 SVN_ERR(svn_ra_change_rev_prop2( 1335 rb->to_session, 1336 0, 1337 SVNSYNC_PROP_LAST_MERGED_REV, 1338 NULL, 1339 svn_string_create(apr_psprintf(pool, "%ld", revision), 1340 subpool), 1341 subpool)); 1342 1343 /* And finally drop the currently copying prop, since we're done 1344 with this revision. */ 1345 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0, 1346 SVNSYNC_PROP_CURRENTLY_COPYING, 1347 NULL, NULL, subpool)); 1348 1349 /* Notify the user that we copied revision properties. */ 1350 if (! rb->sb->quiet) 1351 SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool)); 1352 1353 svn_pool_destroy(subpool); 1354 1355 return SVN_NO_ERROR; 1356} 1357 1358/* Synchronize the repository associated with RA session TO_SESSION, 1359 * using information found in BATON, while the repository is 1360 * locked. Implements `with_locked_func_t' interface. 1361 */ 1362static svn_error_t * 1363do_synchronize(svn_ra_session_t *to_session, 1364 subcommand_baton_t *baton, apr_pool_t *pool) 1365{ 1366 svn_string_t *last_merged_rev; 1367 svn_revnum_t from_latest; 1368 svn_ra_session_t *from_session; 1369 svn_string_t *currently_copying; 1370 svn_revnum_t to_latest, copying, last_merged; 1371 svn_revnum_t start_revision, end_revision; 1372 replay_baton_t *rb; 1373 int normalized_rev_props_count = 0; 1374 1375 SVN_ERR(open_source_session(&from_session, &last_merged_rev, 1376 baton->from_url, to_session, 1377 &(baton->source_callbacks), baton->config, 1378 baton, pool)); 1379 1380 /* Check to see if we have revprops that still need to be copied for 1381 a prior revision we didn't finish copying. But first, check for 1382 state sanity. Remember, mirroring is not an atomic action, 1383 because revision properties are copied separately from the 1384 revision's contents. 1385 1386 So, any time that currently-copying is not set, then 1387 last-merged-rev should be the HEAD revision of the destination 1388 repository. That is, if we didn't fall over in the middle of a 1389 previous synchronization, then our destination repository should 1390 have exactly as many revisions in it as we've synchronized. 1391 1392 Alternately, if currently-copying *is* set, it must 1393 be either last-merged-rev or last-merged-rev + 1, and the HEAD 1394 revision must be equal to either last-merged-rev or 1395 currently-copying. If this is not the case, somebody has meddled 1396 with the destination without using svnsync. 1397 */ 1398 1399 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING, 1400 ¤tly_copying, pool)); 1401 1402 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool)); 1403 1404 last_merged = SVN_STR_TO_REV(last_merged_rev->data); 1405 1406 if (currently_copying) 1407 { 1408 copying = SVN_STR_TO_REV(currently_copying->data); 1409 1410 if ((copying < last_merged) 1411 || (copying > (last_merged + 1)) 1412 || ((to_latest != last_merged) && (to_latest != copying))) 1413 { 1414 return svn_error_createf 1415 (APR_EINVAL, NULL, 1416 _("Revision being currently copied (%ld), last merged revision " 1417 "(%ld), and destination HEAD (%ld) are inconsistent; have you " 1418 "committed to the destination without using svnsync?"), 1419 copying, last_merged, to_latest); 1420 } 1421 else if (copying == to_latest) 1422 { 1423 if (copying > last_merged) 1424 { 1425 SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE, 1426 baton->quiet, baton->source_prop_encoding, 1427 &normalized_rev_props_count, pool)); 1428 last_merged = copying; 1429 last_merged_rev = svn_string_create 1430 (apr_psprintf(pool, "%ld", last_merged), pool); 1431 } 1432 1433 /* Now update last merged rev and drop currently changing. 1434 Note that the order here is significant, if we do them 1435 in the wrong order there are race conditions where we 1436 end up not being able to tell if there have been bogus 1437 (i.e. non-svnsync) commits to the dest repository. */ 1438 1439 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, 1440 SVNSYNC_PROP_LAST_MERGED_REV, 1441 NULL, last_merged_rev, pool)); 1442 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, 1443 SVNSYNC_PROP_CURRENTLY_COPYING, 1444 NULL, NULL, pool)); 1445 } 1446 /* If copying > to_latest, then we just fall through to 1447 attempting to copy the revision again. */ 1448 } 1449 else 1450 { 1451 if (to_latest != last_merged) 1452 return svn_error_createf(APR_EINVAL, NULL, 1453 _("Destination HEAD (%ld) is not the last " 1454 "merged revision (%ld); have you " 1455 "committed to the destination without " 1456 "using svnsync?"), 1457 to_latest, last_merged); 1458 } 1459 1460 /* Now check to see if there are any revisions to copy. */ 1461 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); 1462 1463 if (from_latest < last_merged) 1464 return SVN_NO_ERROR; 1465 1466 /* Ok, so there are new revisions, iterate over them copying them 1467 into the destination repository. */ 1468 SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool)); 1469 1470 /* For compatibility with older svnserve versions, check first if we 1471 support adding revprops to the commit. */ 1472 SVN_ERR(svn_ra_has_capability(rb->to_session, 1473 &rb->has_commit_revprops_capability, 1474 SVN_RA_CAPABILITY_COMMIT_REVPROPS, 1475 pool)); 1476 1477 start_revision = last_merged + 1; 1478 end_revision = from_latest; 1479 1480 SVN_ERR(check_cancel(NULL)); 1481 1482 SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision, 1483 0, TRUE, replay_rev_started, 1484 replay_rev_finished, rb, pool)); 1485 1486 SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count 1487 + normalized_rev_props_count, 1488 rb->normalized_node_props_count, 1489 pool)); 1490 1491 1492 return SVN_NO_ERROR; 1493} 1494 1495 1496/* SUBCOMMAND: sync */ 1497static svn_error_t * 1498synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 1499{ 1500 svn_ra_session_t *to_session; 1501 opt_baton_t *opt_baton = b; 1502 apr_array_header_t *targets; 1503 subcommand_baton_t *baton; 1504 const char *to_url, *from_url; 1505 1506 SVN_ERR(svn_opt__args_to_target_array(&targets, os, 1507 apr_array_make(pool, 0, 1508 sizeof(const char *)), 1509 pool)); 1510 if (targets->nelts < 1) 1511 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1512 if (targets->nelts > 2) 1513 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1514 1515 to_url = APR_ARRAY_IDX(targets, 0, const char *); 1516 if (! svn_path_is_url(to_url)) 1517 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1518 _("Path '%s' is not a URL"), to_url); 1519 1520 if (targets->nelts == 2) 1521 { 1522 from_url = APR_ARRAY_IDX(targets, 1, const char *); 1523 if (! svn_path_is_url(from_url)) 1524 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1525 _("Path '%s' is not a URL"), from_url); 1526 } 1527 else 1528 { 1529 from_url = NULL; /* we'll read it from the destination repos */ 1530 } 1531 1532 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool); 1533 SVN_ERR(open_target_session(&to_session, baton, pool)); 1534 if (opt_baton->disable_locking) 1535 SVN_ERR(do_synchronize(to_session, baton, pool)); 1536 else 1537 SVN_ERR(with_locked(to_session, do_synchronize, baton, 1538 opt_baton->steal_lock, pool)); 1539 1540 return SVN_NO_ERROR; 1541} 1542 1543 1544 1545/*** `svnsync copy-revprops' ***/ 1546 1547/* Copy revision properties to the repository associated with RA 1548 * session TO_SESSION, using information found in BATON, while the 1549 * repository is locked. Implements `with_locked_func_t' interface. 1550 */ 1551static svn_error_t * 1552do_copy_revprops(svn_ra_session_t *to_session, 1553 subcommand_baton_t *baton, apr_pool_t *pool) 1554{ 1555 svn_ra_session_t *from_session; 1556 svn_string_t *last_merged_rev; 1557 svn_revnum_t i; 1558 svn_revnum_t step = 1; 1559 int normalized_rev_props_count = 0; 1560 1561 SVN_ERR(open_source_session(&from_session, &last_merged_rev, 1562 baton->from_url, to_session, 1563 &(baton->source_callbacks), baton->config, 1564 baton, pool)); 1565 1566 /* An invalid revision means "last-synced" */ 1567 if (! SVN_IS_VALID_REVNUM(baton->start_rev)) 1568 baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data); 1569 if (! SVN_IS_VALID_REVNUM(baton->end_rev)) 1570 baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data); 1571 1572 /* Make sure we have revisions within the valid range. */ 1573 if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data)) 1574 return svn_error_createf 1575 (APR_EINVAL, NULL, 1576 _("Cannot copy revprops for a revision (%ld) that has not " 1577 "been synchronized yet"), baton->start_rev); 1578 if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data)) 1579 return svn_error_createf 1580 (APR_EINVAL, NULL, 1581 _("Cannot copy revprops for a revision (%ld) that has not " 1582 "been synchronized yet"), baton->end_rev); 1583 1584 /* Now, copy all the requested revisions, in the requested order. */ 1585 step = (baton->start_rev > baton->end_rev) ? -1 : 1; 1586 for (i = baton->start_rev; i != baton->end_rev + step; i = i + step) 1587 { 1588 int normalized_count; 1589 SVN_ERR(check_cancel(NULL)); 1590 SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet, 1591 baton->source_prop_encoding, &normalized_count, 1592 pool)); 1593 normalized_rev_props_count += normalized_count; 1594 } 1595 1596 /* Notify about normalized props, if any. */ 1597 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool)); 1598 1599 return SVN_NO_ERROR; 1600} 1601 1602 1603/* Set *START_REVNUM to the revision number associated with 1604 START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION 1605 represents "HEAD"; if END_REVISION is specified, set END_REVNUM to 1606 the revision number associated with END_REVISION or to 1607 SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set 1608 END_REVNUM to the same value as START_REVNUM. 1609 1610 As a special case, if neither START_REVISION nor END_REVISION is 1611 specified, set *START_REVNUM to 0 and set *END_REVNUM to 1612 SVN_INVALID_REVNUM. 1613 1614 Freak out if either START_REVISION or END_REVISION represents an 1615 explicit but invalid revision number. */ 1616static svn_error_t * 1617resolve_revnums(svn_revnum_t *start_revnum, 1618 svn_revnum_t *end_revnum, 1619 svn_opt_revision_t start_revision, 1620 svn_opt_revision_t end_revision) 1621{ 1622 svn_revnum_t start_rev, end_rev; 1623 1624 /* Special case: neither revision is specified? This is like 1625 -r0:HEAD. */ 1626 if ((start_revision.kind == svn_opt_revision_unspecified) && 1627 (end_revision.kind == svn_opt_revision_unspecified)) 1628 { 1629 *start_revnum = 0; 1630 *end_revnum = SVN_INVALID_REVNUM; 1631 return SVN_NO_ERROR; 1632 } 1633 1634 /* Get the start revision, which must be either HEAD or a number 1635 (which is required to be a valid one). */ 1636 if (start_revision.kind == svn_opt_revision_head) 1637 { 1638 start_rev = SVN_INVALID_REVNUM; 1639 } 1640 else 1641 { 1642 start_rev = start_revision.value.number; 1643 if (! SVN_IS_VALID_REVNUM(start_rev)) 1644 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1645 _("Invalid revision number (%ld)"), 1646 start_rev); 1647 } 1648 1649 /* Get the end revision, which must be unspecified (meaning, 1650 "same as the start_rev"), HEAD, or a number (which is 1651 required to be a valid one). */ 1652 if (end_revision.kind == svn_opt_revision_unspecified) 1653 { 1654 end_rev = start_rev; 1655 } 1656 else if (end_revision.kind == svn_opt_revision_head) 1657 { 1658 end_rev = SVN_INVALID_REVNUM; 1659 } 1660 else 1661 { 1662 end_rev = end_revision.value.number; 1663 if (! SVN_IS_VALID_REVNUM(end_rev)) 1664 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1665 _("Invalid revision number (%ld)"), 1666 end_rev); 1667 } 1668 1669 *start_revnum = start_rev; 1670 *end_revnum = end_rev; 1671 return SVN_NO_ERROR; 1672} 1673 1674 1675/* SUBCOMMAND: copy-revprops */ 1676static svn_error_t * 1677copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 1678{ 1679 svn_ra_session_t *to_session; 1680 opt_baton_t *opt_baton = b; 1681 apr_array_header_t *targets; 1682 subcommand_baton_t *baton; 1683 const char *to_url = NULL; 1684 const char *from_url = NULL; 1685 svn_opt_revision_t start_revision, end_revision; 1686 svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM; 1687 1688 /* There should be either one or two arguments left to parse. */ 1689 if (os->argc - os->ind > 2) 1690 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1691 if (os->argc - os->ind < 1) 1692 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1693 1694 /* If there are two args, the last one is either a revision range or 1695 the source URL. */ 1696 if (os->argc - os->ind == 2) 1697 { 1698 const char *arg_str = os->argv[os->argc - 1]; 1699 const char *utf_arg_str; 1700 1701 SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool)); 1702 1703 if (! svn_path_is_url(utf_arg_str)) 1704 { 1705 /* This is the old "... TO_URL REV[:REV2]" syntax. 1706 Revisions come only from this argument. (We effectively 1707 pop that last argument from the end of the argument list 1708 so svn_opt__args_to_target_array() can do its thang.) */ 1709 os->argc--; 1710 1711 if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified) 1712 || (opt_baton->end_rev.kind != svn_opt_revision_unspecified)) 1713 return svn_error_create( 1714 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1715 _("Cannot specify revisions via both command-line arguments " 1716 "and the --revision (-r) option")); 1717 1718 start_revision.kind = svn_opt_revision_unspecified; 1719 end_revision.kind = svn_opt_revision_unspecified; 1720 if (svn_opt_parse_revision(&start_revision, &end_revision, 1721 arg_str, pool) != 0) 1722 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1723 _("Invalid revision range '%s' provided"), 1724 arg_str); 1725 1726 SVN_ERR(resolve_revnums(&start_rev, &end_rev, 1727 start_revision, end_revision)); 1728 1729 SVN_ERR(svn_opt__args_to_target_array( 1730 &targets, os, 1731 apr_array_make(pool, 1, sizeof(const char *)), pool)); 1732 if (targets->nelts != 1) 1733 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1734 to_url = APR_ARRAY_IDX(targets, 0, const char *); 1735 from_url = NULL; 1736 } 1737 } 1738 1739 if (! to_url) 1740 { 1741 /* This is the "... TO_URL SOURCE_URL" syntax. Revisions 1742 come only from the --revision parameter. */ 1743 SVN_ERR(resolve_revnums(&start_rev, &end_rev, 1744 opt_baton->start_rev, opt_baton->end_rev)); 1745 1746 SVN_ERR(svn_opt__args_to_target_array( 1747 &targets, os, 1748 apr_array_make(pool, 2, sizeof(const char *)), pool)); 1749 if (targets->nelts < 1) 1750 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1751 if (targets->nelts > 2) 1752 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1753 to_url = APR_ARRAY_IDX(targets, 0, const char *); 1754 if (targets->nelts == 2) 1755 from_url = APR_ARRAY_IDX(targets, 1, const char *); 1756 else 1757 from_url = NULL; 1758 } 1759 1760 if (! svn_path_is_url(to_url)) 1761 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1762 _("Path '%s' is not a URL"), to_url); 1763 if (from_url && (! svn_path_is_url(from_url))) 1764 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1765 _("Path '%s' is not a URL"), from_url); 1766 1767 baton = make_subcommand_baton(opt_baton, to_url, from_url, 1768 start_rev, end_rev, pool); 1769 SVN_ERR(open_target_session(&to_session, baton, pool)); 1770 if (opt_baton->disable_locking) 1771 SVN_ERR(do_copy_revprops(to_session, baton, pool)); 1772 else 1773 SVN_ERR(with_locked(to_session, do_copy_revprops, baton, 1774 opt_baton->steal_lock, pool)); 1775 1776 return SVN_NO_ERROR; 1777} 1778 1779 1780 1781/*** `svnsync info' ***/ 1782 1783 1784/* SUBCOMMAND: info */ 1785static svn_error_t * 1786info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool) 1787{ 1788 svn_ra_session_t *to_session; 1789 opt_baton_t *opt_baton = b; 1790 apr_array_header_t *targets; 1791 subcommand_baton_t *baton; 1792 const char *to_url; 1793 apr_hash_t *props; 1794 svn_string_t *from_url, *from_uuid, *last_merged_rev; 1795 1796 SVN_ERR(svn_opt__args_to_target_array(&targets, os, 1797 apr_array_make(pool, 0, 1798 sizeof(const char *)), 1799 pool)); 1800 if (targets->nelts < 1) 1801 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1802 if (targets->nelts > 1) 1803 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1804 1805 /* Get the mirror repository URL, and verify that it is URL-ish. */ 1806 to_url = APR_ARRAY_IDX(targets, 0, const char *); 1807 if (! svn_path_is_url(to_url)) 1808 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1809 _("Path '%s' is not a URL"), to_url); 1810 1811 /* Open an RA session to the mirror repository URL. */ 1812 baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool); 1813 SVN_ERR(open_target_session(&to_session, baton, pool)); 1814 1815 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool)); 1816 1817 from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL); 1818 1819 if (! from_url) 1820 return svn_error_createf 1821 (SVN_ERR_BAD_URL, NULL, 1822 _("Repository '%s' is not initialized for synchronization"), to_url); 1823 1824 from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID); 1825 last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV); 1826 1827 /* Print the info. */ 1828 SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data)); 1829 if (from_uuid) 1830 SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"), 1831 from_uuid->data)); 1832 if (last_merged_rev) 1833 SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"), 1834 last_merged_rev->data)); 1835 return SVN_NO_ERROR; 1836} 1837 1838 1839 1840/*** `svnsync help' ***/ 1841 1842 1843/* SUBCOMMAND: help */ 1844static svn_error_t * 1845help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1846{ 1847 opt_baton_t *opt_baton = baton; 1848 1849 const char *header = 1850 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n" 1851 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n" 1852 "Type 'svnsync --version' to see the program version and RA modules.\n" 1853 "\n" 1854 "Available subcommands:\n"); 1855 1856 const char *ra_desc_start 1857 = _("The following repository access (RA) modules are available:\n\n"); 1858 1859 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start, 1860 pool); 1861 1862 SVN_ERR(svn_ra_print_modules(version_footer, pool)); 1863 1864 SVN_ERR(svn_opt_print_help4(os, "svnsync", 1865 opt_baton ? opt_baton->version : FALSE, 1866 opt_baton ? opt_baton->quiet : FALSE, 1867 /*###opt_state ? opt_state->verbose :*/ FALSE, 1868 version_footer->data, header, 1869 svnsync_cmd_table, svnsync_options, NULL, 1870 NULL, pool)); 1871 1872 return SVN_NO_ERROR; 1873} 1874 1875 1876 1877/*** Main ***/ 1878 1879int 1880main(int argc, const char *argv[]) 1881{ 1882 const svn_opt_subcommand_desc2_t *subcommand = NULL; 1883 apr_array_header_t *received_opts; 1884 opt_baton_t opt_baton; 1885 svn_config_t *config; 1886 apr_status_t apr_err; 1887 apr_getopt_t *os; 1888 apr_pool_t *pool; 1889 svn_error_t *err; 1890 int opt_id, i; 1891 const char *username = NULL, *source_username = NULL, *sync_username = NULL; 1892 const char *password = NULL, *source_password = NULL, *sync_password = NULL; 1893 apr_array_header_t *config_options = NULL; 1894 const char *source_prop_encoding = NULL; 1895 svn_boolean_t force_interactive = FALSE; 1896 1897 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS) 1898 { 1899 return EXIT_FAILURE; 1900 } 1901 1902 err = check_lib_versions(); 1903 if (err) 1904 return svn_cmdline_handle_exit_error(err, NULL, "svnsync: "); 1905 1906 /* Create our top-level pool. Use a separate mutexless allocator, 1907 * given this application is single threaded. 1908 */ 1909 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 1910 1911 err = svn_ra_initialize(pool); 1912 if (err) 1913 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 1914 1915 /* Initialize the option baton. */ 1916 memset(&opt_baton, 0, sizeof(opt_baton)); 1917 opt_baton.start_rev.kind = svn_opt_revision_unspecified; 1918 opt_baton.end_rev.kind = svn_opt_revision_unspecified; 1919 1920 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 1921 1922 if (argc <= 1) 1923 { 1924 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 1925 svn_pool_destroy(pool); 1926 return EXIT_FAILURE; 1927 } 1928 1929 err = svn_cmdline__getopt_init(&os, argc, argv, pool); 1930 if (err) 1931 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 1932 1933 os->interleave = 1; 1934 1935 for (;;) 1936 { 1937 const char *opt_arg; 1938 svn_error_t* opt_err = NULL; 1939 1940 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg); 1941 if (APR_STATUS_IS_EOF(apr_err)) 1942 break; 1943 else if (apr_err) 1944 { 1945 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 1946 svn_pool_destroy(pool); 1947 return EXIT_FAILURE; 1948 } 1949 1950 APR_ARRAY_PUSH(received_opts, int) = opt_id; 1951 1952 switch (opt_id) 1953 { 1954 case svnsync_opt_non_interactive: 1955 opt_baton.non_interactive = TRUE; 1956 break; 1957 1958 case svnsync_opt_force_interactive: 1959 force_interactive = TRUE; 1960 break; 1961 1962 case svnsync_opt_trust_server_cert: 1963 opt_baton.trust_server_cert = TRUE; 1964 break; 1965 1966 case svnsync_opt_no_auth_cache: 1967 opt_baton.no_auth_cache = TRUE; 1968 break; 1969 1970 case svnsync_opt_auth_username: 1971 opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool); 1972 break; 1973 1974 case svnsync_opt_auth_password: 1975 opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool); 1976 break; 1977 1978 case svnsync_opt_source_username: 1979 opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool); 1980 break; 1981 1982 case svnsync_opt_source_password: 1983 opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool); 1984 break; 1985 1986 case svnsync_opt_sync_username: 1987 opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool); 1988 break; 1989 1990 case svnsync_opt_sync_password: 1991 opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool); 1992 break; 1993 1994 case svnsync_opt_config_dir: 1995 { 1996 const char *path_utf8; 1997 opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool); 1998 1999 if (!opt_err) 2000 opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool); 2001 } 2002 break; 2003 case svnsync_opt_config_options: 2004 if (!config_options) 2005 config_options = 2006 apr_array_make(pool, 1, 2007 sizeof(svn_cmdline__config_argument_t*)); 2008 2009 err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool); 2010 if (!err) 2011 err = svn_cmdline__parse_config_option(config_options, 2012 opt_arg, pool); 2013 if (err) 2014 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2015 break; 2016 2017 case svnsync_opt_source_prop_encoding: 2018 opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg, 2019 pool); 2020 break; 2021 2022 case svnsync_opt_disable_locking: 2023 opt_baton.disable_locking = TRUE; 2024 break; 2025 2026 case svnsync_opt_steal_lock: 2027 opt_baton.steal_lock = TRUE; 2028 break; 2029 2030 case svnsync_opt_version: 2031 opt_baton.version = TRUE; 2032 break; 2033 2034 case svnsync_opt_allow_non_empty: 2035 opt_baton.allow_non_empty = TRUE; 2036 break; 2037 2038 case 'q': 2039 opt_baton.quiet = TRUE; 2040 break; 2041 2042 case 'r': 2043 if (svn_opt_parse_revision(&opt_baton.start_rev, 2044 &opt_baton.end_rev, 2045 opt_arg, pool) != 0) 2046 { 2047 const char *utf8_opt_arg; 2048 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); 2049 if (! err) 2050 err = svn_error_createf( 2051 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2052 _("Syntax error in revision argument '%s'"), 2053 utf8_opt_arg); 2054 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2055 } 2056 2057 /* We only allow numbers and 'HEAD'. */ 2058 if (((opt_baton.start_rev.kind != svn_opt_revision_number) && 2059 (opt_baton.start_rev.kind != svn_opt_revision_head)) 2060 || ((opt_baton.end_rev.kind != svn_opt_revision_number) && 2061 (opt_baton.end_rev.kind != svn_opt_revision_head) && 2062 (opt_baton.end_rev.kind != svn_opt_revision_unspecified))) 2063 { 2064 err = svn_error_createf( 2065 SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2066 _("Invalid revision range '%s' provided"), opt_arg); 2067 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2068 } 2069 break; 2070 2071 case '?': 2072 case 'h': 2073 opt_baton.help = TRUE; 2074 break; 2075 2076 default: 2077 { 2078 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 2079 svn_pool_destroy(pool); 2080 return EXIT_FAILURE; 2081 } 2082 } 2083 2084 if(opt_err) 2085 return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: "); 2086 } 2087 2088 if (opt_baton.help) 2089 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help"); 2090 2091 /* The --non-interactive and --force-interactive options are mutually 2092 * exclusive. */ 2093 if (opt_baton.non_interactive && force_interactive) 2094 { 2095 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2096 _("--non-interactive and --force-interactive " 2097 "are mutually exclusive")); 2098 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2099 } 2100 else 2101 opt_baton.non_interactive = !svn_cmdline__be_interactive( 2102 opt_baton.non_interactive, 2103 force_interactive); 2104 2105 /* Disallow the mixing --username/password with their --source- and 2106 --sync- variants. Treat "--username FOO" as "--source-username 2107 FOO --sync-username FOO"; ditto for "--password FOO". */ 2108 if ((username || password) 2109 && (source_username || sync_username 2110 || source_password || sync_password)) 2111 { 2112 err = svn_error_create 2113 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2114 _("Cannot use --username or --password with any of " 2115 "--source-username, --source-password, --sync-username, " 2116 "or --sync-password.\n")); 2117 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2118 } 2119 if (username) 2120 { 2121 source_username = username; 2122 sync_username = username; 2123 } 2124 if (password) 2125 { 2126 source_password = password; 2127 sync_password = password; 2128 } 2129 opt_baton.source_username = source_username; 2130 opt_baton.source_password = source_password; 2131 opt_baton.sync_username = sync_username; 2132 opt_baton.sync_password = sync_password; 2133 2134 /* Disallow mixing of --steal-lock and --disable-locking. */ 2135 if (opt_baton.steal_lock && opt_baton.disable_locking) 2136 { 2137 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2138 _("--disable-locking and --steal-lock are " 2139 "mutually exclusive")); 2140 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2141 } 2142 2143 /* --trust-server-cert can only be used with --non-interactive */ 2144 if (opt_baton.trust_server_cert && !opt_baton.non_interactive) 2145 { 2146 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2147 _("--trust-server-cert requires " 2148 "--non-interactive")); 2149 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2150 } 2151 2152 err = svn_config_ensure(opt_baton.config_dir, pool); 2153 if (err) 2154 return svn_cmdline_handle_exit_error(err, pool, "synsync: "); 2155 2156 if (subcommand == NULL) 2157 { 2158 if (os->ind >= os->argc) 2159 { 2160 if (opt_baton.version) 2161 { 2162 /* Use the "help" subcommand to handle "--version". */ 2163 static const svn_opt_subcommand_desc2_t pseudo_cmd = 2164 { "--version", help_cmd, {0}, "", 2165 {svnsync_opt_version, /* must accept its own option */ 2166 'q', /* --quiet */ 2167 } }; 2168 2169 subcommand = &pseudo_cmd; 2170 } 2171 else 2172 { 2173 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 2174 svn_pool_destroy(pool); 2175 return EXIT_FAILURE; 2176 } 2177 } 2178 else 2179 { 2180 const char *first_arg = os->argv[os->ind++]; 2181 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, 2182 first_arg); 2183 if (subcommand == NULL) 2184 { 2185 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 2186 svn_pool_destroy(pool); 2187 return EXIT_FAILURE; 2188 } 2189 } 2190 } 2191 2192 for (i = 0; i < received_opts->nelts; ++i) 2193 { 2194 opt_id = APR_ARRAY_IDX(received_opts, i, int); 2195 2196 if (opt_id == 'h' || opt_id == '?') 2197 continue; 2198 2199 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 2200 { 2201 const char *optstr; 2202 const apr_getopt_option_t *badopt = 2203 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand, 2204 pool); 2205 svn_opt_format_option(&optstr, badopt, FALSE, pool); 2206 if (subcommand->name[0] == '-') 2207 { 2208 SVN_INT_ERR(help_cmd(NULL, NULL, pool)); 2209 } 2210 else 2211 { 2212 err = svn_error_createf 2213 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2214 _("Subcommand '%s' doesn't accept option '%s'\n" 2215 "Type 'svnsync help %s' for usage.\n"), 2216 subcommand->name, optstr, subcommand->name); 2217 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2218 } 2219 } 2220 } 2221 2222 err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool); 2223 if (err) 2224 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2225 2226 /* Update the options in the config */ 2227 if (config_options) 2228 { 2229 svn_error_clear( 2230 svn_cmdline__apply_config_options(opt_baton.config, config_options, 2231 "svnsync: ", "--config-option")); 2232 } 2233 2234 config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG); 2235 2236 opt_baton.source_prop_encoding = source_prop_encoding; 2237 2238 apr_signal(SIGINT, signal_handler); 2239 2240#ifdef SIGBREAK 2241 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 2242 apr_signal(SIGBREAK, signal_handler); 2243#endif 2244 2245#ifdef SIGHUP 2246 apr_signal(SIGHUP, signal_handler); 2247#endif 2248 2249#ifdef SIGTERM 2250 apr_signal(SIGTERM, signal_handler); 2251#endif 2252 2253#ifdef SIGPIPE 2254 /* Disable SIGPIPE generation for the platforms that have it. */ 2255 apr_signal(SIGPIPE, SIG_IGN); 2256#endif 2257 2258#ifdef SIGXFSZ 2259 /* Disable SIGXFSZ generation for the platforms that have it, 2260 otherwise working with large files when compiled against an APR 2261 that doesn't have large file support will crash the program, 2262 which is uncool. */ 2263 apr_signal(SIGXFSZ, SIG_IGN); 2264#endif 2265 2266 err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton, 2267 opt_baton.non_interactive, 2268 opt_baton.source_username, 2269 opt_baton.source_password, 2270 opt_baton.config_dir, 2271 opt_baton.no_auth_cache, 2272 opt_baton.trust_server_cert, 2273 config, 2274 check_cancel, NULL, 2275 pool); 2276 if (! err) 2277 err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton, 2278 opt_baton.non_interactive, 2279 opt_baton.sync_username, 2280 opt_baton.sync_password, 2281 opt_baton.config_dir, 2282 opt_baton.no_auth_cache, 2283 opt_baton.trust_server_cert, 2284 config, 2285 check_cancel, NULL, 2286 pool); 2287 if (! err) 2288 err = (*subcommand->cmd_func)(os, &opt_baton, pool); 2289 if (err) 2290 { 2291 /* For argument-related problems, suggest using the 'help' 2292 subcommand. */ 2293 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2294 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2295 { 2296 err = svn_error_quick_wrap(err, 2297 _("Try 'svnsync help' for more info")); 2298 } 2299 2300 return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); 2301 } 2302 2303 svn_pool_destroy(pool); 2304 2305 return EXIT_SUCCESS; 2306} 2307