1251881Speter/* 2251881Speter * ==================================================================== 3251881Speter * Licensed to the Apache Software Foundation (ASF) under one 4251881Speter * or more contributor license agreements. See the NOTICE file 5251881Speter * distributed with this work for additional information 6251881Speter * regarding copyright ownership. The ASF licenses this file 7251881Speter * to you under the Apache License, Version 2.0 (the 8251881Speter * "License"); you may not use this file except in compliance 9251881Speter * with the License. You may obtain a copy of the License at 10251881Speter * 11251881Speter * http://www.apache.org/licenses/LICENSE-2.0 12251881Speter * 13251881Speter * Unless required by applicable law or agreed to in writing, 14251881Speter * software distributed under the License is distributed on an 15251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16251881Speter * KIND, either express or implied. See the License for the 17251881Speter * specific language governing permissions and limitations 18251881Speter * under the License. 19251881Speter * ==================================================================== 20251881Speter */ 21251881Speter 22251881Speter#include "svn_hash.h" 23251881Speter#include "svn_cmdline.h" 24251881Speter#include "svn_config.h" 25251881Speter#include "svn_pools.h" 26251881Speter#include "svn_delta.h" 27251881Speter#include "svn_dirent_uri.h" 28251881Speter#include "svn_path.h" 29251881Speter#include "svn_props.h" 30251881Speter#include "svn_auth.h" 31251881Speter#include "svn_opt.h" 32251881Speter#include "svn_ra.h" 33251881Speter#include "svn_utf.h" 34251881Speter#include "svn_subst.h" 35251881Speter#include "svn_string.h" 36251881Speter#include "svn_version.h" 37251881Speter 38251881Speter#include "private/svn_opt_private.h" 39251881Speter#include "private/svn_ra_private.h" 40251881Speter#include "private/svn_cmdline_private.h" 41251881Speter 42251881Speter#include "sync.h" 43251881Speter 44251881Speter#include "svn_private_config.h" 45251881Speter 46251881Speter#include <apr_signal.h> 47251881Speter#include <apr_uuid.h> 48251881Speter 49251881Speterstatic svn_opt_subcommand_t initialize_cmd, 50251881Speter synchronize_cmd, 51251881Speter copy_revprops_cmd, 52251881Speter info_cmd, 53251881Speter help_cmd; 54251881Speter 55251881Speterenum svnsync__opt { 56251881Speter svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID, 57251881Speter svnsync_opt_force_interactive, 58251881Speter svnsync_opt_no_auth_cache, 59251881Speter svnsync_opt_auth_username, 60251881Speter svnsync_opt_auth_password, 61251881Speter svnsync_opt_source_username, 62251881Speter svnsync_opt_source_password, 63251881Speter svnsync_opt_sync_username, 64251881Speter svnsync_opt_sync_password, 65251881Speter svnsync_opt_config_dir, 66251881Speter svnsync_opt_config_options, 67251881Speter svnsync_opt_source_prop_encoding, 68251881Speter svnsync_opt_disable_locking, 69251881Speter svnsync_opt_version, 70251881Speter svnsync_opt_trust_server_cert, 71299742Sdim svnsync_opt_trust_server_cert_failures_src, 72299742Sdim svnsync_opt_trust_server_cert_failures_dst, 73251881Speter svnsync_opt_allow_non_empty, 74251881Speter svnsync_opt_steal_lock 75251881Speter}; 76251881Speter 77251881Speter#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \ 78251881Speter svnsync_opt_force_interactive, \ 79251881Speter svnsync_opt_no_auth_cache, \ 80251881Speter svnsync_opt_auth_username, \ 81251881Speter svnsync_opt_auth_password, \ 82251881Speter svnsync_opt_trust_server_cert, \ 83299742Sdim svnsync_opt_trust_server_cert_failures_src, \ 84299742Sdim svnsync_opt_trust_server_cert_failures_dst, \ 85251881Speter svnsync_opt_source_username, \ 86251881Speter svnsync_opt_source_password, \ 87251881Speter svnsync_opt_sync_username, \ 88251881Speter svnsync_opt_sync_password, \ 89251881Speter svnsync_opt_config_dir, \ 90251881Speter svnsync_opt_config_options 91251881Speter 92251881Speterstatic const svn_opt_subcommand_desc2_t svnsync_cmd_table[] = 93251881Speter { 94251881Speter { "initialize", initialize_cmd, { "init" }, 95251881Speter N_("usage: svnsync initialize DEST_URL SOURCE_URL\n" 96251881Speter "\n" 97251881Speter "Initialize a destination repository for synchronization from\n" 98251881Speter "another repository.\n" 99251881Speter "\n" 100251881Speter "If the source URL is not the root of a repository, only the\n" 101251881Speter "specified part of the repository will be synchronized.\n" 102251881Speter "\n" 103251881Speter "The destination URL must point to the root of a repository which\n" 104251881Speter "has been configured to allow revision property changes. In\n" 105251881Speter "the general case, the destination repository must contain no\n" 106251881Speter "committed revisions. Use --allow-non-empty to override this\n" 107251881Speter "restriction, which will cause svnsync to assume that any revisions\n" 108251881Speter "already present in the destination repository perfectly mirror\n" 109251881Speter "their counterparts in the source repository. (This is useful\n" 110251881Speter "when initializing a copy of a repository as a mirror of that same\n" 111251881Speter "repository, for example.)\n" 112251881Speter "\n" 113251881Speter "You should not commit to, or make revision property changes in,\n" 114251881Speter "the destination repository by any method other than 'svnsync'.\n" 115251881Speter "In other words, the destination repository should be a read-only\n" 116251881Speter "mirror of the source repository.\n"), 117251881Speter { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 118251881Speter svnsync_opt_allow_non_empty, svnsync_opt_disable_locking, 119299742Sdim svnsync_opt_steal_lock, 'M' } }, 120251881Speter { "synchronize", synchronize_cmd, { "sync" }, 121251881Speter N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n" 122251881Speter "\n" 123251881Speter "Transfer all pending revisions to the destination from the source\n" 124251881Speter "with which it was initialized.\n" 125251881Speter "\n" 126251881Speter "If SOURCE_URL is provided, use that as the source repository URL,\n" 127251881Speter "ignoring what is recorded in the destination repository as the\n" 128251881Speter "source URL. Specifying SOURCE_URL is recommended in particular\n" 129251881Speter "if untrusted users/administrators may have write access to the\n" 130251881Speter "DEST_URL repository.\n"), 131251881Speter { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 132299742Sdim svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } }, 133251881Speter { "copy-revprops", copy_revprops_cmd, { 0 }, 134251881Speter N_("usage:\n" 135251881Speter "\n" 136251881Speter " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n" 137251881Speter " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n" 138251881Speter "\n" 139251881Speter "Copy the revision properties in a given range of revisions to the\n" 140251881Speter "destination from the source with which it was initialized. If the\n" 141251881Speter "revision range is not specified, it defaults to all revisions in\n" 142251881Speter "the DEST_URL repository. Note also that the 'HEAD' revision is the\n" 143251881Speter "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n" 144251881Speter "\n" 145251881Speter "If SOURCE_URL is provided, use that as the source repository URL,\n" 146251881Speter "ignoring what is recorded in the destination repository as the\n" 147251881Speter "source URL. Specifying SOURCE_URL is recommended in particular\n" 148251881Speter "if untrusted users/administrators may have write access to the\n" 149251881Speter "DEST_URL repository.\n" 150251881Speter "\n" 151251881Speter "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"), 152251881Speter { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r', 153299742Sdim svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } }, 154251881Speter { "info", info_cmd, { 0 }, 155251881Speter N_("usage: svnsync info DEST_URL\n" 156251881Speter "\n" 157251881Speter "Print information about the synchronization destination repository\n" 158251881Speter "located at DEST_URL.\n"), 159251881Speter { SVNSYNC_OPTS_DEFAULT } }, 160251881Speter { "help", help_cmd, { "?", "h" }, 161251881Speter N_("usage: svnsync help [SUBCOMMAND...]\n" 162251881Speter "\n" 163251881Speter "Describe the usage of this program or its subcommands.\n"), 164251881Speter { 0 } }, 165251881Speter { NULL, NULL, { 0 }, NULL, { 0 } } 166251881Speter }; 167251881Speter 168251881Speterstatic const apr_getopt_option_t svnsync_options[] = 169251881Speter { 170251881Speter {"quiet", 'q', 0, 171251881Speter N_("print as little as possible") }, 172251881Speter {"revision", 'r', 1, 173251881Speter N_("operate on revision ARG (or range ARG1:ARG2)\n" 174251881Speter " " 175251881Speter "A revision argument can be one of:\n" 176251881Speter " " 177251881Speter " NUMBER revision number\n" 178251881Speter " " 179251881Speter " 'HEAD' latest in repository") }, 180251881Speter {"allow-non-empty", svnsync_opt_allow_non_empty, 0, 181251881Speter N_("allow a non-empty destination repository") }, 182251881Speter {"non-interactive", svnsync_opt_non_interactive, 0, 183251881Speter N_("do no interactive prompting (default is to prompt\n" 184251881Speter " " 185251881Speter "only if standard input is a terminal device)")}, 186251881Speter {"force-interactive", svnsync_opt_force_interactive, 0, 187251881Speter N_("do interactive prompting even if standard input\n" 188251881Speter " " 189251881Speter "is not a terminal device")}, 190251881Speter {"no-auth-cache", svnsync_opt_no_auth_cache, 0, 191251881Speter N_("do not cache authentication tokens") }, 192251881Speter {"username", svnsync_opt_auth_username, 1, 193251881Speter N_("specify a username ARG (deprecated;\n" 194251881Speter " " 195251881Speter "see --source-username and --sync-username)") }, 196251881Speter {"password", svnsync_opt_auth_password, 1, 197251881Speter N_("specify a password ARG (deprecated;\n" 198251881Speter " " 199251881Speter "see --source-password and --sync-password)") }, 200251881Speter {"trust-server-cert", svnsync_opt_trust_server_cert, 0, 201299742Sdim N_("deprecated; same as\n" 202299742Sdim " " 203299742Sdim "--source-trust-server-cert-failures=unknown-ca\n" 204299742Sdim " " 205299742Sdim "--sync-trust-server-cert-failures=unknown-ca")}, 206299742Sdim {"source-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_src, 1, 207299742Sdim N_("with --non-interactive, accept SSL\n" 208299742Sdim " " 209299742Sdim "server certificates with failures.\n" 210299742Sdim " " 211299742Sdim "ARG is a comma-separated list of:\n" 212299742Sdim " " 213299742Sdim "- 'unknown-ca' (Unknown Authority)\n" 214299742Sdim " " 215299742Sdim "- 'cn-mismatch' (Hostname mismatch)\n" 216299742Sdim " " 217299742Sdim "- 'expired' (Expired certificate)\n" 218299742Sdim " " 219299742Sdim "- 'not-yet-valid' (Not yet valid certificate)\n" 220299742Sdim " " 221299742Sdim "- 'other' (all other not separately classified\n" 222299742Sdim " " 223299742Sdim " certificate errors).\n" 224299742Sdim " " 225299742Sdim "Applied to the source URL.")}, 226299742Sdim {"sync-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_dst, 1, 227299742Sdim N_("Like\n" 228251881Speter " " 229299742Sdim "--source-trust-server-cert-failures,\n" 230251881Speter " " 231299742Sdim "but applied to the destination URL.")}, 232251881Speter {"source-username", svnsync_opt_source_username, 1, 233251881Speter N_("connect to source repository with username ARG") }, 234251881Speter {"source-password", svnsync_opt_source_password, 1, 235251881Speter N_("connect to source repository with password ARG") }, 236251881Speter {"sync-username", svnsync_opt_sync_username, 1, 237251881Speter N_("connect to sync repository with username ARG") }, 238251881Speter {"sync-password", svnsync_opt_sync_password, 1, 239251881Speter N_("connect to sync repository with password ARG") }, 240251881Speter {"config-dir", svnsync_opt_config_dir, 1, 241251881Speter N_("read user configuration files from directory ARG")}, 242251881Speter {"config-option", svnsync_opt_config_options, 1, 243251881Speter N_("set user configuration option in the format:\n" 244251881Speter " " 245251881Speter " FILE:SECTION:OPTION=[VALUE]\n" 246251881Speter " " 247251881Speter "For example:\n" 248251881Speter " " 249251881Speter " servers:global:http-library=serf")}, 250251881Speter {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1, 251251881Speter N_("convert translatable properties from encoding ARG\n" 252251881Speter " " 253251881Speter "to UTF-8. If not specified, then properties are\n" 254251881Speter " " 255251881Speter "presumed to be encoded in UTF-8.")}, 256251881Speter {"disable-locking", svnsync_opt_disable_locking, 0, 257251881Speter N_("Disable built-in locking. Use of this option can\n" 258251881Speter " " 259251881Speter "corrupt the mirror unless you ensure that no other\n" 260251881Speter " " 261251881Speter "instance of svnsync is running concurrently.")}, 262251881Speter {"steal-lock", svnsync_opt_steal_lock, 0, 263251881Speter N_("Steal locks as necessary. Use, with caution,\n" 264251881Speter " " 265251881Speter "if your mirror repository contains stale locks\n" 266251881Speter " " 267251881Speter "and is not being concurrently accessed by another\n" 268251881Speter " " 269251881Speter "svnsync instance.")}, 270299742Sdim {"memory-cache-size", 'M', 1, 271299742Sdim N_("size of the extra in-memory cache in MB used to\n" 272299742Sdim " " 273299742Sdim "minimize operations for local 'file' scheme.\n")}, 274251881Speter {"version", svnsync_opt_version, 0, 275251881Speter N_("show program version information")}, 276251881Speter {"help", 'h', 0, 277251881Speter N_("show help on a subcommand")}, 278251881Speter {NULL, '?', 0, 279251881Speter N_("show help on a subcommand")}, 280251881Speter { 0, 0, 0, 0 } 281251881Speter }; 282251881Speter 283251881Spetertypedef struct opt_baton_t { 284251881Speter svn_boolean_t non_interactive; 285299742Sdim struct { 286299742Sdim svn_boolean_t trust_server_cert_unknown_ca; 287299742Sdim svn_boolean_t trust_server_cert_cn_mismatch; 288299742Sdim svn_boolean_t trust_server_cert_expired; 289299742Sdim svn_boolean_t trust_server_cert_not_yet_valid; 290299742Sdim svn_boolean_t trust_server_cert_other_failure; 291299742Sdim } src_trust, dst_trust; 292251881Speter svn_boolean_t no_auth_cache; 293251881Speter svn_auth_baton_t *source_auth_baton; 294251881Speter svn_auth_baton_t *sync_auth_baton; 295251881Speter const char *source_username; 296251881Speter const char *source_password; 297251881Speter const char *sync_username; 298251881Speter const char *sync_password; 299251881Speter const char *config_dir; 300251881Speter apr_hash_t *config; 301251881Speter const char *source_prop_encoding; 302251881Speter svn_boolean_t disable_locking; 303251881Speter svn_boolean_t steal_lock; 304251881Speter svn_boolean_t quiet; 305251881Speter svn_boolean_t allow_non_empty; 306251881Speter svn_boolean_t version; 307251881Speter svn_boolean_t help; 308251881Speter svn_opt_revision_t start_rev; 309251881Speter svn_opt_revision_t end_rev; 310251881Speter} opt_baton_t; 311251881Speter 312251881Speter 313251881Speter 314251881Speter 315251881Speter/*** Helper functions ***/ 316251881Speter 317251881Speter 318251881Speter/* Global record of whether the user has requested cancellation. */ 319251881Speterstatic volatile sig_atomic_t cancelled = FALSE; 320251881Speter 321251881Speter 322251881Speter/* Callback function for apr_signal(). */ 323251881Speterstatic void 324251881Spetersignal_handler(int signum) 325251881Speter{ 326251881Speter apr_signal(signum, SIG_IGN); 327251881Speter cancelled = TRUE; 328251881Speter} 329251881Speter 330251881Speter 331251881Speter/* Cancellation callback function. */ 332251881Speterstatic svn_error_t * 333251881Spetercheck_cancel(void *baton) 334251881Speter{ 335251881Speter if (cancelled) 336251881Speter return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 337251881Speter else 338251881Speter return SVN_NO_ERROR; 339251881Speter} 340251881Speter 341251881Speter 342251881Speter/* Check that the version of libraries in use match what we expect. */ 343251881Speterstatic svn_error_t * 344251881Spetercheck_lib_versions(void) 345251881Speter{ 346251881Speter static const svn_version_checklist_t checklist[] = 347251881Speter { 348251881Speter { "svn_subr", svn_subr_version }, 349251881Speter { "svn_delta", svn_delta_version }, 350251881Speter { "svn_ra", svn_ra_version }, 351251881Speter { NULL, NULL } 352251881Speter }; 353251881Speter SVN_VERSION_DEFINE(my_version); 354251881Speter 355262253Speter return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 356251881Speter} 357251881Speter 358251881Speter 359251881Speter/* Implements `svn_ra__lock_retry_func_t'. */ 360251881Speterstatic svn_error_t * 361251881Speterlock_retry_func(void *baton, 362251881Speter const svn_string_t *reposlocktoken, 363251881Speter apr_pool_t *pool) 364251881Speter{ 365251881Speter return svn_cmdline_printf(pool, 366251881Speter _("Failed to get lock on destination " 367251881Speter "repos, currently held by '%s'\n"), 368251881Speter reposlocktoken->data); 369251881Speter} 370251881Speter 371251881Speter/* Acquire a lock (of sorts) on the repository associated with the 372251881Speter * given RA SESSION. This lock is just a revprop change attempt in a 373251881Speter * time-delay loop. This function is duplicated by svnrdump in 374251881Speter * svnrdump/load_editor.c 375251881Speter */ 376251881Speterstatic svn_error_t * 377251881Speterget_lock(const svn_string_t **lock_string_p, 378251881Speter svn_ra_session_t *session, 379251881Speter svn_boolean_t steal_lock, 380251881Speter apr_pool_t *pool) 381251881Speter{ 382251881Speter svn_error_t *err; 383251881Speter svn_boolean_t be_atomic; 384251881Speter const svn_string_t *stolen_lock; 385251881Speter 386251881Speter SVN_ERR(svn_ra_has_capability(session, &be_atomic, 387251881Speter SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 388251881Speter pool)); 389251881Speter if (! be_atomic) 390251881Speter { 391251881Speter /* Pre-1.7 server. Can't lock without a race condition. 392251881Speter See issue #3546. 393251881Speter */ 394251881Speter err = svn_error_create( 395251881Speter SVN_ERR_UNSUPPORTED_FEATURE, NULL, 396251881Speter _("Target server does not support atomic revision property " 397251881Speter "edits; consider upgrading it to 1.7 or using an external " 398251881Speter "locking program")); 399251881Speter svn_handle_warning2(stderr, err, "svnsync: "); 400251881Speter svn_error_clear(err); 401251881Speter } 402251881Speter 403251881Speter err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session, 404251881Speter SVNSYNC_PROP_LOCK, steal_lock, 405251881Speter 10 /* retries */, lock_retry_func, NULL, 406251881Speter check_cancel, NULL, pool); 407251881Speter if (!err && stolen_lock) 408251881Speter { 409251881Speter return svn_cmdline_printf(pool, 410251881Speter _("Stole lock previously held by '%s'\n"), 411251881Speter stolen_lock->data); 412251881Speter } 413251881Speter return err; 414251881Speter} 415251881Speter 416251881Speter 417251881Speter/* Baton for the various subcommands to share. */ 418251881Spetertypedef struct subcommand_baton_t { 419251881Speter /* common to all subcommands */ 420251881Speter apr_hash_t *config; 421251881Speter svn_ra_callbacks2_t source_callbacks; 422251881Speter svn_ra_callbacks2_t sync_callbacks; 423251881Speter svn_boolean_t quiet; 424251881Speter svn_boolean_t allow_non_empty; 425251881Speter const char *to_url; 426251881Speter 427251881Speter /* initialize, synchronize, and copy-revprops only */ 428251881Speter const char *source_prop_encoding; 429251881Speter 430251881Speter /* initialize only */ 431251881Speter const char *from_url; 432251881Speter 433251881Speter /* synchronize only */ 434251881Speter svn_revnum_t committed_rev; 435251881Speter 436251881Speter /* copy-revprops only */ 437251881Speter svn_revnum_t start_rev; 438251881Speter svn_revnum_t end_rev; 439251881Speter 440251881Speter} subcommand_baton_t; 441251881Speter 442251881Spetertypedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session, 443251881Speter subcommand_baton_t *baton, 444251881Speter apr_pool_t *pool); 445251881Speter 446251881Speter 447251881Speter/* Lock the repository associated with RA SESSION, then execute the 448251881Speter * given FUNC/BATON pair while holding the lock. Finally, drop the 449251881Speter * lock once it finishes. 450251881Speter */ 451251881Speterstatic svn_error_t * 452251881Speterwith_locked(svn_ra_session_t *session, 453251881Speter with_locked_func_t func, 454251881Speter subcommand_baton_t *baton, 455251881Speter svn_boolean_t steal_lock, 456251881Speter apr_pool_t *pool) 457251881Speter{ 458251881Speter const svn_string_t *lock_string; 459251881Speter svn_error_t *err; 460251881Speter 461251881Speter SVN_ERR(get_lock(&lock_string, session, steal_lock, pool)); 462251881Speter 463251881Speter err = func(session, baton, pool); 464251881Speter return svn_error_compose_create(err, 465251881Speter svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK, 466251881Speter lock_string, pool)); 467251881Speter} 468251881Speter 469251881Speter 470251881Speter/* Callback function for the RA session's open_tmp_file() 471251881Speter * requirements. 472251881Speter */ 473251881Speterstatic svn_error_t * 474251881Speteropen_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool) 475251881Speter{ 476251881Speter return svn_io_open_unique_file3(fp, NULL, NULL, 477251881Speter svn_io_file_del_on_pool_cleanup, 478251881Speter pool, pool); 479251881Speter} 480251881Speter 481251881Speter 482251881Speter/* Return SVN_NO_ERROR iff URL identifies the root directory of the 483251881Speter * repository associated with RA session SESS. 484251881Speter */ 485251881Speterstatic svn_error_t * 486251881Spetercheck_if_session_is_at_repos_root(svn_ra_session_t *sess, 487251881Speter const char *url, 488251881Speter apr_pool_t *pool) 489251881Speter{ 490251881Speter const char *sess_root; 491251881Speter 492251881Speter SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool)); 493251881Speter 494251881Speter if (strcmp(url, sess_root) == 0) 495251881Speter return SVN_NO_ERROR; 496251881Speter else 497251881Speter return svn_error_createf 498251881Speter (APR_EINVAL, NULL, 499251881Speter _("Session is rooted at '%s' but the repos root is '%s'"), 500251881Speter url, sess_root); 501251881Speter} 502251881Speter 503251881Speter 504251881Speter/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from 505251881Speter * revision REV of the repository associated with RA session SESSION. 506251881Speter * 507251881Speter * For REV zero, don't remove properties with the "svn:sync-" prefix. 508251881Speter * 509251881Speter * All allocations will be done in a subpool of POOL. 510251881Speter */ 511251881Speterstatic svn_error_t * 512251881Speterremove_props_not_in_source(svn_ra_session_t *session, 513251881Speter svn_revnum_t rev, 514251881Speter apr_hash_t *source_props, 515251881Speter apr_hash_t *target_props, 516251881Speter apr_pool_t *pool) 517251881Speter{ 518251881Speter apr_pool_t *subpool = svn_pool_create(pool); 519251881Speter apr_hash_index_t *hi; 520251881Speter 521251881Speter for (hi = apr_hash_first(pool, target_props); 522251881Speter hi; 523251881Speter hi = apr_hash_next(hi)) 524251881Speter { 525299742Sdim const char *propname = apr_hash_this_key(hi); 526251881Speter 527251881Speter svn_pool_clear(subpool); 528251881Speter 529251881Speter if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX, 530251881Speter sizeof(SVNSYNC_PROP_PREFIX) - 1)) 531251881Speter continue; 532251881Speter 533251881Speter /* Delete property if the name can't be found in SOURCE_PROPS. */ 534251881Speter if (! svn_hash_gets(source_props, propname)) 535251881Speter SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL, 536251881Speter NULL, subpool)); 537251881Speter } 538251881Speter 539251881Speter svn_pool_destroy(subpool); 540251881Speter 541251881Speter return SVN_NO_ERROR; 542251881Speter} 543251881Speter 544251881Speter/* Filter callback function. 545251881Speter * Takes a property name KEY, and is expected to return TRUE if the property 546251881Speter * should be filtered out (ie. not be copied to the target list), or FALSE if 547251881Speter * not. 548251881Speter */ 549251881Spetertypedef svn_boolean_t (*filter_func_t)(const char *key); 550251881Speter 551251881Speter/* Make a new set of properties, by copying those properties in PROPS for which 552251881Speter * the filter FILTER returns FALSE. 553251881Speter * 554251881Speter * The number of properties not copied will be stored in FILTERED_COUNT. 555251881Speter * 556251881Speter * The returned set of properties is allocated from POOL. 557251881Speter */ 558251881Speterstatic apr_hash_t * 559251881Speterfilter_props(int *filtered_count, apr_hash_t *props, 560251881Speter filter_func_t filter, 561251881Speter apr_pool_t *pool) 562251881Speter{ 563251881Speter apr_hash_index_t *hi; 564251881Speter apr_hash_t *filtered = apr_hash_make(pool); 565251881Speter *filtered_count = 0; 566251881Speter 567251881Speter for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi)) 568251881Speter { 569299742Sdim const char *propname = apr_hash_this_key(hi); 570299742Sdim void *propval = apr_hash_this_val(hi); 571251881Speter 572251881Speter /* Copy all properties: 573251881Speter - not matching the exclude pattern if provided OR 574251881Speter - matching the include pattern if provided */ 575251881Speter if (!filter || !filter(propname)) 576251881Speter { 577251881Speter svn_hash_sets(filtered, propname, propval); 578251881Speter } 579251881Speter else 580251881Speter { 581251881Speter *filtered_count += 1; 582251881Speter } 583251881Speter } 584251881Speter 585251881Speter return filtered; 586251881Speter} 587251881Speter 588251881Speter 589251881Speter/* Write the set of revision properties REV_PROPS to revision REV to the 590251881Speter * repository associated with RA session SESSION. 591251881Speter * Omit any properties whose names are in the svnsync property name space, 592251881Speter * and set *FILTERED_COUNT to the number of properties thus omitted. 593251881Speter * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval. 594251881Speter * 595251881Speter * All allocations will be done in a subpool of POOL. 596251881Speter */ 597251881Speterstatic svn_error_t * 598251881Speterwrite_revprops(int *filtered_count, 599251881Speter svn_ra_session_t *session, 600251881Speter svn_revnum_t rev, 601251881Speter apr_hash_t *rev_props, 602251881Speter apr_pool_t *pool) 603251881Speter{ 604251881Speter apr_pool_t *subpool = svn_pool_create(pool); 605251881Speter apr_hash_index_t *hi; 606251881Speter 607251881Speter *filtered_count = 0; 608251881Speter 609251881Speter for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi)) 610251881Speter { 611299742Sdim const char *propname = apr_hash_this_key(hi); 612299742Sdim const svn_string_t *propval = apr_hash_this_val(hi); 613251881Speter 614251881Speter svn_pool_clear(subpool); 615251881Speter 616251881Speter if (strncmp(propname, SVNSYNC_PROP_PREFIX, 617251881Speter sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0) 618251881Speter { 619251881Speter SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL, 620251881Speter propval, subpool)); 621251881Speter } 622251881Speter else 623251881Speter { 624251881Speter *filtered_count += 1; 625251881Speter } 626251881Speter } 627251881Speter 628251881Speter svn_pool_destroy(subpool); 629251881Speter 630251881Speter return SVN_NO_ERROR; 631251881Speter} 632251881Speter 633251881Speter 634251881Speterstatic svn_error_t * 635251881Speterlog_properties_copied(svn_boolean_t syncprops_found, 636251881Speter svn_revnum_t rev, 637251881Speter apr_pool_t *pool) 638251881Speter{ 639251881Speter if (syncprops_found) 640251881Speter SVN_ERR(svn_cmdline_printf(pool, 641251881Speter _("Copied properties for revision %ld " 642251881Speter "(%s* properties skipped).\n"), 643251881Speter rev, SVNSYNC_PROP_PREFIX)); 644251881Speter else 645251881Speter SVN_ERR(svn_cmdline_printf(pool, 646251881Speter _("Copied properties for revision %ld.\n"), 647251881Speter rev)); 648251881Speter 649251881Speter return SVN_NO_ERROR; 650251881Speter} 651251881Speter 652251881Speter/* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and 653251881Speter * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line 654251881Speter * endings, if either of those numbers is non-zero. */ 655251881Speterstatic svn_error_t * 656251881Speterlog_properties_normalized(int normalized_rev_props_count, 657251881Speter int normalized_node_props_count, 658251881Speter apr_pool_t *pool) 659251881Speter{ 660251881Speter if (normalized_rev_props_count > 0 || normalized_node_props_count > 0) 661251881Speter SVN_ERR(svn_cmdline_printf(pool, 662251881Speter _("NOTE: Normalized %s* properties " 663251881Speter "to LF line endings (%d rev-props, " 664251881Speter "%d node-props).\n"), 665251881Speter SVN_PROP_PREFIX, 666251881Speter normalized_rev_props_count, 667251881Speter normalized_node_props_count)); 668251881Speter return SVN_NO_ERROR; 669251881Speter} 670251881Speter 671251881Speter 672251881Speter/* Copy all the revision properties, except for those that have the 673251881Speter * "svn:sync-" prefix, from revision REV of the repository associated 674251881Speter * with RA session FROM_SESSION, to the repository associated with RA 675251881Speter * session TO_SESSION. 676251881Speter * 677251881Speter * If SYNC is TRUE, then properties on the destination revision that 678251881Speter * do not exist on the source revision will be removed. 679251881Speter * 680251881Speter * If QUIET is FALSE, then log_properties_copied() is called to log that 681251881Speter * properties were copied for revision REV. 682251881Speter * 683251881Speter * Make sure the values of svn:* revision properties use only LF (\n) 684251881Speter * line ending style, correcting their values as necessary. The number 685251881Speter * of properties that were normalized is returned in *NORMALIZED_COUNT. 686251881Speter */ 687251881Speterstatic svn_error_t * 688251881Spetercopy_revprops(svn_ra_session_t *from_session, 689251881Speter svn_ra_session_t *to_session, 690251881Speter svn_revnum_t rev, 691251881Speter svn_boolean_t sync, 692251881Speter svn_boolean_t quiet, 693251881Speter const char *source_prop_encoding, 694251881Speter int *normalized_count, 695251881Speter apr_pool_t *pool) 696251881Speter{ 697251881Speter apr_pool_t *subpool = svn_pool_create(pool); 698251881Speter apr_hash_t *existing_props, *rev_props; 699251881Speter int filtered_count = 0; 700251881Speter 701251881Speter /* Get the list of revision properties on REV of TARGET. We're only interested 702251881Speter in the property names, but we'll get the values 'for free'. */ 703251881Speter if (sync) 704251881Speter SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool)); 705251881Speter else 706251881Speter existing_props = NULL; 707251881Speter 708251881Speter /* Get the list of revision properties on REV of SOURCE. */ 709251881Speter SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool)); 710251881Speter 711251881Speter /* If necessary, normalize encoding and line ending style and return the count 712251881Speter of EOL-normalized properties in int *NORMALIZED_COUNT. */ 713251881Speter SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count, 714251881Speter source_prop_encoding, pool)); 715251881Speter 716251881Speter /* Copy all but the svn:svnsync properties. */ 717251881Speter SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool)); 718251881Speter 719251881Speter /* Delete those properties that were in TARGET but not in SOURCE */ 720251881Speter if (sync) 721251881Speter SVN_ERR(remove_props_not_in_source(to_session, rev, 722251881Speter rev_props, existing_props, pool)); 723251881Speter 724251881Speter if (! quiet) 725251881Speter SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool)); 726251881Speter 727251881Speter svn_pool_destroy(subpool); 728251881Speter 729251881Speter return SVN_NO_ERROR; 730251881Speter} 731251881Speter 732251881Speter 733251881Speter/* Return a subcommand baton allocated from POOL and populated with 734251881Speter data from the provided parameters, which include the global 735251881Speter OPT_BATON options structure and a handful of other options. Not 736251881Speter all parameters are used in all subcommands -- see 737251881Speter subcommand_baton_t's definition for details. */ 738251881Speterstatic subcommand_baton_t * 739251881Spetermake_subcommand_baton(opt_baton_t *opt_baton, 740251881Speter const char *to_url, 741251881Speter const char *from_url, 742251881Speter svn_revnum_t start_rev, 743251881Speter svn_revnum_t end_rev, 744251881Speter apr_pool_t *pool) 745251881Speter{ 746251881Speter subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b)); 747251881Speter b->config = opt_baton->config; 748251881Speter b->source_callbacks.open_tmp_file = open_tmp_file; 749251881Speter b->source_callbacks.auth_baton = opt_baton->source_auth_baton; 750251881Speter b->sync_callbacks.open_tmp_file = open_tmp_file; 751251881Speter b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton; 752251881Speter b->quiet = opt_baton->quiet; 753251881Speter b->allow_non_empty = opt_baton->allow_non_empty; 754251881Speter b->to_url = to_url; 755251881Speter b->source_prop_encoding = opt_baton->source_prop_encoding; 756251881Speter b->from_url = from_url; 757251881Speter b->start_rev = start_rev; 758251881Speter b->end_rev = end_rev; 759251881Speter return b; 760251881Speter} 761251881Speter 762251881Speterstatic svn_error_t * 763251881Speteropen_target_session(svn_ra_session_t **to_session_p, 764251881Speter subcommand_baton_t *baton, 765251881Speter apr_pool_t *pool); 766251881Speter 767251881Speter 768251881Speter/*** `svnsync init' ***/ 769251881Speter 770251881Speter/* Initialize the repository associated with RA session TO_SESSION, 771299742Sdim * using information found in BATON. 772299742Sdim * 773299742Sdim * Implements `with_locked_func_t' interface. The caller has 774299742Sdim * acquired a lock on the repository if locking is needed. 775251881Speter */ 776251881Speterstatic svn_error_t * 777251881Speterdo_initialize(svn_ra_session_t *to_session, 778251881Speter subcommand_baton_t *baton, 779251881Speter apr_pool_t *pool) 780251881Speter{ 781251881Speter svn_ra_session_t *from_session; 782251881Speter svn_string_t *from_url; 783251881Speter svn_revnum_t latest, from_latest; 784251881Speter const char *uuid, *root_url; 785251881Speter int normalized_rev_props_count; 786251881Speter 787251881Speter /* First, sanity check to see that we're copying into a brand new 788251881Speter repos. If we aren't, and we aren't being asked to forcibly 789251881Speter complete this initialization, that's a bad news. */ 790251881Speter SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool)); 791251881Speter if ((latest != 0) && (! baton->allow_non_empty)) 792251881Speter return svn_error_create 793251881Speter (APR_EINVAL, NULL, 794251881Speter _("Destination repository already contains revision history; consider " 795251881Speter "using --allow-non-empty if the repository's revisions are known " 796251881Speter "to mirror their respective revisions in the source repository")); 797251881Speter 798251881Speter SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, 799251881Speter &from_url, pool)); 800251881Speter if (from_url && (! baton->allow_non_empty)) 801251881Speter return svn_error_createf 802251881Speter (APR_EINVAL, NULL, 803251881Speter _("Destination repository is already synchronizing from '%s'"), 804251881Speter from_url->data); 805251881Speter 806251881Speter /* Now fill in our bookkeeping info in the dest repository. */ 807251881Speter 808251881Speter SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL, 809251881Speter &(baton->source_callbacks), baton, 810251881Speter baton->config, pool)); 811251881Speter SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool)); 812251881Speter 813251881Speter /* If we're doing a partial replay, we have to check first if the server 814251881Speter supports this. */ 815251881Speter if (strcmp(root_url, baton->from_url) != 0) 816251881Speter { 817251881Speter svn_boolean_t server_supports_partial_replay; 818251881Speter svn_error_t *err = svn_ra_has_capability(from_session, 819251881Speter &server_supports_partial_replay, 820251881Speter SVN_RA_CAPABILITY_PARTIAL_REPLAY, 821251881Speter pool); 822251881Speter if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY) 823251881Speter return svn_error_trace(err); 824251881Speter 825251881Speter if (err || !server_supports_partial_replay) 826251881Speter return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err, 827251881Speter NULL); 828251881Speter } 829251881Speter 830251881Speter /* If we're initializing a non-empty destination, we'll make sure 831251881Speter that it at least doesn't have more revisions than the source. */ 832251881Speter if (latest != 0) 833251881Speter { 834251881Speter SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); 835251881Speter if (from_latest < latest) 836251881Speter return svn_error_create 837251881Speter (APR_EINVAL, NULL, 838251881Speter _("Destination repository has more revisions than source " 839251881Speter "repository")); 840251881Speter } 841251881Speter 842251881Speter SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL, 843251881Speter svn_string_create(baton->from_url, pool), 844251881Speter pool)); 845251881Speter 846251881Speter SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool)); 847251881Speter SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL, 848251881Speter svn_string_create(uuid, pool), pool)); 849251881Speter 850251881Speter SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, 851251881Speter NULL, svn_string_createf(pool, "%ld", latest), 852251881Speter pool)); 853251881Speter 854251881Speter /* Copy all non-svnsync revprops from the LATEST rev in the source 855251881Speter repository into the destination, notifying about normalized 856251881Speter props, if any. When LATEST is 0, this serves the practical 857251881Speter purpose of initializing data that would otherwise be overlooked 858251881Speter by the sync process (which is going to begin with r1). When 859251881Speter LATEST is not 0, this really serves merely aesthetic and 860251881Speter informational purposes, keeping the output of this command 861251881Speter consistent while allowing folks to see what the latest revision is. */ 862251881Speter SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet, 863251881Speter baton->source_prop_encoding, &normalized_rev_props_count, 864251881Speter pool)); 865251881Speter 866251881Speter SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool)); 867251881Speter 868251881Speter /* TODO: It would be nice if we could set the dest repos UUID to be 869251881Speter equal to the UUID of the source repos, at least optionally. That 870251881Speter way people could check out/log/diff using a local fast mirror, 871251881Speter but switch --relocate to the actual final repository in order to 872251881Speter make changes... But at this time, the RA layer doesn't have a 873251881Speter way to set a UUID. */ 874251881Speter 875251881Speter return SVN_NO_ERROR; 876251881Speter} 877251881Speter 878251881Speter 879251881Speter/* SUBCOMMAND: init */ 880251881Speterstatic svn_error_t * 881251881Speterinitialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 882251881Speter{ 883251881Speter const char *to_url, *from_url; 884251881Speter svn_ra_session_t *to_session; 885251881Speter opt_baton_t *opt_baton = b; 886251881Speter apr_array_header_t *targets; 887251881Speter subcommand_baton_t *baton; 888251881Speter 889251881Speter SVN_ERR(svn_opt__args_to_target_array(&targets, os, 890251881Speter apr_array_make(pool, 0, 891251881Speter sizeof(const char *)), 892251881Speter pool)); 893251881Speter if (targets->nelts < 2) 894251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 895251881Speter if (targets->nelts > 2) 896251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 897251881Speter 898251881Speter to_url = APR_ARRAY_IDX(targets, 0, const char *); 899251881Speter from_url = APR_ARRAY_IDX(targets, 1, const char *); 900251881Speter 901251881Speter if (! svn_path_is_url(to_url)) 902251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 903251881Speter _("Path '%s' is not a URL"), to_url); 904251881Speter if (! svn_path_is_url(from_url)) 905251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 906251881Speter _("Path '%s' is not a URL"), from_url); 907251881Speter 908251881Speter baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool); 909251881Speter SVN_ERR(open_target_session(&to_session, baton, pool)); 910251881Speter if (opt_baton->disable_locking) 911251881Speter SVN_ERR(do_initialize(to_session, baton, pool)); 912251881Speter else 913251881Speter SVN_ERR(with_locked(to_session, do_initialize, baton, 914251881Speter opt_baton->steal_lock, pool)); 915251881Speter 916251881Speter return SVN_NO_ERROR; 917251881Speter} 918251881Speter 919251881Speter 920251881Speter 921251881Speter/*** `svnsync sync' ***/ 922251881Speter 923251881Speter/* Implements `svn_commit_callback2_t' interface. */ 924251881Speterstatic svn_error_t * 925251881Spetercommit_callback(const svn_commit_info_t *commit_info, 926251881Speter void *baton, 927251881Speter apr_pool_t *pool) 928251881Speter{ 929251881Speter subcommand_baton_t *sb = baton; 930251881Speter 931251881Speter if (! sb->quiet) 932251881Speter { 933251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"), 934251881Speter commit_info->revision)); 935251881Speter } 936251881Speter 937251881Speter sb->committed_rev = commit_info->revision; 938251881Speter 939251881Speter return SVN_NO_ERROR; 940251881Speter} 941251881Speter 942251881Speter 943251881Speter/* Set *FROM_SESSION to an RA session associated with the source 944251881Speter * repository of the synchronization. If FROM_URL is non-NULL, use it 945251881Speter * as the source repository URL; otherwise, determine the source 946251881Speter * repository URL by reading svn:sync- properties from the destination 947251881Speter * repository (associated with TO_SESSION). Set LAST_MERGED_REV to 948251881Speter * the value of the property which records the most recently 949251881Speter * synchronized revision. 950251881Speter * 951251881Speter * CALLBACKS is a vtable of RA callbacks to provide when creating 952251881Speter * *FROM_SESSION. CONFIG is a configuration hash. 953251881Speter */ 954251881Speterstatic svn_error_t * 955251881Speteropen_source_session(svn_ra_session_t **from_session, 956251881Speter svn_string_t **last_merged_rev, 957251881Speter const char *from_url, 958251881Speter svn_ra_session_t *to_session, 959251881Speter svn_ra_callbacks2_t *callbacks, 960251881Speter apr_hash_t *config, 961251881Speter void *baton, 962251881Speter apr_pool_t *pool) 963251881Speter{ 964251881Speter apr_hash_t *props; 965251881Speter svn_string_t *from_url_str, *from_uuid_str; 966251881Speter 967251881Speter SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool)); 968251881Speter 969251881Speter from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL); 970251881Speter from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID); 971251881Speter *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV); 972251881Speter 973251881Speter if (! from_url_str || ! from_uuid_str || ! *last_merged_rev) 974251881Speter return svn_error_create 975251881Speter (APR_EINVAL, NULL, 976251881Speter _("Destination repository has not been initialized")); 977251881Speter 978251881Speter /* ### TODO: Should we validate that FROM_URL_STR->data matches any 979251881Speter provided FROM_URL here? */ 980251881Speter if (! from_url) 981299742Sdim SVN_ERR(svn_opt__arg_canonicalize_url(&from_url, from_url_str->data, 982299742Sdim pool)); 983251881Speter 984251881Speter /* Open the session to copy the revision data. */ 985251881Speter SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data, 986251881Speter callbacks, baton, config, pool)); 987251881Speter 988251881Speter return SVN_NO_ERROR; 989251881Speter} 990251881Speter 991251881Speter/* Set *TARGET_SESSION_P to an RA session associated with the target 992251881Speter * repository of the synchronization. 993251881Speter */ 994251881Speterstatic svn_error_t * 995251881Speteropen_target_session(svn_ra_session_t **target_session_p, 996251881Speter subcommand_baton_t *baton, 997251881Speter apr_pool_t *pool) 998251881Speter{ 999251881Speter svn_ra_session_t *target_session; 1000251881Speter SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL, 1001251881Speter &(baton->sync_callbacks), baton, baton->config, pool)); 1002251881Speter SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool)); 1003251881Speter 1004251881Speter *target_session_p = target_session; 1005251881Speter return SVN_NO_ERROR; 1006251881Speter} 1007251881Speter 1008251881Speter/* Replay baton, used during synchronization. */ 1009251881Spetertypedef struct replay_baton_t { 1010251881Speter svn_ra_session_t *from_session; 1011251881Speter svn_ra_session_t *to_session; 1012251881Speter svn_revnum_t current_revision; 1013251881Speter subcommand_baton_t *sb; 1014251881Speter svn_boolean_t has_commit_revprops_capability; 1015299742Sdim svn_boolean_t has_atomic_revprops_capability; 1016251881Speter int normalized_rev_props_count; 1017251881Speter int normalized_node_props_count; 1018251881Speter const char *to_root; 1019299742Sdim 1020299742Sdim#ifdef ENABLE_EV2_SHIMS 1021299742Sdim /* Extra 'backdoor' session for fetching data *from* the target repo. */ 1022299742Sdim svn_ra_session_t *extra_to_session; 1023299742Sdim#endif 1024251881Speter} replay_baton_t; 1025251881Speter 1026251881Speter/* Return a replay baton allocated from POOL and populated with 1027251881Speter data from the provided parameters. */ 1028251881Speterstatic svn_error_t * 1029251881Spetermake_replay_baton(replay_baton_t **baton_p, 1030251881Speter svn_ra_session_t *from_session, 1031251881Speter svn_ra_session_t *to_session, 1032251881Speter subcommand_baton_t *sb, apr_pool_t *pool) 1033251881Speter{ 1034251881Speter replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb)); 1035251881Speter rb->from_session = from_session; 1036251881Speter rb->to_session = to_session; 1037251881Speter rb->sb = sb; 1038251881Speter 1039251881Speter SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool)); 1040251881Speter 1041251881Speter#ifdef ENABLE_EV2_SHIMS 1042251881Speter /* Open up the extra baton. Only needed for Ev2 shims. */ 1043251881Speter SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool)); 1044251881Speter#endif 1045251881Speter 1046251881Speter *baton_p = rb; 1047251881Speter return SVN_NO_ERROR; 1048251881Speter} 1049251881Speter 1050251881Speter/* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync 1051251881Speter * property. Implements filter_func_t. Use with filter_props() to filter out 1052251881Speter * svn:date and svn:author and svnsync properties. 1053251881Speter */ 1054251881Speterstatic svn_boolean_t 1055251881Speterfilter_exclude_date_author_sync(const char *key) 1056251881Speter{ 1057251881Speter if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0) 1058251881Speter return TRUE; 1059251881Speter else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0) 1060251881Speter return TRUE; 1061251881Speter else if (strncmp(key, SVNSYNC_PROP_PREFIX, 1062251881Speter sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0) 1063251881Speter return TRUE; 1064251881Speter 1065251881Speter return FALSE; 1066251881Speter} 1067251881Speter 1068251881Speter/* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync 1069251881Speter * property. Implements filter_func_t. Use with filter_props() to filter out 1070251881Speter * all properties except svn:date and svn:author and svnsync properties. 1071251881Speter */ 1072251881Speterstatic svn_boolean_t 1073251881Speterfilter_include_date_author_sync(const char *key) 1074251881Speter{ 1075251881Speter return ! filter_exclude_date_author_sync(key); 1076251881Speter} 1077251881Speter 1078251881Speter 1079251881Speter/* Return TRUE iff KEY is the name of the svn:log property. 1080251881Speter * Implements filter_func_t. Use with filter_props() to only exclude svn:log. 1081251881Speter */ 1082251881Speterstatic svn_boolean_t 1083251881Speterfilter_exclude_log(const char *key) 1084251881Speter{ 1085251881Speter if (strcmp(key, SVN_PROP_REVISION_LOG) == 0) 1086251881Speter return TRUE; 1087251881Speter else 1088251881Speter return FALSE; 1089251881Speter} 1090251881Speter 1091251881Speter/* Return FALSE iff KEY is the name of the svn:log property. 1092251881Speter * Implements filter_func_t. Use with filter_props() to only include svn:log. 1093251881Speter */ 1094251881Speterstatic svn_boolean_t 1095251881Speterfilter_include_log(const char *key) 1096251881Speter{ 1097251881Speter return ! filter_exclude_log(key); 1098251881Speter} 1099251881Speter 1100299742Sdim#ifdef ENABLE_EV2_SHIMS 1101251881Speterstatic svn_error_t * 1102251881Speterfetch_base_func(const char **filename, 1103251881Speter void *baton, 1104251881Speter const char *path, 1105251881Speter svn_revnum_t base_revision, 1106251881Speter apr_pool_t *result_pool, 1107251881Speter apr_pool_t *scratch_pool) 1108251881Speter{ 1109251881Speter struct replay_baton_t *rb = baton; 1110251881Speter svn_stream_t *fstream; 1111251881Speter svn_error_t *err; 1112251881Speter 1113251881Speter if (svn_path_is_url(path)) 1114251881Speter path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1115251881Speter else if (path[0] == '/') 1116251881Speter path += 1; 1117251881Speter 1118251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 1119251881Speter base_revision = rb->current_revision - 1; 1120251881Speter 1121251881Speter SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 1122251881Speter svn_io_file_del_on_pool_cleanup, 1123251881Speter result_pool, scratch_pool)); 1124251881Speter 1125251881Speter err = svn_ra_get_file(rb->extra_to_session, path, base_revision, 1126251881Speter fstream, NULL, NULL, scratch_pool); 1127251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1128251881Speter { 1129251881Speter svn_error_clear(err); 1130251881Speter SVN_ERR(svn_stream_close(fstream)); 1131251881Speter 1132251881Speter *filename = NULL; 1133251881Speter return SVN_NO_ERROR; 1134251881Speter } 1135251881Speter else if (err) 1136251881Speter return svn_error_trace(err); 1137251881Speter 1138251881Speter SVN_ERR(svn_stream_close(fstream)); 1139251881Speter 1140251881Speter return SVN_NO_ERROR; 1141251881Speter} 1142251881Speter 1143251881Speterstatic svn_error_t * 1144251881Speterfetch_props_func(apr_hash_t **props, 1145251881Speter void *baton, 1146251881Speter const char *path, 1147251881Speter svn_revnum_t base_revision, 1148251881Speter apr_pool_t *result_pool, 1149251881Speter apr_pool_t *scratch_pool) 1150251881Speter{ 1151251881Speter struct replay_baton_t *rb = baton; 1152251881Speter svn_node_kind_t node_kind; 1153251881Speter 1154251881Speter if (svn_path_is_url(path)) 1155251881Speter path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1156251881Speter else if (path[0] == '/') 1157251881Speter path += 1; 1158251881Speter 1159251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 1160251881Speter base_revision = rb->current_revision - 1; 1161251881Speter 1162251881Speter SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision, 1163251881Speter &node_kind, scratch_pool)); 1164251881Speter 1165251881Speter if (node_kind == svn_node_file) 1166251881Speter { 1167251881Speter SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision, 1168251881Speter NULL, NULL, props, result_pool)); 1169251881Speter } 1170251881Speter else if (node_kind == svn_node_dir) 1171251881Speter { 1172251881Speter apr_array_header_t *tmp_props; 1173251881Speter 1174251881Speter SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path, 1175251881Speter base_revision, 0 /* Dirent fields */, 1176251881Speter result_pool)); 1177251881Speter tmp_props = svn_prop_hash_to_array(*props, result_pool); 1178251881Speter SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 1179251881Speter result_pool)); 1180251881Speter *props = svn_prop_array_to_hash(tmp_props, result_pool); 1181251881Speter } 1182251881Speter else 1183251881Speter { 1184251881Speter *props = apr_hash_make(result_pool); 1185251881Speter } 1186251881Speter 1187251881Speter return SVN_NO_ERROR; 1188251881Speter} 1189251881Speter 1190251881Speterstatic svn_error_t * 1191251881Speterfetch_kind_func(svn_node_kind_t *kind, 1192251881Speter void *baton, 1193251881Speter const char *path, 1194251881Speter svn_revnum_t base_revision, 1195251881Speter apr_pool_t *scratch_pool) 1196251881Speter{ 1197251881Speter struct replay_baton_t *rb = baton; 1198251881Speter 1199251881Speter if (svn_path_is_url(path)) 1200251881Speter path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool); 1201251881Speter else if (path[0] == '/') 1202251881Speter path += 1; 1203251881Speter 1204251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 1205251881Speter base_revision = rb->current_revision - 1; 1206251881Speter 1207251881Speter SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision, 1208251881Speter kind, scratch_pool)); 1209251881Speter 1210251881Speter return SVN_NO_ERROR; 1211251881Speter} 1212251881Speter 1213251881Speter 1214251881Speterstatic svn_delta_shim_callbacks_t * 1215251881Speterget_shim_callbacks(replay_baton_t *rb, 1216251881Speter apr_pool_t *result_pool) 1217251881Speter{ 1218251881Speter svn_delta_shim_callbacks_t *callbacks = 1219251881Speter svn_delta_shim_callbacks_default(result_pool); 1220251881Speter 1221251881Speter callbacks->fetch_props_func = fetch_props_func; 1222251881Speter callbacks->fetch_kind_func = fetch_kind_func; 1223251881Speter callbacks->fetch_base_func = fetch_base_func; 1224251881Speter callbacks->fetch_baton = rb; 1225251881Speter 1226251881Speter return callbacks; 1227251881Speter} 1228299742Sdim#endif 1229251881Speter 1230251881Speter 1231251881Speter/* Callback function for svn_ra_replay_range, invoked when starting to parse 1232251881Speter * a replay report. 1233251881Speter */ 1234251881Speterstatic svn_error_t * 1235251881Speterreplay_rev_started(svn_revnum_t revision, 1236251881Speter void *replay_baton, 1237251881Speter const svn_delta_editor_t **editor, 1238251881Speter void **edit_baton, 1239251881Speter apr_hash_t *rev_props, 1240251881Speter apr_pool_t *pool) 1241251881Speter{ 1242251881Speter const svn_delta_editor_t *commit_editor; 1243251881Speter const svn_delta_editor_t *cancel_editor; 1244251881Speter const svn_delta_editor_t *sync_editor; 1245251881Speter void *commit_baton; 1246251881Speter void *cancel_baton; 1247251881Speter void *sync_baton; 1248251881Speter replay_baton_t *rb = replay_baton; 1249251881Speter apr_hash_t *filtered; 1250251881Speter int filtered_count; 1251251881Speter int normalized_count; 1252251881Speter 1253251881Speter /* We set this property so that if we error out for some reason 1254251881Speter we can later determine where we were in the process of 1255251881Speter merging a revision. If we had committed the change, but we 1256251881Speter hadn't finished copying the revprops we need to know that, so 1257251881Speter we can go back and finish the job before we move on. 1258251881Speter 1259251881Speter NOTE: We have to set this before we start the commit editor, 1260251881Speter because ra_svn doesn't let you change rev props during a 1261251881Speter commit. */ 1262251881Speter SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0, 1263251881Speter SVNSYNC_PROP_CURRENTLY_COPYING, 1264251881Speter NULL, 1265251881Speter svn_string_createf(pool, "%ld", revision), 1266251881Speter pool)); 1267251881Speter 1268251881Speter /* The actual copy is just a replay hooked up to a commit. Include 1269251881Speter all the revision properties from the source repositories, except 1270251881Speter 'svn:author' and 'svn:date', those are not guaranteed to get 1271251881Speter through the editor anyway. 1272251881Speter If we're syncing to an non-commit-revprops capable server, filter 1273251881Speter out all revprops except svn:log and add them later in 1274251881Speter revplay_rev_finished. */ 1275251881Speter filtered = filter_props(&filtered_count, rev_props, 1276251881Speter (rb->has_commit_revprops_capability 1277251881Speter ? filter_exclude_date_author_sync 1278251881Speter : filter_include_log), 1279251881Speter pool); 1280251881Speter 1281251881Speter /* svn_ra_get_commit_editor3 requires the log message to be 1282251881Speter set. It's possible that we didn't receive 'svn:log' here, so we 1283251881Speter have to set it to at least the empty string. If there's a svn:log 1284251881Speter property on this revision, we will write the actual value in the 1285251881Speter replay_rev_finished callback. */ 1286251881Speter if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG)) 1287251881Speter svn_hash_sets(filtered, SVN_PROP_REVISION_LOG, 1288251881Speter svn_string_create_empty(pool)); 1289251881Speter 1290251881Speter /* If necessary, normalize encoding and line ending style. Add the number 1291251881Speter of properties that required EOL normalization to the overall count 1292251881Speter in the replay baton. */ 1293251881Speter SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count, 1294251881Speter rb->sb->source_prop_encoding, pool)); 1295251881Speter rb->normalized_rev_props_count += normalized_count; 1296251881Speter 1297299742Sdim#ifdef ENABLE_EV2_SHIMS 1298251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session, 1299251881Speter get_shim_callbacks(rb, pool))); 1300299742Sdim#endif 1301251881Speter SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor, 1302251881Speter &commit_baton, 1303251881Speter filtered, 1304251881Speter commit_callback, rb->sb, 1305251881Speter NULL, FALSE, pool)); 1306251881Speter 1307251881Speter /* There's one catch though, the diff shows us props we can't send 1308251881Speter over the RA interface, so we need an editor that's smart enough 1309251881Speter to filter those out for us. */ 1310251881Speter SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1, 1311251881Speter rb->sb->to_url, rb->sb->source_prop_encoding, 1312251881Speter rb->sb->quiet, &sync_editor, &sync_baton, 1313251881Speter &(rb->normalized_node_props_count), pool)); 1314251881Speter 1315251881Speter SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL, 1316251881Speter sync_editor, sync_baton, 1317251881Speter &cancel_editor, 1318251881Speter &cancel_baton, 1319251881Speter pool)); 1320251881Speter *editor = cancel_editor; 1321251881Speter *edit_baton = cancel_baton; 1322251881Speter 1323251881Speter rb->current_revision = revision; 1324251881Speter return SVN_NO_ERROR; 1325251881Speter} 1326251881Speter 1327251881Speter/* Callback function for svn_ra_replay_range, invoked when finishing parsing 1328251881Speter * a replay report. 1329251881Speter */ 1330251881Speterstatic svn_error_t * 1331251881Speterreplay_rev_finished(svn_revnum_t revision, 1332251881Speter void *replay_baton, 1333251881Speter const svn_delta_editor_t *editor, 1334251881Speter void *edit_baton, 1335251881Speter apr_hash_t *rev_props, 1336251881Speter apr_pool_t *pool) 1337251881Speter{ 1338251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1339251881Speter replay_baton_t *rb = replay_baton; 1340251881Speter apr_hash_t *filtered, *existing_props; 1341251881Speter int filtered_count; 1342251881Speter int normalized_count; 1343299742Sdim const svn_string_t *rev_str; 1344251881Speter 1345251881Speter SVN_ERR(editor->close_edit(edit_baton, pool)); 1346251881Speter 1347251881Speter /* Sanity check that we actually committed the revision we meant to. */ 1348251881Speter if (rb->sb->committed_rev != revision) 1349251881Speter return svn_error_createf 1350251881Speter (APR_EINVAL, NULL, 1351262253Speter _("Commit created r%ld but should have created r%ld"), 1352251881Speter rb->sb->committed_rev, revision); 1353251881Speter 1354251881Speter SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props, 1355251881Speter subpool)); 1356251881Speter 1357251881Speter 1358251881Speter /* Ok, we're done with the data, now we just need to copy the remaining 1359251881Speter 'svn:date' and 'svn:author' revprops and we're all set. 1360251881Speter If the server doesn't support revprops-in-a-commit, we still have to 1361251881Speter set all revision properties except svn:log. */ 1362251881Speter filtered = filter_props(&filtered_count, rev_props, 1363251881Speter (rb->has_commit_revprops_capability 1364251881Speter ? filter_include_date_author_sync 1365251881Speter : filter_exclude_log), 1366251881Speter subpool); 1367251881Speter 1368251881Speter /* If necessary, normalize encoding and line ending style, and add the number 1369251881Speter of EOL-normalized properties to the overall count in the replay baton. */ 1370251881Speter SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count, 1371251881Speter rb->sb->source_prop_encoding, pool)); 1372251881Speter rb->normalized_rev_props_count += normalized_count; 1373251881Speter 1374251881Speter SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered, 1375251881Speter subpool)); 1376251881Speter 1377251881Speter /* Remove all extra properties in TARGET. */ 1378251881Speter SVN_ERR(remove_props_not_in_source(rb->to_session, revision, 1379251881Speter rev_props, existing_props, subpool)); 1380251881Speter 1381251881Speter svn_pool_clear(subpool); 1382251881Speter 1383299742Sdim rev_str = svn_string_createf(subpool, "%ld", revision); 1384299742Sdim 1385251881Speter /* Ok, we're done, bring the last-merged-rev property up to date. */ 1386251881Speter SVN_ERR(svn_ra_change_rev_prop2( 1387251881Speter rb->to_session, 1388251881Speter 0, 1389251881Speter SVNSYNC_PROP_LAST_MERGED_REV, 1390251881Speter NULL, 1391299742Sdim rev_str, 1392251881Speter subpool)); 1393251881Speter 1394251881Speter /* And finally drop the currently copying prop, since we're done 1395251881Speter with this revision. */ 1396251881Speter SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0, 1397251881Speter SVNSYNC_PROP_CURRENTLY_COPYING, 1398299742Sdim rb->has_atomic_revprops_capability 1399299742Sdim ? &rev_str : NULL, 1400299742Sdim NULL, subpool)); 1401251881Speter 1402251881Speter /* Notify the user that we copied revision properties. */ 1403251881Speter if (! rb->sb->quiet) 1404251881Speter SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool)); 1405251881Speter 1406251881Speter svn_pool_destroy(subpool); 1407251881Speter 1408251881Speter return SVN_NO_ERROR; 1409251881Speter} 1410251881Speter 1411251881Speter/* Synchronize the repository associated with RA session TO_SESSION, 1412299742Sdim * using information found in BATON. 1413299742Sdim * 1414299742Sdim * Implements `with_locked_func_t' interface. The caller has 1415299742Sdim * acquired a lock on the repository if locking is needed. 1416251881Speter */ 1417251881Speterstatic svn_error_t * 1418251881Speterdo_synchronize(svn_ra_session_t *to_session, 1419251881Speter subcommand_baton_t *baton, apr_pool_t *pool) 1420251881Speter{ 1421251881Speter svn_string_t *last_merged_rev; 1422251881Speter svn_revnum_t from_latest; 1423251881Speter svn_ra_session_t *from_session; 1424251881Speter svn_string_t *currently_copying; 1425251881Speter svn_revnum_t to_latest, copying, last_merged; 1426251881Speter svn_revnum_t start_revision, end_revision; 1427251881Speter replay_baton_t *rb; 1428251881Speter int normalized_rev_props_count = 0; 1429251881Speter 1430251881Speter SVN_ERR(open_source_session(&from_session, &last_merged_rev, 1431251881Speter baton->from_url, to_session, 1432251881Speter &(baton->source_callbacks), baton->config, 1433251881Speter baton, pool)); 1434251881Speter 1435251881Speter /* Check to see if we have revprops that still need to be copied for 1436251881Speter a prior revision we didn't finish copying. But first, check for 1437251881Speter state sanity. Remember, mirroring is not an atomic action, 1438251881Speter because revision properties are copied separately from the 1439251881Speter revision's contents. 1440251881Speter 1441251881Speter So, any time that currently-copying is not set, then 1442251881Speter last-merged-rev should be the HEAD revision of the destination 1443251881Speter repository. That is, if we didn't fall over in the middle of a 1444251881Speter previous synchronization, then our destination repository should 1445251881Speter have exactly as many revisions in it as we've synchronized. 1446251881Speter 1447251881Speter Alternately, if currently-copying *is* set, it must 1448251881Speter be either last-merged-rev or last-merged-rev + 1, and the HEAD 1449251881Speter revision must be equal to either last-merged-rev or 1450251881Speter currently-copying. If this is not the case, somebody has meddled 1451251881Speter with the destination without using svnsync. 1452251881Speter */ 1453251881Speter 1454251881Speter SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING, 1455251881Speter ¤tly_copying, pool)); 1456251881Speter 1457251881Speter SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool)); 1458251881Speter 1459251881Speter last_merged = SVN_STR_TO_REV(last_merged_rev->data); 1460251881Speter 1461251881Speter if (currently_copying) 1462251881Speter { 1463251881Speter copying = SVN_STR_TO_REV(currently_copying->data); 1464251881Speter 1465251881Speter if ((copying < last_merged) 1466251881Speter || (copying > (last_merged + 1)) 1467251881Speter || ((to_latest != last_merged) && (to_latest != copying))) 1468251881Speter { 1469251881Speter return svn_error_createf 1470251881Speter (APR_EINVAL, NULL, 1471251881Speter _("Revision being currently copied (%ld), last merged revision " 1472251881Speter "(%ld), and destination HEAD (%ld) are inconsistent; have you " 1473251881Speter "committed to the destination without using svnsync?"), 1474251881Speter copying, last_merged, to_latest); 1475251881Speter } 1476251881Speter else if (copying == to_latest) 1477251881Speter { 1478251881Speter if (copying > last_merged) 1479251881Speter { 1480251881Speter SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE, 1481251881Speter baton->quiet, baton->source_prop_encoding, 1482251881Speter &normalized_rev_props_count, pool)); 1483251881Speter last_merged = copying; 1484251881Speter last_merged_rev = svn_string_create 1485251881Speter (apr_psprintf(pool, "%ld", last_merged), pool); 1486251881Speter } 1487251881Speter 1488251881Speter /* Now update last merged rev and drop currently changing. 1489251881Speter Note that the order here is significant, if we do them 1490251881Speter in the wrong order there are race conditions where we 1491251881Speter end up not being able to tell if there have been bogus 1492251881Speter (i.e. non-svnsync) commits to the dest repository. */ 1493251881Speter 1494251881Speter SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, 1495251881Speter SVNSYNC_PROP_LAST_MERGED_REV, 1496251881Speter NULL, last_merged_rev, pool)); 1497251881Speter SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, 1498251881Speter SVNSYNC_PROP_CURRENTLY_COPYING, 1499251881Speter NULL, NULL, pool)); 1500251881Speter } 1501251881Speter /* If copying > to_latest, then we just fall through to 1502251881Speter attempting to copy the revision again. */ 1503251881Speter } 1504251881Speter else 1505251881Speter { 1506251881Speter if (to_latest != last_merged) 1507251881Speter return svn_error_createf(APR_EINVAL, NULL, 1508251881Speter _("Destination HEAD (%ld) is not the last " 1509251881Speter "merged revision (%ld); have you " 1510251881Speter "committed to the destination without " 1511251881Speter "using svnsync?"), 1512251881Speter to_latest, last_merged); 1513251881Speter } 1514251881Speter 1515251881Speter /* Now check to see if there are any revisions to copy. */ 1516251881Speter SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); 1517251881Speter 1518251881Speter if (from_latest < last_merged) 1519251881Speter return SVN_NO_ERROR; 1520251881Speter 1521251881Speter /* Ok, so there are new revisions, iterate over them copying them 1522251881Speter into the destination repository. */ 1523251881Speter SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool)); 1524251881Speter 1525251881Speter /* For compatibility with older svnserve versions, check first if we 1526251881Speter support adding revprops to the commit. */ 1527251881Speter SVN_ERR(svn_ra_has_capability(rb->to_session, 1528251881Speter &rb->has_commit_revprops_capability, 1529251881Speter SVN_RA_CAPABILITY_COMMIT_REVPROPS, 1530251881Speter pool)); 1531251881Speter 1532299742Sdim SVN_ERR(svn_ra_has_capability(rb->to_session, 1533299742Sdim &rb->has_atomic_revprops_capability, 1534299742Sdim SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 1535299742Sdim pool)); 1536299742Sdim 1537251881Speter start_revision = last_merged + 1; 1538251881Speter end_revision = from_latest; 1539251881Speter 1540251881Speter SVN_ERR(check_cancel(NULL)); 1541251881Speter 1542251881Speter SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision, 1543251881Speter 0, TRUE, replay_rev_started, 1544251881Speter replay_rev_finished, rb, pool)); 1545251881Speter 1546251881Speter SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count 1547251881Speter + normalized_rev_props_count, 1548251881Speter rb->normalized_node_props_count, 1549251881Speter pool)); 1550251881Speter 1551251881Speter 1552251881Speter return SVN_NO_ERROR; 1553251881Speter} 1554251881Speter 1555251881Speter 1556251881Speter/* SUBCOMMAND: sync */ 1557251881Speterstatic svn_error_t * 1558251881Spetersynchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 1559251881Speter{ 1560251881Speter svn_ra_session_t *to_session; 1561251881Speter opt_baton_t *opt_baton = b; 1562251881Speter apr_array_header_t *targets; 1563251881Speter subcommand_baton_t *baton; 1564251881Speter const char *to_url, *from_url; 1565251881Speter 1566251881Speter SVN_ERR(svn_opt__args_to_target_array(&targets, os, 1567251881Speter apr_array_make(pool, 0, 1568251881Speter sizeof(const char *)), 1569251881Speter pool)); 1570251881Speter if (targets->nelts < 1) 1571251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1572251881Speter if (targets->nelts > 2) 1573251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1574251881Speter 1575251881Speter to_url = APR_ARRAY_IDX(targets, 0, const char *); 1576251881Speter if (! svn_path_is_url(to_url)) 1577251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1578251881Speter _("Path '%s' is not a URL"), to_url); 1579251881Speter 1580251881Speter if (targets->nelts == 2) 1581251881Speter { 1582251881Speter from_url = APR_ARRAY_IDX(targets, 1, const char *); 1583251881Speter if (! svn_path_is_url(from_url)) 1584251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1585251881Speter _("Path '%s' is not a URL"), from_url); 1586251881Speter } 1587251881Speter else 1588251881Speter { 1589251881Speter from_url = NULL; /* we'll read it from the destination repos */ 1590251881Speter } 1591251881Speter 1592251881Speter baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool); 1593251881Speter SVN_ERR(open_target_session(&to_session, baton, pool)); 1594251881Speter if (opt_baton->disable_locking) 1595251881Speter SVN_ERR(do_synchronize(to_session, baton, pool)); 1596251881Speter else 1597251881Speter SVN_ERR(with_locked(to_session, do_synchronize, baton, 1598251881Speter opt_baton->steal_lock, pool)); 1599251881Speter 1600251881Speter return SVN_NO_ERROR; 1601251881Speter} 1602251881Speter 1603251881Speter 1604251881Speter 1605251881Speter/*** `svnsync copy-revprops' ***/ 1606251881Speter 1607251881Speter/* Copy revision properties to the repository associated with RA 1608299742Sdim * session TO_SESSION, using information found in BATON. 1609299742Sdim * 1610299742Sdim * Implements `with_locked_func_t' interface. The caller has 1611299742Sdim * acquired a lock on the repository if locking is needed. 1612251881Speter */ 1613251881Speterstatic svn_error_t * 1614251881Speterdo_copy_revprops(svn_ra_session_t *to_session, 1615251881Speter subcommand_baton_t *baton, apr_pool_t *pool) 1616251881Speter{ 1617251881Speter svn_ra_session_t *from_session; 1618251881Speter svn_string_t *last_merged_rev; 1619251881Speter svn_revnum_t i; 1620251881Speter svn_revnum_t step = 1; 1621251881Speter int normalized_rev_props_count = 0; 1622251881Speter 1623251881Speter SVN_ERR(open_source_session(&from_session, &last_merged_rev, 1624251881Speter baton->from_url, to_session, 1625251881Speter &(baton->source_callbacks), baton->config, 1626251881Speter baton, pool)); 1627251881Speter 1628251881Speter /* An invalid revision means "last-synced" */ 1629251881Speter if (! SVN_IS_VALID_REVNUM(baton->start_rev)) 1630251881Speter baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data); 1631251881Speter if (! SVN_IS_VALID_REVNUM(baton->end_rev)) 1632251881Speter baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data); 1633251881Speter 1634251881Speter /* Make sure we have revisions within the valid range. */ 1635251881Speter if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data)) 1636251881Speter return svn_error_createf 1637251881Speter (APR_EINVAL, NULL, 1638251881Speter _("Cannot copy revprops for a revision (%ld) that has not " 1639251881Speter "been synchronized yet"), baton->start_rev); 1640251881Speter if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data)) 1641251881Speter return svn_error_createf 1642251881Speter (APR_EINVAL, NULL, 1643251881Speter _("Cannot copy revprops for a revision (%ld) that has not " 1644251881Speter "been synchronized yet"), baton->end_rev); 1645251881Speter 1646251881Speter /* Now, copy all the requested revisions, in the requested order. */ 1647251881Speter step = (baton->start_rev > baton->end_rev) ? -1 : 1; 1648251881Speter for (i = baton->start_rev; i != baton->end_rev + step; i = i + step) 1649251881Speter { 1650251881Speter int normalized_count; 1651251881Speter SVN_ERR(check_cancel(NULL)); 1652251881Speter SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet, 1653251881Speter baton->source_prop_encoding, &normalized_count, 1654251881Speter pool)); 1655251881Speter normalized_rev_props_count += normalized_count; 1656251881Speter } 1657251881Speter 1658251881Speter /* Notify about normalized props, if any. */ 1659251881Speter SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool)); 1660251881Speter 1661251881Speter return SVN_NO_ERROR; 1662251881Speter} 1663251881Speter 1664251881Speter 1665251881Speter/* Set *START_REVNUM to the revision number associated with 1666251881Speter START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION 1667251881Speter represents "HEAD"; if END_REVISION is specified, set END_REVNUM to 1668251881Speter the revision number associated with END_REVISION or to 1669251881Speter SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set 1670251881Speter END_REVNUM to the same value as START_REVNUM. 1671251881Speter 1672251881Speter As a special case, if neither START_REVISION nor END_REVISION is 1673251881Speter specified, set *START_REVNUM to 0 and set *END_REVNUM to 1674251881Speter SVN_INVALID_REVNUM. 1675251881Speter 1676251881Speter Freak out if either START_REVISION or END_REVISION represents an 1677251881Speter explicit but invalid revision number. */ 1678251881Speterstatic svn_error_t * 1679251881Speterresolve_revnums(svn_revnum_t *start_revnum, 1680251881Speter svn_revnum_t *end_revnum, 1681251881Speter svn_opt_revision_t start_revision, 1682251881Speter svn_opt_revision_t end_revision) 1683251881Speter{ 1684251881Speter svn_revnum_t start_rev, end_rev; 1685251881Speter 1686251881Speter /* Special case: neither revision is specified? This is like 1687251881Speter -r0:HEAD. */ 1688251881Speter if ((start_revision.kind == svn_opt_revision_unspecified) && 1689251881Speter (end_revision.kind == svn_opt_revision_unspecified)) 1690251881Speter { 1691251881Speter *start_revnum = 0; 1692251881Speter *end_revnum = SVN_INVALID_REVNUM; 1693251881Speter return SVN_NO_ERROR; 1694251881Speter } 1695251881Speter 1696251881Speter /* Get the start revision, which must be either HEAD or a number 1697251881Speter (which is required to be a valid one). */ 1698251881Speter if (start_revision.kind == svn_opt_revision_head) 1699251881Speter { 1700251881Speter start_rev = SVN_INVALID_REVNUM; 1701251881Speter } 1702251881Speter else 1703251881Speter { 1704251881Speter start_rev = start_revision.value.number; 1705251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1706251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1707251881Speter _("Invalid revision number (%ld)"), 1708251881Speter start_rev); 1709251881Speter } 1710251881Speter 1711251881Speter /* Get the end revision, which must be unspecified (meaning, 1712251881Speter "same as the start_rev"), HEAD, or a number (which is 1713251881Speter required to be a valid one). */ 1714251881Speter if (end_revision.kind == svn_opt_revision_unspecified) 1715251881Speter { 1716251881Speter end_rev = start_rev; 1717251881Speter } 1718251881Speter else if (end_revision.kind == svn_opt_revision_head) 1719251881Speter { 1720251881Speter end_rev = SVN_INVALID_REVNUM; 1721251881Speter } 1722251881Speter else 1723251881Speter { 1724251881Speter end_rev = end_revision.value.number; 1725251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1726251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1727251881Speter _("Invalid revision number (%ld)"), 1728251881Speter end_rev); 1729251881Speter } 1730251881Speter 1731251881Speter *start_revnum = start_rev; 1732251881Speter *end_revnum = end_rev; 1733251881Speter return SVN_NO_ERROR; 1734251881Speter} 1735251881Speter 1736251881Speter 1737251881Speter/* SUBCOMMAND: copy-revprops */ 1738251881Speterstatic svn_error_t * 1739251881Spetercopy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) 1740251881Speter{ 1741251881Speter svn_ra_session_t *to_session; 1742251881Speter opt_baton_t *opt_baton = b; 1743251881Speter apr_array_header_t *targets; 1744251881Speter subcommand_baton_t *baton; 1745251881Speter const char *to_url = NULL; 1746251881Speter const char *from_url = NULL; 1747251881Speter svn_opt_revision_t start_revision, end_revision; 1748251881Speter svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM; 1749251881Speter 1750251881Speter /* There should be either one or two arguments left to parse. */ 1751251881Speter if (os->argc - os->ind > 2) 1752251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1753251881Speter if (os->argc - os->ind < 1) 1754251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1755251881Speter 1756251881Speter /* If there are two args, the last one is either a revision range or 1757251881Speter the source URL. */ 1758251881Speter if (os->argc - os->ind == 2) 1759251881Speter { 1760251881Speter const char *arg_str = os->argv[os->argc - 1]; 1761251881Speter const char *utf_arg_str; 1762251881Speter 1763251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool)); 1764251881Speter 1765251881Speter if (! svn_path_is_url(utf_arg_str)) 1766251881Speter { 1767251881Speter /* This is the old "... TO_URL REV[:REV2]" syntax. 1768251881Speter Revisions come only from this argument. (We effectively 1769251881Speter pop that last argument from the end of the argument list 1770251881Speter so svn_opt__args_to_target_array() can do its thang.) */ 1771251881Speter os->argc--; 1772251881Speter 1773251881Speter if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified) 1774251881Speter || (opt_baton->end_rev.kind != svn_opt_revision_unspecified)) 1775251881Speter return svn_error_create( 1776251881Speter SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1777251881Speter _("Cannot specify revisions via both command-line arguments " 1778251881Speter "and the --revision (-r) option")); 1779251881Speter 1780251881Speter start_revision.kind = svn_opt_revision_unspecified; 1781251881Speter end_revision.kind = svn_opt_revision_unspecified; 1782251881Speter if (svn_opt_parse_revision(&start_revision, &end_revision, 1783251881Speter arg_str, pool) != 0) 1784251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1785251881Speter _("Invalid revision range '%s' provided"), 1786251881Speter arg_str); 1787251881Speter 1788251881Speter SVN_ERR(resolve_revnums(&start_rev, &end_rev, 1789251881Speter start_revision, end_revision)); 1790251881Speter 1791251881Speter SVN_ERR(svn_opt__args_to_target_array( 1792251881Speter &targets, os, 1793251881Speter apr_array_make(pool, 1, sizeof(const char *)), pool)); 1794251881Speter if (targets->nelts != 1) 1795251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1796251881Speter to_url = APR_ARRAY_IDX(targets, 0, const char *); 1797251881Speter from_url = NULL; 1798251881Speter } 1799251881Speter } 1800251881Speter 1801251881Speter if (! to_url) 1802251881Speter { 1803251881Speter /* This is the "... TO_URL SOURCE_URL" syntax. Revisions 1804251881Speter come only from the --revision parameter. */ 1805251881Speter SVN_ERR(resolve_revnums(&start_rev, &end_rev, 1806251881Speter opt_baton->start_rev, opt_baton->end_rev)); 1807251881Speter 1808251881Speter SVN_ERR(svn_opt__args_to_target_array( 1809251881Speter &targets, os, 1810251881Speter apr_array_make(pool, 2, sizeof(const char *)), pool)); 1811251881Speter if (targets->nelts < 1) 1812251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1813251881Speter if (targets->nelts > 2) 1814251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1815251881Speter to_url = APR_ARRAY_IDX(targets, 0, const char *); 1816251881Speter if (targets->nelts == 2) 1817251881Speter from_url = APR_ARRAY_IDX(targets, 1, const char *); 1818251881Speter else 1819251881Speter from_url = NULL; 1820251881Speter } 1821251881Speter 1822251881Speter if (! svn_path_is_url(to_url)) 1823251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1824251881Speter _("Path '%s' is not a URL"), to_url); 1825251881Speter if (from_url && (! svn_path_is_url(from_url))) 1826251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1827251881Speter _("Path '%s' is not a URL"), from_url); 1828251881Speter 1829251881Speter baton = make_subcommand_baton(opt_baton, to_url, from_url, 1830251881Speter start_rev, end_rev, pool); 1831251881Speter SVN_ERR(open_target_session(&to_session, baton, pool)); 1832251881Speter if (opt_baton->disable_locking) 1833251881Speter SVN_ERR(do_copy_revprops(to_session, baton, pool)); 1834251881Speter else 1835251881Speter SVN_ERR(with_locked(to_session, do_copy_revprops, baton, 1836251881Speter opt_baton->steal_lock, pool)); 1837251881Speter 1838251881Speter return SVN_NO_ERROR; 1839251881Speter} 1840251881Speter 1841251881Speter 1842251881Speter 1843251881Speter/*** `svnsync info' ***/ 1844251881Speter 1845251881Speter 1846251881Speter/* SUBCOMMAND: info */ 1847251881Speterstatic svn_error_t * 1848251881Speterinfo_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool) 1849251881Speter{ 1850251881Speter svn_ra_session_t *to_session; 1851251881Speter opt_baton_t *opt_baton = b; 1852251881Speter apr_array_header_t *targets; 1853251881Speter subcommand_baton_t *baton; 1854251881Speter const char *to_url; 1855251881Speter apr_hash_t *props; 1856251881Speter svn_string_t *from_url, *from_uuid, *last_merged_rev; 1857251881Speter 1858251881Speter SVN_ERR(svn_opt__args_to_target_array(&targets, os, 1859251881Speter apr_array_make(pool, 0, 1860251881Speter sizeof(const char *)), 1861251881Speter pool)); 1862251881Speter if (targets->nelts < 1) 1863251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 1864251881Speter if (targets->nelts > 1) 1865251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 1866251881Speter 1867251881Speter /* Get the mirror repository URL, and verify that it is URL-ish. */ 1868251881Speter to_url = APR_ARRAY_IDX(targets, 0, const char *); 1869251881Speter if (! svn_path_is_url(to_url)) 1870251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1871251881Speter _("Path '%s' is not a URL"), to_url); 1872251881Speter 1873251881Speter /* Open an RA session to the mirror repository URL. */ 1874251881Speter baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool); 1875251881Speter SVN_ERR(open_target_session(&to_session, baton, pool)); 1876251881Speter 1877251881Speter SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool)); 1878251881Speter 1879251881Speter from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL); 1880251881Speter 1881251881Speter if (! from_url) 1882251881Speter return svn_error_createf 1883251881Speter (SVN_ERR_BAD_URL, NULL, 1884251881Speter _("Repository '%s' is not initialized for synchronization"), to_url); 1885251881Speter 1886251881Speter from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID); 1887251881Speter last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV); 1888251881Speter 1889251881Speter /* Print the info. */ 1890251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data)); 1891251881Speter if (from_uuid) 1892251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"), 1893251881Speter from_uuid->data)); 1894251881Speter if (last_merged_rev) 1895251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"), 1896251881Speter last_merged_rev->data)); 1897251881Speter return SVN_NO_ERROR; 1898251881Speter} 1899251881Speter 1900251881Speter 1901251881Speter 1902251881Speter/*** `svnsync help' ***/ 1903251881Speter 1904251881Speter 1905251881Speter/* SUBCOMMAND: help */ 1906251881Speterstatic svn_error_t * 1907251881Speterhelp_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1908251881Speter{ 1909251881Speter opt_baton_t *opt_baton = baton; 1910251881Speter 1911251881Speter const char *header = 1912251881Speter _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n" 1913299742Sdim "Subversion repository replication tool.\n" 1914251881Speter "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n" 1915251881Speter "Type 'svnsync --version' to see the program version and RA modules.\n" 1916251881Speter "\n" 1917251881Speter "Available subcommands:\n"); 1918251881Speter 1919251881Speter const char *ra_desc_start 1920251881Speter = _("The following repository access (RA) modules are available:\n\n"); 1921251881Speter 1922251881Speter svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start, 1923251881Speter pool); 1924251881Speter 1925251881Speter SVN_ERR(svn_ra_print_modules(version_footer, pool)); 1926251881Speter 1927251881Speter SVN_ERR(svn_opt_print_help4(os, "svnsync", 1928251881Speter opt_baton ? opt_baton->version : FALSE, 1929251881Speter opt_baton ? opt_baton->quiet : FALSE, 1930251881Speter /*###opt_state ? opt_state->verbose :*/ FALSE, 1931251881Speter version_footer->data, header, 1932251881Speter svnsync_cmd_table, svnsync_options, NULL, 1933251881Speter NULL, pool)); 1934251881Speter 1935251881Speter return SVN_NO_ERROR; 1936251881Speter} 1937251881Speter 1938251881Speter 1939251881Speter 1940251881Speter/*** Main ***/ 1941251881Speter 1942299742Sdim/* 1943299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 1944299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and 1945299742Sdim * return SVN_NO_ERROR. 1946299742Sdim */ 1947299742Sdimstatic svn_error_t * 1948299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 1949251881Speter{ 1950251881Speter const svn_opt_subcommand_desc2_t *subcommand = NULL; 1951251881Speter apr_array_header_t *received_opts; 1952251881Speter opt_baton_t opt_baton; 1953251881Speter svn_config_t *config; 1954251881Speter apr_status_t apr_err; 1955251881Speter apr_getopt_t *os; 1956251881Speter svn_error_t *err; 1957251881Speter int opt_id, i; 1958251881Speter const char *username = NULL, *source_username = NULL, *sync_username = NULL; 1959251881Speter const char *password = NULL, *source_password = NULL, *sync_password = NULL; 1960251881Speter apr_array_header_t *config_options = NULL; 1961251881Speter const char *source_prop_encoding = NULL; 1962251881Speter svn_boolean_t force_interactive = FALSE; 1963251881Speter 1964299742Sdim /* Check library versions */ 1965299742Sdim SVN_ERR(check_lib_versions()); 1966251881Speter 1967299742Sdim SVN_ERR(svn_ra_initialize(pool)); 1968251881Speter 1969251881Speter /* Initialize the option baton. */ 1970251881Speter memset(&opt_baton, 0, sizeof(opt_baton)); 1971251881Speter opt_baton.start_rev.kind = svn_opt_revision_unspecified; 1972251881Speter opt_baton.end_rev.kind = svn_opt_revision_unspecified; 1973251881Speter 1974251881Speter received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 1975251881Speter 1976251881Speter if (argc <= 1) 1977251881Speter { 1978299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 1979299742Sdim *exit_code = EXIT_FAILURE; 1980299742Sdim return SVN_NO_ERROR; 1981251881Speter } 1982251881Speter 1983299742Sdim SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 1984251881Speter 1985251881Speter os->interleave = 1; 1986251881Speter 1987251881Speter for (;;) 1988251881Speter { 1989251881Speter const char *opt_arg; 1990251881Speter svn_error_t* opt_err = NULL; 1991251881Speter 1992251881Speter apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg); 1993251881Speter if (APR_STATUS_IS_EOF(apr_err)) 1994251881Speter break; 1995251881Speter else if (apr_err) 1996251881Speter { 1997299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 1998299742Sdim *exit_code = EXIT_FAILURE; 1999299742Sdim return SVN_NO_ERROR; 2000251881Speter } 2001251881Speter 2002251881Speter APR_ARRAY_PUSH(received_opts, int) = opt_id; 2003251881Speter 2004251881Speter switch (opt_id) 2005251881Speter { 2006251881Speter case svnsync_opt_non_interactive: 2007251881Speter opt_baton.non_interactive = TRUE; 2008251881Speter break; 2009251881Speter 2010251881Speter case svnsync_opt_force_interactive: 2011251881Speter force_interactive = TRUE; 2012251881Speter break; 2013251881Speter 2014299742Sdim case svnsync_opt_trust_server_cert: /* backwards compat */ 2015299742Sdim opt_baton.src_trust.trust_server_cert_unknown_ca = TRUE; 2016299742Sdim opt_baton.dst_trust.trust_server_cert_unknown_ca = TRUE; 2017251881Speter break; 2018251881Speter 2019299742Sdim case svnsync_opt_trust_server_cert_failures_src: 2020299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 2021299742Sdim SVN_ERR(svn_cmdline__parse_trust_options( 2022299742Sdim &opt_baton.src_trust.trust_server_cert_unknown_ca, 2023299742Sdim &opt_baton.src_trust.trust_server_cert_cn_mismatch, 2024299742Sdim &opt_baton.src_trust.trust_server_cert_expired, 2025299742Sdim &opt_baton.src_trust.trust_server_cert_not_yet_valid, 2026299742Sdim &opt_baton.src_trust.trust_server_cert_other_failure, 2027299742Sdim opt_arg, pool)); 2028299742Sdim break; 2029299742Sdim 2030299742Sdim case svnsync_opt_trust_server_cert_failures_dst: 2031299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 2032299742Sdim SVN_ERR(svn_cmdline__parse_trust_options( 2033299742Sdim &opt_baton.dst_trust.trust_server_cert_unknown_ca, 2034299742Sdim &opt_baton.dst_trust.trust_server_cert_cn_mismatch, 2035299742Sdim &opt_baton.dst_trust.trust_server_cert_expired, 2036299742Sdim &opt_baton.dst_trust.trust_server_cert_not_yet_valid, 2037299742Sdim &opt_baton.dst_trust.trust_server_cert_other_failure, 2038299742Sdim opt_arg, pool)); 2039299742Sdim break; 2040299742Sdim 2041251881Speter case svnsync_opt_no_auth_cache: 2042251881Speter opt_baton.no_auth_cache = TRUE; 2043251881Speter break; 2044251881Speter 2045251881Speter case svnsync_opt_auth_username: 2046251881Speter opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool); 2047251881Speter break; 2048251881Speter 2049251881Speter case svnsync_opt_auth_password: 2050251881Speter opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool); 2051251881Speter break; 2052251881Speter 2053251881Speter case svnsync_opt_source_username: 2054251881Speter opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool); 2055251881Speter break; 2056251881Speter 2057251881Speter case svnsync_opt_source_password: 2058251881Speter opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool); 2059251881Speter break; 2060251881Speter 2061251881Speter case svnsync_opt_sync_username: 2062251881Speter opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool); 2063251881Speter break; 2064251881Speter 2065251881Speter case svnsync_opt_sync_password: 2066251881Speter opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool); 2067251881Speter break; 2068251881Speter 2069251881Speter case svnsync_opt_config_dir: 2070251881Speter { 2071251881Speter const char *path_utf8; 2072251881Speter opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool); 2073251881Speter 2074251881Speter if (!opt_err) 2075251881Speter opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool); 2076251881Speter } 2077251881Speter break; 2078251881Speter case svnsync_opt_config_options: 2079251881Speter if (!config_options) 2080251881Speter config_options = 2081251881Speter apr_array_make(pool, 1, 2082251881Speter sizeof(svn_cmdline__config_argument_t*)); 2083251881Speter 2084299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 2085299742Sdim SVN_ERR(svn_cmdline__parse_config_option(config_options, 2086299742Sdim opt_arg, "svnsync: ", 2087299742Sdim pool)); 2088251881Speter break; 2089251881Speter 2090251881Speter case svnsync_opt_source_prop_encoding: 2091251881Speter opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg, 2092251881Speter pool); 2093251881Speter break; 2094251881Speter 2095251881Speter case svnsync_opt_disable_locking: 2096251881Speter opt_baton.disable_locking = TRUE; 2097251881Speter break; 2098251881Speter 2099251881Speter case svnsync_opt_steal_lock: 2100251881Speter opt_baton.steal_lock = TRUE; 2101251881Speter break; 2102251881Speter 2103251881Speter case svnsync_opt_version: 2104251881Speter opt_baton.version = TRUE; 2105251881Speter break; 2106251881Speter 2107251881Speter case svnsync_opt_allow_non_empty: 2108251881Speter opt_baton.allow_non_empty = TRUE; 2109251881Speter break; 2110251881Speter 2111251881Speter case 'q': 2112251881Speter opt_baton.quiet = TRUE; 2113251881Speter break; 2114251881Speter 2115251881Speter case 'r': 2116251881Speter if (svn_opt_parse_revision(&opt_baton.start_rev, 2117251881Speter &opt_baton.end_rev, 2118251881Speter opt_arg, pool) != 0) 2119251881Speter { 2120251881Speter const char *utf8_opt_arg; 2121299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); 2122299742Sdim return svn_error_createf( 2123251881Speter SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2124251881Speter _("Syntax error in revision argument '%s'"), 2125251881Speter utf8_opt_arg); 2126251881Speter } 2127251881Speter 2128251881Speter /* We only allow numbers and 'HEAD'. */ 2129251881Speter if (((opt_baton.start_rev.kind != svn_opt_revision_number) && 2130251881Speter (opt_baton.start_rev.kind != svn_opt_revision_head)) 2131251881Speter || ((opt_baton.end_rev.kind != svn_opt_revision_number) && 2132251881Speter (opt_baton.end_rev.kind != svn_opt_revision_head) && 2133251881Speter (opt_baton.end_rev.kind != svn_opt_revision_unspecified))) 2134251881Speter { 2135299742Sdim return svn_error_createf( 2136251881Speter SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2137251881Speter _("Invalid revision range '%s' provided"), opt_arg); 2138251881Speter } 2139251881Speter break; 2140251881Speter 2141299742Sdim case 'M': 2142299742Sdim if (!config_options) 2143299742Sdim config_options = 2144299742Sdim apr_array_make(pool, 1, 2145299742Sdim sizeof(svn_cmdline__config_argument_t*)); 2146299742Sdim 2147299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 2148299742Sdim SVN_ERR(svn_cmdline__parse_config_option( 2149299742Sdim config_options, 2150299742Sdim apr_psprintf(pool, 2151299742Sdim "config:miscellany:memory-cache-size=%s", 2152299742Sdim opt_arg), 2153299742Sdim NULL /* won't be used */, 2154299742Sdim pool)); 2155299742Sdim break; 2156299742Sdim 2157251881Speter case '?': 2158251881Speter case 'h': 2159251881Speter opt_baton.help = TRUE; 2160251881Speter break; 2161251881Speter 2162251881Speter default: 2163251881Speter { 2164299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 2165299742Sdim *exit_code = EXIT_FAILURE; 2166299742Sdim return SVN_NO_ERROR; 2167251881Speter } 2168251881Speter } 2169251881Speter 2170299742Sdim if (opt_err) 2171299742Sdim return opt_err; 2172251881Speter } 2173251881Speter 2174251881Speter if (opt_baton.help) 2175251881Speter subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help"); 2176251881Speter 2177251881Speter /* The --non-interactive and --force-interactive options are mutually 2178251881Speter * exclusive. */ 2179251881Speter if (opt_baton.non_interactive && force_interactive) 2180251881Speter { 2181299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2182299742Sdim _("--non-interactive and --force-interactive " 2183299742Sdim "are mutually exclusive")); 2184251881Speter } 2185251881Speter else 2186251881Speter opt_baton.non_interactive = !svn_cmdline__be_interactive( 2187251881Speter opt_baton.non_interactive, 2188251881Speter force_interactive); 2189251881Speter 2190251881Speter /* Disallow the mixing --username/password with their --source- and 2191251881Speter --sync- variants. Treat "--username FOO" as "--source-username 2192251881Speter FOO --sync-username FOO"; ditto for "--password FOO". */ 2193251881Speter if ((username || password) 2194251881Speter && (source_username || sync_username 2195251881Speter || source_password || sync_password)) 2196251881Speter { 2197299742Sdim return svn_error_create 2198251881Speter (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2199251881Speter _("Cannot use --username or --password with any of " 2200251881Speter "--source-username, --source-password, --sync-username, " 2201251881Speter "or --sync-password.\n")); 2202251881Speter } 2203251881Speter if (username) 2204251881Speter { 2205251881Speter source_username = username; 2206251881Speter sync_username = username; 2207251881Speter } 2208251881Speter if (password) 2209251881Speter { 2210251881Speter source_password = password; 2211251881Speter sync_password = password; 2212251881Speter } 2213251881Speter opt_baton.source_username = source_username; 2214251881Speter opt_baton.source_password = source_password; 2215251881Speter opt_baton.sync_username = sync_username; 2216251881Speter opt_baton.sync_password = sync_password; 2217251881Speter 2218251881Speter /* Disallow mixing of --steal-lock and --disable-locking. */ 2219251881Speter if (opt_baton.steal_lock && opt_baton.disable_locking) 2220251881Speter { 2221299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2222299742Sdim _("--disable-locking and --steal-lock are " 2223299742Sdim "mutually exclusive")); 2224251881Speter } 2225251881Speter 2226299742Sdim /* --trust-* can only be used with --non-interactive */ 2227299742Sdim if (!opt_baton.non_interactive) 2228251881Speter { 2229299742Sdim if (opt_baton.src_trust.trust_server_cert_unknown_ca 2230299742Sdim || opt_baton.src_trust.trust_server_cert_cn_mismatch 2231299742Sdim || opt_baton.src_trust.trust_server_cert_expired 2232299742Sdim || opt_baton.src_trust.trust_server_cert_not_yet_valid 2233299742Sdim || opt_baton.src_trust.trust_server_cert_other_failure 2234299742Sdim || opt_baton.dst_trust.trust_server_cert_unknown_ca 2235299742Sdim || opt_baton.dst_trust.trust_server_cert_cn_mismatch 2236299742Sdim || opt_baton.dst_trust.trust_server_cert_expired 2237299742Sdim || opt_baton.dst_trust.trust_server_cert_not_yet_valid 2238299742Sdim || opt_baton.dst_trust.trust_server_cert_other_failure) 2239299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2240299742Sdim _("--source-trust-server-cert-failures " 2241299742Sdim "and " 2242299742Sdim "--sync-trust-server-cert-failures require " 2243299742Sdim "--non-interactive")); 2244251881Speter } 2245251881Speter 2246299742Sdim SVN_ERR(svn_config_ensure(opt_baton.config_dir, pool)); 2247251881Speter 2248251881Speter if (subcommand == NULL) 2249251881Speter { 2250251881Speter if (os->ind >= os->argc) 2251251881Speter { 2252251881Speter if (opt_baton.version) 2253251881Speter { 2254251881Speter /* Use the "help" subcommand to handle "--version". */ 2255251881Speter static const svn_opt_subcommand_desc2_t pseudo_cmd = 2256251881Speter { "--version", help_cmd, {0}, "", 2257251881Speter {svnsync_opt_version, /* must accept its own option */ 2258251881Speter 'q', /* --quiet */ 2259251881Speter } }; 2260251881Speter 2261251881Speter subcommand = &pseudo_cmd; 2262251881Speter } 2263251881Speter else 2264251881Speter { 2265299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 2266299742Sdim *exit_code = EXIT_FAILURE; 2267299742Sdim return SVN_NO_ERROR; 2268251881Speter } 2269251881Speter } 2270251881Speter else 2271251881Speter { 2272251881Speter const char *first_arg = os->argv[os->ind++]; 2273251881Speter subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, 2274251881Speter first_arg); 2275251881Speter if (subcommand == NULL) 2276251881Speter { 2277299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 2278299742Sdim *exit_code = EXIT_FAILURE; 2279299742Sdim return SVN_NO_ERROR; 2280251881Speter } 2281251881Speter } 2282251881Speter } 2283251881Speter 2284251881Speter for (i = 0; i < received_opts->nelts; ++i) 2285251881Speter { 2286251881Speter opt_id = APR_ARRAY_IDX(received_opts, i, int); 2287251881Speter 2288251881Speter if (opt_id == 'h' || opt_id == '?') 2289251881Speter continue; 2290251881Speter 2291251881Speter if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 2292251881Speter { 2293251881Speter const char *optstr; 2294251881Speter const apr_getopt_option_t *badopt = 2295251881Speter svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand, 2296251881Speter pool); 2297251881Speter svn_opt_format_option(&optstr, badopt, FALSE, pool); 2298251881Speter if (subcommand->name[0] == '-') 2299251881Speter { 2300299742Sdim SVN_ERR(help_cmd(NULL, NULL, pool)); 2301251881Speter } 2302251881Speter else 2303251881Speter { 2304299742Sdim return svn_error_createf 2305251881Speter (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2306251881Speter _("Subcommand '%s' doesn't accept option '%s'\n" 2307251881Speter "Type 'svnsync help %s' for usage.\n"), 2308251881Speter subcommand->name, optstr, subcommand->name); 2309251881Speter } 2310251881Speter } 2311251881Speter } 2312251881Speter 2313299742Sdim SVN_ERR(svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool)); 2314251881Speter 2315251881Speter /* Update the options in the config */ 2316251881Speter if (config_options) 2317251881Speter { 2318251881Speter svn_error_clear( 2319251881Speter svn_cmdline__apply_config_options(opt_baton.config, config_options, 2320251881Speter "svnsync: ", "--config-option")); 2321251881Speter } 2322251881Speter 2323251881Speter config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG); 2324251881Speter 2325251881Speter opt_baton.source_prop_encoding = source_prop_encoding; 2326251881Speter 2327251881Speter apr_signal(SIGINT, signal_handler); 2328251881Speter 2329251881Speter#ifdef SIGBREAK 2330251881Speter /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 2331251881Speter apr_signal(SIGBREAK, signal_handler); 2332251881Speter#endif 2333251881Speter 2334251881Speter#ifdef SIGHUP 2335251881Speter apr_signal(SIGHUP, signal_handler); 2336251881Speter#endif 2337251881Speter 2338251881Speter#ifdef SIGTERM 2339251881Speter apr_signal(SIGTERM, signal_handler); 2340251881Speter#endif 2341251881Speter 2342251881Speter#ifdef SIGPIPE 2343251881Speter /* Disable SIGPIPE generation for the platforms that have it. */ 2344251881Speter apr_signal(SIGPIPE, SIG_IGN); 2345251881Speter#endif 2346251881Speter 2347251881Speter#ifdef SIGXFSZ 2348251881Speter /* Disable SIGXFSZ generation for the platforms that have it, 2349251881Speter otherwise working with large files when compiled against an APR 2350251881Speter that doesn't have large file support will crash the program, 2351251881Speter which is uncool. */ 2352251881Speter apr_signal(SIGXFSZ, SIG_IGN); 2353251881Speter#endif 2354251881Speter 2355299742Sdim err = svn_cmdline_create_auth_baton2( 2356299742Sdim &opt_baton.source_auth_baton, 2357299742Sdim opt_baton.non_interactive, 2358299742Sdim opt_baton.source_username, 2359299742Sdim opt_baton.source_password, 2360299742Sdim opt_baton.config_dir, 2361299742Sdim opt_baton.no_auth_cache, 2362299742Sdim opt_baton.src_trust.trust_server_cert_unknown_ca, 2363299742Sdim opt_baton.src_trust.trust_server_cert_cn_mismatch, 2364299742Sdim opt_baton.src_trust.trust_server_cert_expired, 2365299742Sdim opt_baton.src_trust.trust_server_cert_not_yet_valid, 2366299742Sdim opt_baton.src_trust.trust_server_cert_other_failure, 2367299742Sdim config, 2368299742Sdim check_cancel, NULL, 2369299742Sdim pool); 2370251881Speter if (! err) 2371299742Sdim err = svn_cmdline_create_auth_baton2( 2372299742Sdim &opt_baton.sync_auth_baton, 2373299742Sdim opt_baton.non_interactive, 2374299742Sdim opt_baton.sync_username, 2375299742Sdim opt_baton.sync_password, 2376299742Sdim opt_baton.config_dir, 2377299742Sdim opt_baton.no_auth_cache, 2378299742Sdim opt_baton.dst_trust.trust_server_cert_unknown_ca, 2379299742Sdim opt_baton.dst_trust.trust_server_cert_cn_mismatch, 2380299742Sdim opt_baton.dst_trust.trust_server_cert_expired, 2381299742Sdim opt_baton.dst_trust.trust_server_cert_not_yet_valid, 2382299742Sdim opt_baton.dst_trust.trust_server_cert_other_failure, 2383299742Sdim config, 2384299742Sdim check_cancel, NULL, 2385299742Sdim pool); 2386251881Speter if (! err) 2387251881Speter err = (*subcommand->cmd_func)(os, &opt_baton, pool); 2388251881Speter if (err) 2389251881Speter { 2390251881Speter /* For argument-related problems, suggest using the 'help' 2391251881Speter subcommand. */ 2392251881Speter if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2393251881Speter || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2394251881Speter { 2395251881Speter err = svn_error_quick_wrap(err, 2396251881Speter _("Try 'svnsync help' for more info")); 2397251881Speter } 2398251881Speter 2399299742Sdim return err; 2400251881Speter } 2401251881Speter 2402299742Sdim return SVN_NO_ERROR; 2403299742Sdim} 2404299742Sdim 2405299742Sdimint 2406299742Sdimmain(int argc, const char *argv[]) 2407299742Sdim{ 2408299742Sdim apr_pool_t *pool; 2409299742Sdim int exit_code = EXIT_SUCCESS; 2410299742Sdim svn_error_t *err; 2411299742Sdim 2412299742Sdim /* Initialize the app. */ 2413299742Sdim if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS) 2414299742Sdim return EXIT_FAILURE; 2415299742Sdim 2416299742Sdim /* Create our top-level pool. Use a separate mutexless allocator, 2417299742Sdim * given this application is single threaded. 2418299742Sdim */ 2419299742Sdim pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 2420299742Sdim 2421299742Sdim err = sub_main(&exit_code, argc, argv, pool); 2422299742Sdim 2423299742Sdim /* Flush stdout and report if it fails. It would be flushed on exit anyway 2424299742Sdim but this makes sure that output is not silently lost if it fails. */ 2425299742Sdim err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 2426299742Sdim 2427299742Sdim if (err) 2428299742Sdim { 2429299742Sdim exit_code = EXIT_FAILURE; 2430299742Sdim svn_cmdline_handle_exit_error(err, NULL, "svnsync: "); 2431299742Sdim } 2432299742Sdim 2433251881Speter svn_pool_destroy(pool); 2434299742Sdim return exit_code; 2435251881Speter} 2436