2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
19 * ====================================================================
23 #include "svn_cmdline.h"
24 #include "svn_config.h"
25 #include "svn_pools.h"
26 #include "svn_delta.h"
27 #include "svn_dirent_uri.h"
29 #include "svn_props.h"
34 #include "svn_subst.h"
35 #include "svn_string.h"
36 #include "svn_version.h"
38 #include "private/svn_opt_private.h"
39 #include "private/svn_ra_private.h"
40 #include "private/svn_cmdline_private.h"
44 #include "svn_private_config.h"
46 #include <apr_signal.h>
49 static svn_opt_subcommand_t initialize_cmd,
56 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
57 svnsync_opt_force_interactive,
58 svnsync_opt_no_auth_cache,
59 svnsync_opt_auth_username,
60 svnsync_opt_auth_password,
61 svnsync_opt_source_username,
62 svnsync_opt_source_password,
63 svnsync_opt_sync_username,
64 svnsync_opt_sync_password,
65 svnsync_opt_config_dir,
66 svnsync_opt_config_options,
67 svnsync_opt_source_prop_encoding,
68 svnsync_opt_disable_locking,
70 svnsync_opt_trust_server_cert,
71 svnsync_opt_allow_non_empty,
72 svnsync_opt_steal_lock
75 #define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
76 svnsync_opt_force_interactive, \
77 svnsync_opt_no_auth_cache, \
78 svnsync_opt_auth_username, \
79 svnsync_opt_auth_password, \
80 svnsync_opt_trust_server_cert, \
81 svnsync_opt_source_username, \
82 svnsync_opt_source_password, \
83 svnsync_opt_sync_username, \
84 svnsync_opt_sync_password, \
85 svnsync_opt_config_dir, \
86 svnsync_opt_config_options
88 static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
90 { "initialize", initialize_cmd, { "init" },
91 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
93 "Initialize a destination repository for synchronization from\n"
94 "another repository.\n"
96 "If the source URL is not the root of a repository, only the\n"
97 "specified part of the repository will be synchronized.\n"
99 "The destination URL must point to the root of a repository which\n"
100 "has been configured to allow revision property changes. In\n"
101 "the general case, the destination repository must contain no\n"
102 "committed revisions. Use --allow-non-empty to override this\n"
103 "restriction, which will cause svnsync to assume that any revisions\n"
104 "already present in the destination repository perfectly mirror\n"
105 "their counterparts in the source repository. (This is useful\n"
106 "when initializing a copy of a repository as a mirror of that same\n"
107 "repository, for example.)\n"
109 "You should not commit to, or make revision property changes in,\n"
110 "the destination repository by any method other than 'svnsync'.\n"
111 "In other words, the destination repository should be a read-only\n"
112 "mirror of the source repository.\n"),
113 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
114 svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
115 svnsync_opt_steal_lock } },
116 { "synchronize", synchronize_cmd, { "sync" },
117 N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
119 "Transfer all pending revisions to the destination from the source\n"
120 "with which it was initialized.\n"
122 "If SOURCE_URL is provided, use that as the source repository URL,\n"
123 "ignoring what is recorded in the destination repository as the\n"
124 "source URL. Specifying SOURCE_URL is recommended in particular\n"
125 "if untrusted users/administrators may have write access to the\n"
126 "DEST_URL repository.\n"),
127 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
128 svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
129 { "copy-revprops", copy_revprops_cmd, { 0 },
132 " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
133 " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
135 "Copy the revision properties in a given range of revisions to the\n"
136 "destination from the source with which it was initialized. If the\n"
137 "revision range is not specified, it defaults to all revisions in\n"
138 "the DEST_URL repository. Note also that the 'HEAD' revision is the\n"
139 "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
141 "If SOURCE_URL is provided, use that as the source repository URL,\n"
142 "ignoring what is recorded in the destination repository as the\n"
143 "source URL. Specifying SOURCE_URL is recommended in particular\n"
144 "if untrusted users/administrators may have write access to the\n"
145 "DEST_URL repository.\n"
147 "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
148 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
149 svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
150 { "info", info_cmd, { 0 },
151 N_("usage: svnsync info DEST_URL\n"
153 "Print information about the synchronization destination repository\n"
154 "located at DEST_URL.\n"),
155 { SVNSYNC_OPTS_DEFAULT } },
156 { "help", help_cmd, { "?", "h" },
157 N_("usage: svnsync help [SUBCOMMAND...]\n"
159 "Describe the usage of this program or its subcommands.\n"),
161 { NULL, NULL, { 0 }, NULL, { 0 } }
164 static const apr_getopt_option_t svnsync_options[] =
167 N_("print as little as possible") },
169 N_("operate on revision ARG (or range ARG1:ARG2)\n"
171 "A revision argument can be one of:\n"
173 " NUMBER revision number\n"
175 " 'HEAD' latest in repository") },
176 {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
177 N_("allow a non-empty destination repository") },
178 {"non-interactive", svnsync_opt_non_interactive, 0,
179 N_("do no interactive prompting (default is to prompt\n"
181 "only if standard input is a terminal device)")},
182 {"force-interactive", svnsync_opt_force_interactive, 0,
183 N_("do interactive prompting even if standard input\n"
185 "is not a terminal device")},
186 {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
187 N_("do not cache authentication tokens") },
188 {"username", svnsync_opt_auth_username, 1,
189 N_("specify a username ARG (deprecated;\n"
191 "see --source-username and --sync-username)") },
192 {"password", svnsync_opt_auth_password, 1,
193 N_("specify a password ARG (deprecated;\n"
195 "see --source-password and --sync-password)") },
196 {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
197 N_("accept SSL server certificates from unknown\n"
199 "certificate authorities without prompting (but only\n"
201 "with '--non-interactive')") },
202 {"source-username", svnsync_opt_source_username, 1,
203 N_("connect to source repository with username ARG") },
204 {"source-password", svnsync_opt_source_password, 1,
205 N_("connect to source repository with password ARG") },
206 {"sync-username", svnsync_opt_sync_username, 1,
207 N_("connect to sync repository with username ARG") },
208 {"sync-password", svnsync_opt_sync_password, 1,
209 N_("connect to sync repository with password ARG") },
210 {"config-dir", svnsync_opt_config_dir, 1,
211 N_("read user configuration files from directory ARG")},
212 {"config-option", svnsync_opt_config_options, 1,
213 N_("set user configuration option in the format:\n"
215 " FILE:SECTION:OPTION=[VALUE]\n"
219 " servers:global:http-library=serf")},
220 {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
221 N_("convert translatable properties from encoding ARG\n"
223 "to UTF-8. If not specified, then properties are\n"
225 "presumed to be encoded in UTF-8.")},
226 {"disable-locking", svnsync_opt_disable_locking, 0,
227 N_("Disable built-in locking. Use of this option can\n"
229 "corrupt the mirror unless you ensure that no other\n"
231 "instance of svnsync is running concurrently.")},
232 {"steal-lock", svnsync_opt_steal_lock, 0,
233 N_("Steal locks as necessary. Use, with caution,\n"
235 "if your mirror repository contains stale locks\n"
237 "and is not being concurrently accessed by another\n"
239 "svnsync instance.")},
240 {"version", svnsync_opt_version, 0,
241 N_("show program version information")},
243 N_("show help on a subcommand")},
245 N_("show help on a subcommand")},
249 typedef struct opt_baton_t {
250 svn_boolean_t non_interactive;
251 svn_boolean_t trust_server_cert;
252 svn_boolean_t no_auth_cache;
253 svn_auth_baton_t *source_auth_baton;
254 svn_auth_baton_t *sync_auth_baton;
255 const char *source_username;
256 const char *source_password;
257 const char *sync_username;
258 const char *sync_password;
259 const char *config_dir;
261 const char *source_prop_encoding;
262 svn_boolean_t disable_locking;
263 svn_boolean_t steal_lock;
265 svn_boolean_t allow_non_empty;
266 svn_boolean_t version;
268 svn_opt_revision_t start_rev;
269 svn_opt_revision_t end_rev;
275 /*** Helper functions ***/
278 /* Global record of whether the user has requested cancellation. */
279 static volatile sig_atomic_t cancelled = FALSE;
282 /* Callback function for apr_signal(). */
284 signal_handler(int signum)
286 apr_signal(signum, SIG_IGN);
291 /* Cancellation callback function. */
293 check_cancel(void *baton)
296 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
302 /* Check that the version of libraries in use match what we expect. */
304 check_lib_versions(void)
306 static const svn_version_checklist_t checklist[] =
308 { "svn_subr", svn_subr_version },
309 { "svn_delta", svn_delta_version },
310 { "svn_ra", svn_ra_version },
313 SVN_VERSION_DEFINE(my_version);
315 return svn_ver_check_list(&my_version, checklist);
319 /* Implements `svn_ra__lock_retry_func_t'. */
321 lock_retry_func(void *baton,
322 const svn_string_t *reposlocktoken,
325 return svn_cmdline_printf(pool,
326 _("Failed to get lock on destination "
327 "repos, currently held by '%s'\n"),
328 reposlocktoken->data);
331 /* Acquire a lock (of sorts) on the repository associated with the
332 * given RA SESSION. This lock is just a revprop change attempt in a
333 * time-delay loop. This function is duplicated by svnrdump in
334 * svnrdump/load_editor.c
337 get_lock(const svn_string_t **lock_string_p,
338 svn_ra_session_t *session,
339 svn_boolean_t steal_lock,
343 svn_boolean_t be_atomic;
344 const svn_string_t *stolen_lock;
346 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
347 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
351 /* Pre-1.7 server. Can't lock without a race condition.
354 err = svn_error_create(
355 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
356 _("Target server does not support atomic revision property "
357 "edits; consider upgrading it to 1.7 or using an external "
359 svn_handle_warning2(stderr, err, "svnsync: ");
360 svn_error_clear(err);
363 err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
364 SVNSYNC_PROP_LOCK, steal_lock,
365 10 /* retries */, lock_retry_func, NULL,
366 check_cancel, NULL, pool);
367 if (!err && stolen_lock)
369 return svn_cmdline_printf(pool,
370 _("Stole lock previously held by '%s'\n"),
377 /* Baton for the various subcommands to share. */
378 typedef struct subcommand_baton_t {
379 /* common to all subcommands */
381 svn_ra_callbacks2_t source_callbacks;
382 svn_ra_callbacks2_t sync_callbacks;
384 svn_boolean_t allow_non_empty;
387 /* initialize, synchronize, and copy-revprops only */
388 const char *source_prop_encoding;
390 /* initialize only */
391 const char *from_url;
393 /* synchronize only */
394 svn_revnum_t committed_rev;
396 /* copy-revprops only */
397 svn_revnum_t start_rev;
398 svn_revnum_t end_rev;
400 } subcommand_baton_t;
402 typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
403 subcommand_baton_t *baton,
407 /* Lock the repository associated with RA SESSION, then execute the
408 * given FUNC/BATON pair while holding the lock. Finally, drop the
409 * lock once it finishes.
412 with_locked(svn_ra_session_t *session,
413 with_locked_func_t func,
414 subcommand_baton_t *baton,
415 svn_boolean_t steal_lock,
418 const svn_string_t *lock_string;
421 SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
423 err = func(session, baton, pool);
424 return svn_error_compose_create(err,
425 svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
430 /* Callback function for the RA session's open_tmp_file()
434 open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
436 return svn_io_open_unique_file3(fp, NULL, NULL,
437 svn_io_file_del_on_pool_cleanup,
442 /* Return SVN_NO_ERROR iff URL identifies the root directory of the
443 * repository associated with RA session SESS.
446 check_if_session_is_at_repos_root(svn_ra_session_t *sess,
450 const char *sess_root;
452 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
454 if (strcmp(url, sess_root) == 0)
457 return svn_error_createf
459 _("Session is rooted at '%s' but the repos root is '%s'"),
464 /* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
465 * revision REV of the repository associated with RA session SESSION.
467 * For REV zero, don't remove properties with the "svn:sync-" prefix.
469 * All allocations will be done in a subpool of POOL.
472 remove_props_not_in_source(svn_ra_session_t *session,
474 apr_hash_t *source_props,
475 apr_hash_t *target_props,
478 apr_pool_t *subpool = svn_pool_create(pool);
479 apr_hash_index_t *hi;
481 for (hi = apr_hash_first(pool, target_props);
483 hi = apr_hash_next(hi))
485 const char *propname = svn__apr_hash_index_key(hi);
487 svn_pool_clear(subpool);
489 if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
490 sizeof(SVNSYNC_PROP_PREFIX) - 1))
493 /* Delete property if the name can't be found in SOURCE_PROPS. */
494 if (! svn_hash_gets(source_props, propname))
495 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
499 svn_pool_destroy(subpool);
504 /* Filter callback function.
505 * Takes a property name KEY, and is expected to return TRUE if the property
506 * should be filtered out (ie. not be copied to the target list), or FALSE if
509 typedef svn_boolean_t (*filter_func_t)(const char *key);
511 /* Make a new set of properties, by copying those properties in PROPS for which
512 * the filter FILTER returns FALSE.
514 * The number of properties not copied will be stored in FILTERED_COUNT.
516 * The returned set of properties is allocated from POOL.
519 filter_props(int *filtered_count, apr_hash_t *props,
520 filter_func_t filter,
523 apr_hash_index_t *hi;
524 apr_hash_t *filtered = apr_hash_make(pool);
527 for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
529 const char *propname = svn__apr_hash_index_key(hi);
530 void *propval = svn__apr_hash_index_val(hi);
532 /* Copy all properties:
533 - not matching the exclude pattern if provided OR
534 - matching the include pattern if provided */
535 if (!filter || !filter(propname))
537 svn_hash_sets(filtered, propname, propval);
541 *filtered_count += 1;
549 /* Write the set of revision properties REV_PROPS to revision REV to the
550 * repository associated with RA session SESSION.
551 * Omit any properties whose names are in the svnsync property name space,
552 * and set *FILTERED_COUNT to the number of properties thus omitted.
553 * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
555 * All allocations will be done in a subpool of POOL.
558 write_revprops(int *filtered_count,
559 svn_ra_session_t *session,
561 apr_hash_t *rev_props,
564 apr_pool_t *subpool = svn_pool_create(pool);
565 apr_hash_index_t *hi;
569 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
571 const char *propname = svn__apr_hash_index_key(hi);
572 const svn_string_t *propval = svn__apr_hash_index_val(hi);
574 svn_pool_clear(subpool);
576 if (strncmp(propname, SVNSYNC_PROP_PREFIX,
577 sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
579 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
584 *filtered_count += 1;
588 svn_pool_destroy(subpool);
595 log_properties_copied(svn_boolean_t syncprops_found,
600 SVN_ERR(svn_cmdline_printf(pool,
601 _("Copied properties for revision %ld "
602 "(%s* properties skipped).\n"),
603 rev, SVNSYNC_PROP_PREFIX));
605 SVN_ERR(svn_cmdline_printf(pool,
606 _("Copied properties for revision %ld.\n"),
612 /* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
613 * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
614 * endings, if either of those numbers is non-zero. */
616 log_properties_normalized(int normalized_rev_props_count,
617 int normalized_node_props_count,
620 if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
621 SVN_ERR(svn_cmdline_printf(pool,
622 _("NOTE: Normalized %s* properties "
623 "to LF line endings (%d rev-props, "
624 "%d node-props).\n"),
626 normalized_rev_props_count,
627 normalized_node_props_count));
632 /* Copy all the revision properties, except for those that have the
633 * "svn:sync-" prefix, from revision REV of the repository associated
634 * with RA session FROM_SESSION, to the repository associated with RA
635 * session TO_SESSION.
637 * If SYNC is TRUE, then properties on the destination revision that
638 * do not exist on the source revision will be removed.
640 * If QUIET is FALSE, then log_properties_copied() is called to log that
641 * properties were copied for revision REV.
643 * Make sure the values of svn:* revision properties use only LF (\n)
644 * line ending style, correcting their values as necessary. The number
645 * of properties that were normalized is returned in *NORMALIZED_COUNT.
648 copy_revprops(svn_ra_session_t *from_session,
649 svn_ra_session_t *to_session,
653 const char *source_prop_encoding,
654 int *normalized_count,
657 apr_pool_t *subpool = svn_pool_create(pool);
658 apr_hash_t *existing_props, *rev_props;
659 int filtered_count = 0;
661 /* Get the list of revision properties on REV of TARGET. We're only interested
662 in the property names, but we'll get the values 'for free'. */
664 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
666 existing_props = NULL;
668 /* Get the list of revision properties on REV of SOURCE. */
669 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
671 /* If necessary, normalize encoding and line ending style and return the count
672 of EOL-normalized properties in int *NORMALIZED_COUNT. */
673 SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
674 source_prop_encoding, pool));
676 /* Copy all but the svn:svnsync properties. */
677 SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
679 /* Delete those properties that were in TARGET but not in SOURCE */
681 SVN_ERR(remove_props_not_in_source(to_session, rev,
682 rev_props, existing_props, pool));
685 SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
687 svn_pool_destroy(subpool);
693 /* Return a subcommand baton allocated from POOL and populated with
694 data from the provided parameters, which include the global
695 OPT_BATON options structure and a handful of other options. Not
696 all parameters are used in all subcommands -- see
697 subcommand_baton_t's definition for details. */
698 static subcommand_baton_t *
699 make_subcommand_baton(opt_baton_t *opt_baton,
701 const char *from_url,
702 svn_revnum_t start_rev,
703 svn_revnum_t end_rev,
706 subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
707 b->config = opt_baton->config;
708 b->source_callbacks.open_tmp_file = open_tmp_file;
709 b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
710 b->sync_callbacks.open_tmp_file = open_tmp_file;
711 b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
712 b->quiet = opt_baton->quiet;
713 b->allow_non_empty = opt_baton->allow_non_empty;
715 b->source_prop_encoding = opt_baton->source_prop_encoding;
716 b->from_url = from_url;
717 b->start_rev = start_rev;
718 b->end_rev = end_rev;
723 open_target_session(svn_ra_session_t **to_session_p,
724 subcommand_baton_t *baton,
728 /*** `svnsync init' ***/
730 /* Initialize the repository associated with RA session TO_SESSION,
731 * using information found in BATON, while the repository is
732 * locked. Implements `with_locked_func_t' interface.
735 do_initialize(svn_ra_session_t *to_session,
736 subcommand_baton_t *baton,
739 svn_ra_session_t *from_session;
740 svn_string_t *from_url;
741 svn_revnum_t latest, from_latest;
742 const char *uuid, *root_url;
743 int normalized_rev_props_count;
745 /* First, sanity check to see that we're copying into a brand new
746 repos. If we aren't, and we aren't being asked to forcibly
747 complete this initialization, that's a bad news. */
748 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
749 if ((latest != 0) && (! baton->allow_non_empty))
750 return svn_error_create
752 _("Destination repository already contains revision history; consider "
753 "using --allow-non-empty if the repository's revisions are known "
754 "to mirror their respective revisions in the source repository"));
756 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
758 if (from_url && (! baton->allow_non_empty))
759 return svn_error_createf
761 _("Destination repository is already synchronizing from '%s'"),
764 /* Now fill in our bookkeeping info in the dest repository. */
766 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
767 &(baton->source_callbacks), baton,
768 baton->config, pool));
769 SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
771 /* If we're doing a partial replay, we have to check first if the server
773 if (strcmp(root_url, baton->from_url) != 0)
775 svn_boolean_t server_supports_partial_replay;
776 svn_error_t *err = svn_ra_has_capability(from_session,
777 &server_supports_partial_replay,
778 SVN_RA_CAPABILITY_PARTIAL_REPLAY,
780 if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
781 return svn_error_trace(err);
783 if (err || !server_supports_partial_replay)
784 return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
788 /* If we're initializing a non-empty destination, we'll make sure
789 that it at least doesn't have more revisions than the source. */
792 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
793 if (from_latest < latest)
794 return svn_error_create
796 _("Destination repository has more revisions than source "
800 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
801 svn_string_create(baton->from_url, pool),
804 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
805 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
806 svn_string_create(uuid, pool), pool));
808 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
809 NULL, svn_string_createf(pool, "%ld", latest),
812 /* Copy all non-svnsync revprops from the LATEST rev in the source
813 repository into the destination, notifying about normalized
814 props, if any. When LATEST is 0, this serves the practical
815 purpose of initializing data that would otherwise be overlooked
816 by the sync process (which is going to begin with r1). When
817 LATEST is not 0, this really serves merely aesthetic and
818 informational purposes, keeping the output of this command
819 consistent while allowing folks to see what the latest revision is. */
820 SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
821 baton->source_prop_encoding, &normalized_rev_props_count,
824 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
826 /* TODO: It would be nice if we could set the dest repos UUID to be
827 equal to the UUID of the source repos, at least optionally. That
828 way people could check out/log/diff using a local fast mirror,
829 but switch --relocate to the actual final repository in order to
830 make changes... But at this time, the RA layer doesn't have a
831 way to set a UUID. */
837 /* SUBCOMMAND: init */
839 initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
841 const char *to_url, *from_url;
842 svn_ra_session_t *to_session;
843 opt_baton_t *opt_baton = b;
844 apr_array_header_t *targets;
845 subcommand_baton_t *baton;
847 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
848 apr_array_make(pool, 0,
849 sizeof(const char *)),
851 if (targets->nelts < 2)
852 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
853 if (targets->nelts > 2)
854 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
856 to_url = APR_ARRAY_IDX(targets, 0, const char *);
857 from_url = APR_ARRAY_IDX(targets, 1, const char *);
859 if (! svn_path_is_url(to_url))
860 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
861 _("Path '%s' is not a URL"), to_url);
862 if (! svn_path_is_url(from_url))
863 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
864 _("Path '%s' is not a URL"), from_url);
866 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
867 SVN_ERR(open_target_session(&to_session, baton, pool));
868 if (opt_baton->disable_locking)
869 SVN_ERR(do_initialize(to_session, baton, pool));
871 SVN_ERR(with_locked(to_session, do_initialize, baton,
872 opt_baton->steal_lock, pool));
879 /*** `svnsync sync' ***/
881 /* Implements `svn_commit_callback2_t' interface. */
883 commit_callback(const svn_commit_info_t *commit_info,
887 subcommand_baton_t *sb = baton;
891 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
892 commit_info->revision));
895 sb->committed_rev = commit_info->revision;
901 /* Set *FROM_SESSION to an RA session associated with the source
902 * repository of the synchronization. If FROM_URL is non-NULL, use it
903 * as the source repository URL; otherwise, determine the source
904 * repository URL by reading svn:sync- properties from the destination
905 * repository (associated with TO_SESSION). Set LAST_MERGED_REV to
906 * the value of the property which records the most recently
907 * synchronized revision.
909 * CALLBACKS is a vtable of RA callbacks to provide when creating
910 * *FROM_SESSION. CONFIG is a configuration hash.
913 open_source_session(svn_ra_session_t **from_session,
914 svn_string_t **last_merged_rev,
915 const char *from_url,
916 svn_ra_session_t *to_session,
917 svn_ra_callbacks2_t *callbacks,
923 svn_string_t *from_url_str, *from_uuid_str;
925 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
927 from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
928 from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
929 *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
931 if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
932 return svn_error_create
934 _("Destination repository has not been initialized"));
936 /* ### TODO: Should we validate that FROM_URL_STR->data matches any
937 provided FROM_URL here? */
939 from_url = from_url_str->data;
941 /* Open the session to copy the revision data. */
942 SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
943 callbacks, baton, config, pool));
948 /* Set *TARGET_SESSION_P to an RA session associated with the target
949 * repository of the synchronization.
952 open_target_session(svn_ra_session_t **target_session_p,
953 subcommand_baton_t *baton,
956 svn_ra_session_t *target_session;
957 SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
958 &(baton->sync_callbacks), baton, baton->config, pool));
959 SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
961 *target_session_p = target_session;
965 /* Replay baton, used during synchronization. */
966 typedef struct replay_baton_t {
967 svn_ra_session_t *from_session;
968 svn_ra_session_t *to_session;
969 /* Extra 'backdoor' session for fetching data *from* the target repo. */
970 svn_ra_session_t *extra_to_session;
971 svn_revnum_t current_revision;
972 subcommand_baton_t *sb;
973 svn_boolean_t has_commit_revprops_capability;
974 int normalized_rev_props_count;
975 int normalized_node_props_count;
979 /* Return a replay baton allocated from POOL and populated with
980 data from the provided parameters. */
982 make_replay_baton(replay_baton_t **baton_p,
983 svn_ra_session_t *from_session,
984 svn_ra_session_t *to_session,
985 subcommand_baton_t *sb, apr_pool_t *pool)
987 replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
988 rb->from_session = from_session;
989 rb->to_session = to_session;
992 SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
994 #ifdef ENABLE_EV2_SHIMS
995 /* Open up the extra baton. Only needed for Ev2 shims. */
996 SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
1000 return SVN_NO_ERROR;
1003 /* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
1004 * property. Implements filter_func_t. Use with filter_props() to filter out
1005 * svn:date and svn:author and svnsync properties.
1007 static svn_boolean_t
1008 filter_exclude_date_author_sync(const char *key)
1010 if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1012 else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1014 else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1015 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1021 /* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
1022 * property. Implements filter_func_t. Use with filter_props() to filter out
1023 * all properties except svn:date and svn:author and svnsync properties.
1025 static svn_boolean_t
1026 filter_include_date_author_sync(const char *key)
1028 return ! filter_exclude_date_author_sync(key);
1032 /* Return TRUE iff KEY is the name of the svn:log property.
1033 * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
1035 static svn_boolean_t
1036 filter_exclude_log(const char *key)
1038 if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1044 /* Return FALSE iff KEY is the name of the svn:log property.
1045 * Implements filter_func_t. Use with filter_props() to only include svn:log.
1047 static svn_boolean_t
1048 filter_include_log(const char *key)
1050 return ! filter_exclude_log(key);
1054 static svn_error_t *
1055 fetch_base_func(const char **filename,
1058 svn_revnum_t base_revision,
1059 apr_pool_t *result_pool,
1060 apr_pool_t *scratch_pool)
1062 struct replay_baton_t *rb = baton;
1063 svn_stream_t *fstream;
1066 if (svn_path_is_url(path))
1067 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1068 else if (path[0] == '/')
1071 if (! SVN_IS_VALID_REVNUM(base_revision))
1072 base_revision = rb->current_revision - 1;
1074 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1075 svn_io_file_del_on_pool_cleanup,
1076 result_pool, scratch_pool));
1078 err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
1079 fstream, NULL, NULL, scratch_pool);
1080 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1082 svn_error_clear(err);
1083 SVN_ERR(svn_stream_close(fstream));
1086 return SVN_NO_ERROR;
1089 return svn_error_trace(err);
1091 SVN_ERR(svn_stream_close(fstream));
1093 return SVN_NO_ERROR;
1096 static svn_error_t *
1097 fetch_props_func(apr_hash_t **props,
1100 svn_revnum_t base_revision,
1101 apr_pool_t *result_pool,
1102 apr_pool_t *scratch_pool)
1104 struct replay_baton_t *rb = baton;
1105 svn_node_kind_t node_kind;
1107 if (svn_path_is_url(path))
1108 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1109 else if (path[0] == '/')
1112 if (! SVN_IS_VALID_REVNUM(base_revision))
1113 base_revision = rb->current_revision - 1;
1115 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1116 &node_kind, scratch_pool));
1118 if (node_kind == svn_node_file)
1120 SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
1121 NULL, NULL, props, result_pool));
1123 else if (node_kind == svn_node_dir)
1125 apr_array_header_t *tmp_props;
1127 SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
1128 base_revision, 0 /* Dirent fields */,
1130 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1131 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1133 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1137 *props = apr_hash_make(result_pool);
1140 return SVN_NO_ERROR;
1143 static svn_error_t *
1144 fetch_kind_func(svn_node_kind_t *kind,
1147 svn_revnum_t base_revision,
1148 apr_pool_t *scratch_pool)
1150 struct replay_baton_t *rb = baton;
1152 if (svn_path_is_url(path))
1153 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1154 else if (path[0] == '/')
1157 if (! SVN_IS_VALID_REVNUM(base_revision))
1158 base_revision = rb->current_revision - 1;
1160 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1161 kind, scratch_pool));
1163 return SVN_NO_ERROR;
1167 static svn_delta_shim_callbacks_t *
1168 get_shim_callbacks(replay_baton_t *rb,
1169 apr_pool_t *result_pool)
1171 svn_delta_shim_callbacks_t *callbacks =
1172 svn_delta_shim_callbacks_default(result_pool);
1174 callbacks->fetch_props_func = fetch_props_func;
1175 callbacks->fetch_kind_func = fetch_kind_func;
1176 callbacks->fetch_base_func = fetch_base_func;
1177 callbacks->fetch_baton = rb;
1183 /* Callback function for svn_ra_replay_range, invoked when starting to parse
1186 static svn_error_t *
1187 replay_rev_started(svn_revnum_t revision,
1189 const svn_delta_editor_t **editor,
1191 apr_hash_t *rev_props,
1194 const svn_delta_editor_t *commit_editor;
1195 const svn_delta_editor_t *cancel_editor;
1196 const svn_delta_editor_t *sync_editor;
1200 replay_baton_t *rb = replay_baton;
1201 apr_hash_t *filtered;
1203 int normalized_count;
1205 /* We set this property so that if we error out for some reason
1206 we can later determine where we were in the process of
1207 merging a revision. If we had committed the change, but we
1208 hadn't finished copying the revprops we need to know that, so
1209 we can go back and finish the job before we move on.
1211 NOTE: We have to set this before we start the commit editor,
1212 because ra_svn doesn't let you change rev props during a
1214 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1215 SVNSYNC_PROP_CURRENTLY_COPYING,
1217 svn_string_createf(pool, "%ld", revision),
1220 /* The actual copy is just a replay hooked up to a commit. Include
1221 all the revision properties from the source repositories, except
1222 'svn:author' and 'svn:date', those are not guaranteed to get
1223 through the editor anyway.
1224 If we're syncing to an non-commit-revprops capable server, filter
1225 out all revprops except svn:log and add them later in
1226 revplay_rev_finished. */
1227 filtered = filter_props(&filtered_count, rev_props,
1228 (rb->has_commit_revprops_capability
1229 ? filter_exclude_date_author_sync
1230 : filter_include_log),
1233 /* svn_ra_get_commit_editor3 requires the log message to be
1234 set. It's possible that we didn't receive 'svn:log' here, so we
1235 have to set it to at least the empty string. If there's a svn:log
1236 property on this revision, we will write the actual value in the
1237 replay_rev_finished callback. */
1238 if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
1239 svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
1240 svn_string_create_empty(pool));
1242 /* If necessary, normalize encoding and line ending style. Add the number
1243 of properties that required EOL normalization to the overall count
1244 in the replay baton. */
1245 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1246 rb->sb->source_prop_encoding, pool));
1247 rb->normalized_rev_props_count += normalized_count;
1249 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
1250 get_shim_callbacks(rb, pool)));
1251 SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1254 commit_callback, rb->sb,
1255 NULL, FALSE, pool));
1257 /* There's one catch though, the diff shows us props we can't send
1258 over the RA interface, so we need an editor that's smart enough
1259 to filter those out for us. */
1260 SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
1261 rb->sb->to_url, rb->sb->source_prop_encoding,
1262 rb->sb->quiet, &sync_editor, &sync_baton,
1263 &(rb->normalized_node_props_count), pool));
1265 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1266 sync_editor, sync_baton,
1270 *editor = cancel_editor;
1271 *edit_baton = cancel_baton;
1273 rb->current_revision = revision;
1274 return SVN_NO_ERROR;
1277 /* Callback function for svn_ra_replay_range, invoked when finishing parsing
1280 static svn_error_t *
1281 replay_rev_finished(svn_revnum_t revision,
1283 const svn_delta_editor_t *editor,
1285 apr_hash_t *rev_props,
1288 apr_pool_t *subpool = svn_pool_create(pool);
1289 replay_baton_t *rb = replay_baton;
1290 apr_hash_t *filtered, *existing_props;
1292 int normalized_count;
1294 SVN_ERR(editor->close_edit(edit_baton, pool));
1296 /* Sanity check that we actually committed the revision we meant to. */
1297 if (rb->sb->committed_rev != revision)
1298 return svn_error_createf
1300 _("Commit created rev %ld but should have created %ld"),
1301 rb->sb->committed_rev, revision);
1303 SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1307 /* Ok, we're done with the data, now we just need to copy the remaining
1308 'svn:date' and 'svn:author' revprops and we're all set.
1309 If the server doesn't support revprops-in-a-commit, we still have to
1310 set all revision properties except svn:log. */
1311 filtered = filter_props(&filtered_count, rev_props,
1312 (rb->has_commit_revprops_capability
1313 ? filter_include_date_author_sync
1314 : filter_exclude_log),
1317 /* If necessary, normalize encoding and line ending style, and add the number
1318 of EOL-normalized properties to the overall count in the replay baton. */
1319 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1320 rb->sb->source_prop_encoding, pool));
1321 rb->normalized_rev_props_count += normalized_count;
1323 SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1326 /* Remove all extra properties in TARGET. */
1327 SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1328 rev_props, existing_props, subpool));
1330 svn_pool_clear(subpool);
1332 /* Ok, we're done, bring the last-merged-rev property up to date. */
1333 SVN_ERR(svn_ra_change_rev_prop2(
1336 SVNSYNC_PROP_LAST_MERGED_REV,
1338 svn_string_create(apr_psprintf(pool, "%ld", revision),
1342 /* And finally drop the currently copying prop, since we're done
1343 with this revision. */
1344 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1345 SVNSYNC_PROP_CURRENTLY_COPYING,
1346 NULL, NULL, subpool));
1348 /* Notify the user that we copied revision properties. */
1349 if (! rb->sb->quiet)
1350 SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1352 svn_pool_destroy(subpool);
1354 return SVN_NO_ERROR;
1357 /* Synchronize the repository associated with RA session TO_SESSION,
1358 * using information found in BATON, while the repository is
1359 * locked. Implements `with_locked_func_t' interface.
1361 static svn_error_t *
1362 do_synchronize(svn_ra_session_t *to_session,
1363 subcommand_baton_t *baton, apr_pool_t *pool)
1365 svn_string_t *last_merged_rev;
1366 svn_revnum_t from_latest;
1367 svn_ra_session_t *from_session;
1368 svn_string_t *currently_copying;
1369 svn_revnum_t to_latest, copying, last_merged;
1370 svn_revnum_t start_revision, end_revision;
1372 int normalized_rev_props_count = 0;
1374 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1375 baton->from_url, to_session,
1376 &(baton->source_callbacks), baton->config,
1379 /* Check to see if we have revprops that still need to be copied for
1380 a prior revision we didn't finish copying. But first, check for
1381 state sanity. Remember, mirroring is not an atomic action,
1382 because revision properties are copied separately from the
1383 revision's contents.
1385 So, any time that currently-copying is not set, then
1386 last-merged-rev should be the HEAD revision of the destination
1387 repository. That is, if we didn't fall over in the middle of a
1388 previous synchronization, then our destination repository should
1389 have exactly as many revisions in it as we've synchronized.
1391 Alternately, if currently-copying *is* set, it must
1392 be either last-merged-rev or last-merged-rev + 1, and the HEAD
1393 revision must be equal to either last-merged-rev or
1394 currently-copying. If this is not the case, somebody has meddled
1395 with the destination without using svnsync.
1398 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1399 ¤tly_copying, pool));
1401 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1403 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1405 if (currently_copying)
1407 copying = SVN_STR_TO_REV(currently_copying->data);
1409 if ((copying < last_merged)
1410 || (copying > (last_merged + 1))
1411 || ((to_latest != last_merged) && (to_latest != copying)))
1413 return svn_error_createf
1415 _("Revision being currently copied (%ld), last merged revision "
1416 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1417 "committed to the destination without using svnsync?"),
1418 copying, last_merged, to_latest);
1420 else if (copying == to_latest)
1422 if (copying > last_merged)
1424 SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
1425 baton->quiet, baton->source_prop_encoding,
1426 &normalized_rev_props_count, pool));
1427 last_merged = copying;
1428 last_merged_rev = svn_string_create
1429 (apr_psprintf(pool, "%ld", last_merged), pool);
1432 /* Now update last merged rev and drop currently changing.
1433 Note that the order here is significant, if we do them
1434 in the wrong order there are race conditions where we
1435 end up not being able to tell if there have been bogus
1436 (i.e. non-svnsync) commits to the dest repository. */
1438 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1439 SVNSYNC_PROP_LAST_MERGED_REV,
1440 NULL, last_merged_rev, pool));
1441 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1442 SVNSYNC_PROP_CURRENTLY_COPYING,
1445 /* If copying > to_latest, then we just fall through to
1446 attempting to copy the revision again. */
1450 if (to_latest != last_merged)
1451 return svn_error_createf(APR_EINVAL, NULL,
1452 _("Destination HEAD (%ld) is not the last "
1453 "merged revision (%ld); have you "
1454 "committed to the destination without "
1456 to_latest, last_merged);
1459 /* Now check to see if there are any revisions to copy. */
1460 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1462 if (from_latest < last_merged)
1463 return SVN_NO_ERROR;
1465 /* Ok, so there are new revisions, iterate over them copying them
1466 into the destination repository. */
1467 SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
1469 /* For compatibility with older svnserve versions, check first if we
1470 support adding revprops to the commit. */
1471 SVN_ERR(svn_ra_has_capability(rb->to_session,
1472 &rb->has_commit_revprops_capability,
1473 SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1476 start_revision = last_merged + 1;
1477 end_revision = from_latest;
1479 SVN_ERR(check_cancel(NULL));
1481 SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1482 0, TRUE, replay_rev_started,
1483 replay_rev_finished, rb, pool));
1485 SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
1486 + normalized_rev_props_count,
1487 rb->normalized_node_props_count,
1491 return SVN_NO_ERROR;
1495 /* SUBCOMMAND: sync */
1496 static svn_error_t *
1497 synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1499 svn_ra_session_t *to_session;
1500 opt_baton_t *opt_baton = b;
1501 apr_array_header_t *targets;
1502 subcommand_baton_t *baton;
1503 const char *to_url, *from_url;
1505 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1506 apr_array_make(pool, 0,
1507 sizeof(const char *)),
1509 if (targets->nelts < 1)
1510 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1511 if (targets->nelts > 2)
1512 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1514 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1515 if (! svn_path_is_url(to_url))
1516 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1517 _("Path '%s' is not a URL"), to_url);
1519 if (targets->nelts == 2)
1521 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1522 if (! svn_path_is_url(from_url))
1523 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1524 _("Path '%s' is not a URL"), from_url);
1528 from_url = NULL; /* we'll read it from the destination repos */
1531 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
1532 SVN_ERR(open_target_session(&to_session, baton, pool));
1533 if (opt_baton->disable_locking)
1534 SVN_ERR(do_synchronize(to_session, baton, pool));
1536 SVN_ERR(with_locked(to_session, do_synchronize, baton,
1537 opt_baton->steal_lock, pool));
1539 return SVN_NO_ERROR;
1544 /*** `svnsync copy-revprops' ***/
1546 /* Copy revision properties to the repository associated with RA
1547 * session TO_SESSION, using information found in BATON, while the
1548 * repository is locked. Implements `with_locked_func_t' interface.
1550 static svn_error_t *
1551 do_copy_revprops(svn_ra_session_t *to_session,
1552 subcommand_baton_t *baton, apr_pool_t *pool)
1554 svn_ra_session_t *from_session;
1555 svn_string_t *last_merged_rev;
1557 svn_revnum_t step = 1;
1558 int normalized_rev_props_count = 0;
1560 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1561 baton->from_url, to_session,
1562 &(baton->source_callbacks), baton->config,
1565 /* An invalid revision means "last-synced" */
1566 if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1567 baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1568 if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1569 baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1571 /* Make sure we have revisions within the valid range. */
1572 if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1573 return svn_error_createf
1575 _("Cannot copy revprops for a revision (%ld) that has not "
1576 "been synchronized yet"), baton->start_rev);
1577 if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1578 return svn_error_createf
1580 _("Cannot copy revprops for a revision (%ld) that has not "
1581 "been synchronized yet"), baton->end_rev);
1583 /* Now, copy all the requested revisions, in the requested order. */
1584 step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1585 for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1587 int normalized_count;
1588 SVN_ERR(check_cancel(NULL));
1589 SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
1590 baton->source_prop_encoding, &normalized_count,
1592 normalized_rev_props_count += normalized_count;
1595 /* Notify about normalized props, if any. */
1596 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
1598 return SVN_NO_ERROR;
1602 /* Set *START_REVNUM to the revision number associated with
1603 START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
1604 represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
1605 the revision number associated with END_REVISION or to
1606 SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
1607 END_REVNUM to the same value as START_REVNUM.
1609 As a special case, if neither START_REVISION nor END_REVISION is
1610 specified, set *START_REVNUM to 0 and set *END_REVNUM to
1613 Freak out if either START_REVISION or END_REVISION represents an
1614 explicit but invalid revision number. */
1615 static svn_error_t *
1616 resolve_revnums(svn_revnum_t *start_revnum,
1617 svn_revnum_t *end_revnum,
1618 svn_opt_revision_t start_revision,
1619 svn_opt_revision_t end_revision)
1621 svn_revnum_t start_rev, end_rev;
1623 /* Special case: neither revision is specified? This is like
1625 if ((start_revision.kind == svn_opt_revision_unspecified) &&
1626 (end_revision.kind == svn_opt_revision_unspecified))
1629 *end_revnum = SVN_INVALID_REVNUM;
1630 return SVN_NO_ERROR;
1633 /* Get the start revision, which must be either HEAD or a number
1634 (which is required to be a valid one). */
1635 if (start_revision.kind == svn_opt_revision_head)
1637 start_rev = SVN_INVALID_REVNUM;
1641 start_rev = start_revision.value.number;
1642 if (! SVN_IS_VALID_REVNUM(start_rev))
1643 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1644 _("Invalid revision number (%ld)"),
1648 /* Get the end revision, which must be unspecified (meaning,
1649 "same as the start_rev"), HEAD, or a number (which is
1650 required to be a valid one). */
1651 if (end_revision.kind == svn_opt_revision_unspecified)
1653 end_rev = start_rev;
1655 else if (end_revision.kind == svn_opt_revision_head)
1657 end_rev = SVN_INVALID_REVNUM;
1661 end_rev = end_revision.value.number;
1662 if (! SVN_IS_VALID_REVNUM(end_rev))
1663 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1664 _("Invalid revision number (%ld)"),
1668 *start_revnum = start_rev;
1669 *end_revnum = end_rev;
1670 return SVN_NO_ERROR;
1674 /* SUBCOMMAND: copy-revprops */
1675 static svn_error_t *
1676 copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1678 svn_ra_session_t *to_session;
1679 opt_baton_t *opt_baton = b;
1680 apr_array_header_t *targets;
1681 subcommand_baton_t *baton;
1682 const char *to_url = NULL;
1683 const char *from_url = NULL;
1684 svn_opt_revision_t start_revision, end_revision;
1685 svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1687 /* There should be either one or two arguments left to parse. */
1688 if (os->argc - os->ind > 2)
1689 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1690 if (os->argc - os->ind < 1)
1691 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1693 /* If there are two args, the last one is either a revision range or
1695 if (os->argc - os->ind == 2)
1697 const char *arg_str = os->argv[os->argc - 1];
1698 const char *utf_arg_str;
1700 SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
1702 if (! svn_path_is_url(utf_arg_str))
1704 /* This is the old "... TO_URL REV[:REV2]" syntax.
1705 Revisions come only from this argument. (We effectively
1706 pop that last argument from the end of the argument list
1707 so svn_opt__args_to_target_array() can do its thang.) */
1710 if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
1711 || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
1712 return svn_error_create(
1713 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1714 _("Cannot specify revisions via both command-line arguments "
1715 "and the --revision (-r) option"));
1717 start_revision.kind = svn_opt_revision_unspecified;
1718 end_revision.kind = svn_opt_revision_unspecified;
1719 if (svn_opt_parse_revision(&start_revision, &end_revision,
1720 arg_str, pool) != 0)
1721 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1722 _("Invalid revision range '%s' provided"),
1725 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1726 start_revision, end_revision));
1728 SVN_ERR(svn_opt__args_to_target_array(
1730 apr_array_make(pool, 1, sizeof(const char *)), pool));
1731 if (targets->nelts != 1)
1732 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1733 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1740 /* This is the "... TO_URL SOURCE_URL" syntax. Revisions
1741 come only from the --revision parameter. */
1742 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1743 opt_baton->start_rev, opt_baton->end_rev));
1745 SVN_ERR(svn_opt__args_to_target_array(
1747 apr_array_make(pool, 2, sizeof(const char *)), pool));
1748 if (targets->nelts < 1)
1749 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1750 if (targets->nelts > 2)
1751 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1752 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1753 if (targets->nelts == 2)
1754 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1759 if (! svn_path_is_url(to_url))
1760 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1761 _("Path '%s' is not a URL"), to_url);
1762 if (from_url && (! svn_path_is_url(from_url)))
1763 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1764 _("Path '%s' is not a URL"), from_url);
1766 baton = make_subcommand_baton(opt_baton, to_url, from_url,
1767 start_rev, end_rev, pool);
1768 SVN_ERR(open_target_session(&to_session, baton, pool));
1769 if (opt_baton->disable_locking)
1770 SVN_ERR(do_copy_revprops(to_session, baton, pool));
1772 SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
1773 opt_baton->steal_lock, pool));
1775 return SVN_NO_ERROR;
1780 /*** `svnsync info' ***/
1783 /* SUBCOMMAND: info */
1784 static svn_error_t *
1785 info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1787 svn_ra_session_t *to_session;
1788 opt_baton_t *opt_baton = b;
1789 apr_array_header_t *targets;
1790 subcommand_baton_t *baton;
1793 svn_string_t *from_url, *from_uuid, *last_merged_rev;
1795 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1796 apr_array_make(pool, 0,
1797 sizeof(const char *)),
1799 if (targets->nelts < 1)
1800 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1801 if (targets->nelts > 1)
1802 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1804 /* Get the mirror repository URL, and verify that it is URL-ish. */
1805 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1806 if (! svn_path_is_url(to_url))
1807 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1808 _("Path '%s' is not a URL"), to_url);
1810 /* Open an RA session to the mirror repository URL. */
1811 baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1812 SVN_ERR(open_target_session(&to_session, baton, pool));
1814 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
1816 from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
1819 return svn_error_createf
1820 (SVN_ERR_BAD_URL, NULL,
1821 _("Repository '%s' is not initialized for synchronization"), to_url);
1823 from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
1824 last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
1826 /* Print the info. */
1827 SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1829 SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1831 if (last_merged_rev)
1832 SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1833 last_merged_rev->data));
1834 return SVN_NO_ERROR;
1839 /*** `svnsync help' ***/
1842 /* SUBCOMMAND: help */
1843 static svn_error_t *
1844 help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1846 opt_baton_t *opt_baton = baton;
1848 const char *header =
1849 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
1850 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1851 "Type 'svnsync --version' to see the program version and RA modules.\n"
1853 "Available subcommands:\n");
1855 const char *ra_desc_start
1856 = _("The following repository access (RA) modules are available:\n\n");
1858 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1861 SVN_ERR(svn_ra_print_modules(version_footer, pool));
1863 SVN_ERR(svn_opt_print_help4(os, "svnsync",
1864 opt_baton ? opt_baton->version : FALSE,
1865 opt_baton ? opt_baton->quiet : FALSE,
1866 /*###opt_state ? opt_state->verbose :*/ FALSE,
1867 version_footer->data, header,
1868 svnsync_cmd_table, svnsync_options, NULL,
1871 return SVN_NO_ERROR;
1879 main(int argc, const char *argv[])
1881 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1882 apr_array_header_t *received_opts;
1883 opt_baton_t opt_baton;
1884 svn_config_t *config;
1885 apr_status_t apr_err;
1890 const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1891 const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1892 apr_array_header_t *config_options = NULL;
1893 const char *source_prop_encoding = NULL;
1894 svn_boolean_t force_interactive = FALSE;
1896 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
1898 return EXIT_FAILURE;
1901 err = check_lib_versions();
1903 return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
1905 /* Create our top-level pool. Use a separate mutexless allocator,
1906 * given this application is single threaded.
1908 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1910 err = svn_ra_initialize(pool);
1912 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1914 /* Initialize the option baton. */
1915 memset(&opt_baton, 0, sizeof(opt_baton));
1916 opt_baton.start_rev.kind = svn_opt_revision_unspecified;
1917 opt_baton.end_rev.kind = svn_opt_revision_unspecified;
1919 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1923 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
1924 svn_pool_destroy(pool);
1925 return EXIT_FAILURE;
1928 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1930 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1936 const char *opt_arg;
1937 svn_error_t* opt_err = NULL;
1939 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
1940 if (APR_STATUS_IS_EOF(apr_err))
1944 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
1945 svn_pool_destroy(pool);
1946 return EXIT_FAILURE;
1949 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1953 case svnsync_opt_non_interactive:
1954 opt_baton.non_interactive = TRUE;
1957 case svnsync_opt_force_interactive:
1958 force_interactive = TRUE;
1961 case svnsync_opt_trust_server_cert:
1962 opt_baton.trust_server_cert = TRUE;
1965 case svnsync_opt_no_auth_cache:
1966 opt_baton.no_auth_cache = TRUE;
1969 case svnsync_opt_auth_username:
1970 opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
1973 case svnsync_opt_auth_password:
1974 opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
1977 case svnsync_opt_source_username:
1978 opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
1981 case svnsync_opt_source_password:
1982 opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
1985 case svnsync_opt_sync_username:
1986 opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
1989 case svnsync_opt_sync_password:
1990 opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
1993 case svnsync_opt_config_dir:
1995 const char *path_utf8;
1996 opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
1999 opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
2002 case svnsync_opt_config_options:
2003 if (!config_options)
2005 apr_array_make(pool, 1,
2006 sizeof(svn_cmdline__config_argument_t*));
2008 err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
2010 err = svn_cmdline__parse_config_option(config_options,
2013 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2016 case svnsync_opt_source_prop_encoding:
2017 opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
2021 case svnsync_opt_disable_locking:
2022 opt_baton.disable_locking = TRUE;
2025 case svnsync_opt_steal_lock:
2026 opt_baton.steal_lock = TRUE;
2029 case svnsync_opt_version:
2030 opt_baton.version = TRUE;
2033 case svnsync_opt_allow_non_empty:
2034 opt_baton.allow_non_empty = TRUE;
2038 opt_baton.quiet = TRUE;
2042 if (svn_opt_parse_revision(&opt_baton.start_rev,
2044 opt_arg, pool) != 0)
2046 const char *utf8_opt_arg;
2047 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
2049 err = svn_error_createf(
2050 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2051 _("Syntax error in revision argument '%s'"),
2053 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2056 /* We only allow numbers and 'HEAD'. */
2057 if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
2058 (opt_baton.start_rev.kind != svn_opt_revision_head))
2059 || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
2060 (opt_baton.end_rev.kind != svn_opt_revision_head) &&
2061 (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
2063 err = svn_error_createf(
2064 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2065 _("Invalid revision range '%s' provided"), opt_arg);
2066 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2072 opt_baton.help = TRUE;
2077 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2078 svn_pool_destroy(pool);
2079 return EXIT_FAILURE;
2084 return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: ");
2088 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2090 /* The --non-interactive and --force-interactive options are mutually
2092 if (opt_baton.non_interactive && force_interactive)
2094 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2095 _("--non-interactive and --force-interactive "
2096 "are mutually exclusive"));
2097 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2100 opt_baton.non_interactive = !svn_cmdline__be_interactive(
2101 opt_baton.non_interactive,
2104 /* Disallow the mixing --username/password with their --source- and
2105 --sync- variants. Treat "--username FOO" as "--source-username
2106 FOO --sync-username FOO"; ditto for "--password FOO". */
2107 if ((username || password)
2108 && (source_username || sync_username
2109 || source_password || sync_password))
2111 err = svn_error_create
2112 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2113 _("Cannot use --username or --password with any of "
2114 "--source-username, --source-password, --sync-username, "
2115 "or --sync-password.\n"));
2116 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2120 source_username = username;
2121 sync_username = username;
2125 source_password = password;
2126 sync_password = password;
2128 opt_baton.source_username = source_username;
2129 opt_baton.source_password = source_password;
2130 opt_baton.sync_username = sync_username;
2131 opt_baton.sync_password = sync_password;
2133 /* Disallow mixing of --steal-lock and --disable-locking. */
2134 if (opt_baton.steal_lock && opt_baton.disable_locking)
2136 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2137 _("--disable-locking and --steal-lock are "
2138 "mutually exclusive"));
2139 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2142 /* --trust-server-cert can only be used with --non-interactive */
2143 if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
2145 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2146 _("--trust-server-cert requires "
2147 "--non-interactive"));
2148 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2151 err = svn_config_ensure(opt_baton.config_dir, pool);
2153 return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
2155 if (subcommand == NULL)
2157 if (os->ind >= os->argc)
2159 if (opt_baton.version)
2161 /* Use the "help" subcommand to handle "--version". */
2162 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2163 { "--version", help_cmd, {0}, "",
2164 {svnsync_opt_version, /* must accept its own option */
2168 subcommand = &pseudo_cmd;
2172 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2173 svn_pool_destroy(pool);
2174 return EXIT_FAILURE;
2179 const char *first_arg = os->argv[os->ind++];
2180 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2182 if (subcommand == NULL)
2184 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2185 svn_pool_destroy(pool);
2186 return EXIT_FAILURE;
2191 for (i = 0; i < received_opts->nelts; ++i)
2193 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2195 if (opt_id == 'h' || opt_id == '?')
2198 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2201 const apr_getopt_option_t *badopt =
2202 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2204 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2205 if (subcommand->name[0] == '-')
2207 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2211 err = svn_error_createf
2212 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2213 _("Subcommand '%s' doesn't accept option '%s'\n"
2214 "Type 'svnsync help %s' for usage.\n"),
2215 subcommand->name, optstr, subcommand->name);
2216 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2221 err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
2223 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2225 /* Update the options in the config */
2229 svn_cmdline__apply_config_options(opt_baton.config, config_options,
2230 "svnsync: ", "--config-option"));
2233 config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
2235 opt_baton.source_prop_encoding = source_prop_encoding;
2237 apr_signal(SIGINT, signal_handler);
2240 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2241 apr_signal(SIGBREAK, signal_handler);
2245 apr_signal(SIGHUP, signal_handler);
2249 apr_signal(SIGTERM, signal_handler);
2253 /* Disable SIGPIPE generation for the platforms that have it. */
2254 apr_signal(SIGPIPE, SIG_IGN);
2258 /* Disable SIGXFSZ generation for the platforms that have it,
2259 otherwise working with large files when compiled against an APR
2260 that doesn't have large file support will crash the program,
2262 apr_signal(SIGXFSZ, SIG_IGN);
2265 err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton,
2266 opt_baton.non_interactive,
2267 opt_baton.source_username,
2268 opt_baton.source_password,
2269 opt_baton.config_dir,
2270 opt_baton.no_auth_cache,
2271 opt_baton.trust_server_cert,
2276 err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton,
2277 opt_baton.non_interactive,
2278 opt_baton.sync_username,
2279 opt_baton.sync_password,
2280 opt_baton.config_dir,
2281 opt_baton.no_auth_cache,
2282 opt_baton.trust_server_cert,
2287 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2290 /* For argument-related problems, suggest using the 'help'
2292 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2293 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2295 err = svn_error_quick_wrap(err,
2296 _("Try 'svnsync help' for more info"));
2299 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2302 svn_pool_destroy(pool);
2304 return EXIT_SUCCESS;