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