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"
41 #include "private/svn_subr_private.h"
45 #include "svn_private_config.h"
47 #include <apr_signal.h>
50 static svn_opt_subcommand_t initialize_cmd,
57 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
58 svnsync_opt_force_interactive,
59 svnsync_opt_no_auth_cache,
60 svnsync_opt_auth_username,
61 svnsync_opt_auth_password,
62 svnsync_opt_source_username,
63 svnsync_opt_source_password,
64 svnsync_opt_sync_username,
65 svnsync_opt_sync_password,
66 svnsync_opt_config_dir,
67 svnsync_opt_config_options,
68 svnsync_opt_source_prop_encoding,
69 svnsync_opt_disable_locking,
71 svnsync_opt_trust_server_cert,
72 svnsync_opt_allow_non_empty,
73 svnsync_opt_steal_lock
76 #define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
77 svnsync_opt_force_interactive, \
78 svnsync_opt_no_auth_cache, \
79 svnsync_opt_auth_username, \
80 svnsync_opt_auth_password, \
81 svnsync_opt_trust_server_cert, \
82 svnsync_opt_source_username, \
83 svnsync_opt_source_password, \
84 svnsync_opt_sync_username, \
85 svnsync_opt_sync_password, \
86 svnsync_opt_config_dir, \
87 svnsync_opt_config_options
89 static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
91 { "initialize", initialize_cmd, { "init" },
92 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
94 "Initialize a destination repository for synchronization from\n"
95 "another repository.\n"
97 "If the source URL is not the root of a repository, only the\n"
98 "specified part of the repository will be synchronized.\n"
100 "The destination URL must point to the root of a repository which\n"
101 "has been configured to allow revision property changes. In\n"
102 "the general case, the destination repository must contain no\n"
103 "committed revisions. Use --allow-non-empty to override this\n"
104 "restriction, which will cause svnsync to assume that any revisions\n"
105 "already present in the destination repository perfectly mirror\n"
106 "their counterparts in the source repository. (This is useful\n"
107 "when initializing a copy of a repository as a mirror of that same\n"
108 "repository, for example.)\n"
110 "You should not commit to, or make revision property changes in,\n"
111 "the destination repository by any method other than 'svnsync'.\n"
112 "In other words, the destination repository should be a read-only\n"
113 "mirror of the source repository.\n"),
114 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
115 svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
116 svnsync_opt_steal_lock } },
117 { "synchronize", synchronize_cmd, { "sync" },
118 N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
120 "Transfer all pending revisions to the destination from the source\n"
121 "with which it was initialized.\n"
123 "If SOURCE_URL is provided, use that as the source repository URL,\n"
124 "ignoring what is recorded in the destination repository as the\n"
125 "source URL. Specifying SOURCE_URL is recommended in particular\n"
126 "if untrusted users/administrators may have write access to the\n"
127 "DEST_URL repository.\n"),
128 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
129 svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
130 { "copy-revprops", copy_revprops_cmd, { 0 },
133 " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
134 " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
136 "Copy the revision properties in a given range of revisions to the\n"
137 "destination from the source with which it was initialized. If the\n"
138 "revision range is not specified, it defaults to all revisions in\n"
139 "the DEST_URL repository. Note also that the 'HEAD' revision is the\n"
140 "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
142 "If SOURCE_URL is provided, use that as the source repository URL,\n"
143 "ignoring what is recorded in the destination repository as the\n"
144 "source URL. Specifying SOURCE_URL is recommended in particular\n"
145 "if untrusted users/administrators may have write access to the\n"
146 "DEST_URL repository.\n"
148 "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
149 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
150 svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
151 { "info", info_cmd, { 0 },
152 N_("usage: svnsync info DEST_URL\n"
154 "Print information about the synchronization destination repository\n"
155 "located at DEST_URL.\n"),
156 { SVNSYNC_OPTS_DEFAULT } },
157 { "help", help_cmd, { "?", "h" },
158 N_("usage: svnsync help [SUBCOMMAND...]\n"
160 "Describe the usage of this program or its subcommands.\n"),
162 { NULL, NULL, { 0 }, NULL, { 0 } }
165 static const apr_getopt_option_t svnsync_options[] =
168 N_("print as little as possible") },
170 N_("operate on revision ARG (or range ARG1:ARG2)\n"
172 "A revision argument can be one of:\n"
174 " NUMBER revision number\n"
176 " 'HEAD' latest in repository") },
177 {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
178 N_("allow a non-empty destination repository") },
179 {"non-interactive", svnsync_opt_non_interactive, 0,
180 N_("do no interactive prompting (default is to prompt\n"
182 "only if standard input is a terminal device)")},
183 {"force-interactive", svnsync_opt_force_interactive, 0,
184 N_("do interactive prompting even if standard input\n"
186 "is not a terminal device")},
187 {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
188 N_("do not cache authentication tokens") },
189 {"username", svnsync_opt_auth_username, 1,
190 N_("specify a username ARG (deprecated;\n"
192 "see --source-username and --sync-username)") },
193 {"password", svnsync_opt_auth_password, 1,
194 N_("specify a password ARG (deprecated;\n"
196 "see --source-password and --sync-password)") },
197 {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
198 N_("accept SSL server certificates from unknown\n"
200 "certificate authorities without prompting (but only\n"
202 "with '--non-interactive')") },
203 {"source-username", svnsync_opt_source_username, 1,
204 N_("connect to source repository with username ARG") },
205 {"source-password", svnsync_opt_source_password, 1,
206 N_("connect to source repository with password ARG") },
207 {"sync-username", svnsync_opt_sync_username, 1,
208 N_("connect to sync repository with username ARG") },
209 {"sync-password", svnsync_opt_sync_password, 1,
210 N_("connect to sync repository with password ARG") },
211 {"config-dir", svnsync_opt_config_dir, 1,
212 N_("read user configuration files from directory ARG")},
213 {"config-option", svnsync_opt_config_options, 1,
214 N_("set user configuration option in the format:\n"
216 " FILE:SECTION:OPTION=[VALUE]\n"
220 " servers:global:http-library=serf")},
221 {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
222 N_("convert translatable properties from encoding ARG\n"
224 "to UTF-8. If not specified, then properties are\n"
226 "presumed to be encoded in UTF-8.")},
227 {"disable-locking", svnsync_opt_disable_locking, 0,
228 N_("Disable built-in locking. Use of this option can\n"
230 "corrupt the mirror unless you ensure that no other\n"
232 "instance of svnsync is running concurrently.")},
233 {"steal-lock", svnsync_opt_steal_lock, 0,
234 N_("Steal locks as necessary. Use, with caution,\n"
236 "if your mirror repository contains stale locks\n"
238 "and is not being concurrently accessed by another\n"
240 "svnsync instance.")},
241 {"version", svnsync_opt_version, 0,
242 N_("show program version information")},
244 N_("show help on a subcommand")},
246 N_("show help on a subcommand")},
250 typedef struct opt_baton_t {
251 svn_boolean_t non_interactive;
252 svn_boolean_t trust_server_cert;
253 svn_boolean_t no_auth_cache;
254 svn_auth_baton_t *source_auth_baton;
255 svn_auth_baton_t *sync_auth_baton;
256 const char *source_username;
257 const char *source_password;
258 const char *sync_username;
259 const char *sync_password;
260 const char *config_dir;
262 const char *source_prop_encoding;
263 svn_boolean_t disable_locking;
264 svn_boolean_t steal_lock;
266 svn_boolean_t allow_non_empty;
267 svn_boolean_t version;
269 svn_opt_revision_t start_rev;
270 svn_opt_revision_t end_rev;
276 /*** Helper functions ***/
279 /* Global record of whether the user has requested cancellation. */
280 static volatile sig_atomic_t cancelled = FALSE;
283 /* Callback function for apr_signal(). */
285 signal_handler(int signum)
287 apr_signal(signum, SIG_IGN);
292 /* Cancellation callback function. */
294 check_cancel(void *baton)
297 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
303 /* Check that the version of libraries in use match what we expect. */
305 check_lib_versions(void)
307 static const svn_version_checklist_t checklist[] =
309 { "svn_subr", svn_subr_version },
310 { "svn_delta", svn_delta_version },
311 { "svn_ra", svn_ra_version },
314 SVN_VERSION_DEFINE(my_version);
316 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
320 /* Implements `svn_ra__lock_retry_func_t'. */
322 lock_retry_func(void *baton,
323 const svn_string_t *reposlocktoken,
326 return svn_cmdline_printf(pool,
327 _("Failed to get lock on destination "
328 "repos, currently held by '%s'\n"),
329 reposlocktoken->data);
332 /* Acquire a lock (of sorts) on the repository associated with the
333 * given RA SESSION. This lock is just a revprop change attempt in a
334 * time-delay loop. This function is duplicated by svnrdump in
335 * svnrdump/load_editor.c
338 get_lock(const svn_string_t **lock_string_p,
339 svn_ra_session_t *session,
340 svn_boolean_t steal_lock,
344 svn_boolean_t be_atomic;
345 const svn_string_t *stolen_lock;
347 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
348 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
352 /* Pre-1.7 server. Can't lock without a race condition.
355 err = svn_error_create(
356 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
357 _("Target server does not support atomic revision property "
358 "edits; consider upgrading it to 1.7 or using an external "
360 svn_handle_warning2(stderr, err, "svnsync: ");
361 svn_error_clear(err);
364 err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
365 SVNSYNC_PROP_LOCK, steal_lock,
366 10 /* retries */, lock_retry_func, NULL,
367 check_cancel, NULL, pool);
368 if (!err && stolen_lock)
370 return svn_cmdline_printf(pool,
371 _("Stole lock previously held by '%s'\n"),
378 /* Baton for the various subcommands to share. */
379 typedef struct subcommand_baton_t {
380 /* common to all subcommands */
382 svn_ra_callbacks2_t source_callbacks;
383 svn_ra_callbacks2_t sync_callbacks;
385 svn_boolean_t allow_non_empty;
388 /* initialize, synchronize, and copy-revprops only */
389 const char *source_prop_encoding;
391 /* initialize only */
392 const char *from_url;
394 /* synchronize only */
395 svn_revnum_t committed_rev;
397 /* copy-revprops only */
398 svn_revnum_t start_rev;
399 svn_revnum_t end_rev;
401 } subcommand_baton_t;
403 typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
404 subcommand_baton_t *baton,
408 /* Lock the repository associated with RA SESSION, then execute the
409 * given FUNC/BATON pair while holding the lock. Finally, drop the
410 * lock once it finishes.
413 with_locked(svn_ra_session_t *session,
414 with_locked_func_t func,
415 subcommand_baton_t *baton,
416 svn_boolean_t steal_lock,
419 const svn_string_t *lock_string;
422 SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
424 err = func(session, baton, pool);
425 return svn_error_compose_create(err,
426 svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
431 /* Callback function for the RA session's open_tmp_file()
435 open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
437 return svn_io_open_unique_file3(fp, NULL, NULL,
438 svn_io_file_del_on_pool_cleanup,
443 /* Return SVN_NO_ERROR iff URL identifies the root directory of the
444 * repository associated with RA session SESS.
447 check_if_session_is_at_repos_root(svn_ra_session_t *sess,
451 const char *sess_root;
453 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
455 if (strcmp(url, sess_root) == 0)
458 return svn_error_createf
460 _("Session is rooted at '%s' but the repos root is '%s'"),
465 /* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
466 * revision REV of the repository associated with RA session SESSION.
468 * For REV zero, don't remove properties with the "svn:sync-" prefix.
470 * All allocations will be done in a subpool of POOL.
473 remove_props_not_in_source(svn_ra_session_t *session,
475 apr_hash_t *source_props,
476 apr_hash_t *target_props,
479 apr_pool_t *subpool = svn_pool_create(pool);
480 apr_hash_index_t *hi;
482 for (hi = apr_hash_first(pool, target_props);
484 hi = apr_hash_next(hi))
486 const char *propname = svn__apr_hash_index_key(hi);
488 svn_pool_clear(subpool);
490 if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
491 sizeof(SVNSYNC_PROP_PREFIX) - 1))
494 /* Delete property if the name can't be found in SOURCE_PROPS. */
495 if (! svn_hash_gets(source_props, propname))
496 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
500 svn_pool_destroy(subpool);
505 /* Filter callback function.
506 * Takes a property name KEY, and is expected to return TRUE if the property
507 * should be filtered out (ie. not be copied to the target list), or FALSE if
510 typedef svn_boolean_t (*filter_func_t)(const char *key);
512 /* Make a new set of properties, by copying those properties in PROPS for which
513 * the filter FILTER returns FALSE.
515 * The number of properties not copied will be stored in FILTERED_COUNT.
517 * The returned set of properties is allocated from POOL.
520 filter_props(int *filtered_count, apr_hash_t *props,
521 filter_func_t filter,
524 apr_hash_index_t *hi;
525 apr_hash_t *filtered = apr_hash_make(pool);
528 for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
530 const char *propname = svn__apr_hash_index_key(hi);
531 void *propval = svn__apr_hash_index_val(hi);
533 /* Copy all properties:
534 - not matching the exclude pattern if provided OR
535 - matching the include pattern if provided */
536 if (!filter || !filter(propname))
538 svn_hash_sets(filtered, propname, propval);
542 *filtered_count += 1;
550 /* Write the set of revision properties REV_PROPS to revision REV to the
551 * repository associated with RA session SESSION.
552 * Omit any properties whose names are in the svnsync property name space,
553 * and set *FILTERED_COUNT to the number of properties thus omitted.
554 * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
556 * All allocations will be done in a subpool of POOL.
559 write_revprops(int *filtered_count,
560 svn_ra_session_t *session,
562 apr_hash_t *rev_props,
565 apr_pool_t *subpool = svn_pool_create(pool);
566 apr_hash_index_t *hi;
570 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
572 const char *propname = svn__apr_hash_index_key(hi);
573 const svn_string_t *propval = svn__apr_hash_index_val(hi);
575 svn_pool_clear(subpool);
577 if (strncmp(propname, SVNSYNC_PROP_PREFIX,
578 sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
580 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
585 *filtered_count += 1;
589 svn_pool_destroy(subpool);
596 log_properties_copied(svn_boolean_t syncprops_found,
601 SVN_ERR(svn_cmdline_printf(pool,
602 _("Copied properties for revision %ld "
603 "(%s* properties skipped).\n"),
604 rev, SVNSYNC_PROP_PREFIX));
606 SVN_ERR(svn_cmdline_printf(pool,
607 _("Copied properties for revision %ld.\n"),
613 /* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
614 * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
615 * endings, if either of those numbers is non-zero. */
617 log_properties_normalized(int normalized_rev_props_count,
618 int normalized_node_props_count,
621 if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
622 SVN_ERR(svn_cmdline_printf(pool,
623 _("NOTE: Normalized %s* properties "
624 "to LF line endings (%d rev-props, "
625 "%d node-props).\n"),
627 normalized_rev_props_count,
628 normalized_node_props_count));
633 /* Copy all the revision properties, except for those that have the
634 * "svn:sync-" prefix, from revision REV of the repository associated
635 * with RA session FROM_SESSION, to the repository associated with RA
636 * session TO_SESSION.
638 * If SYNC is TRUE, then properties on the destination revision that
639 * do not exist on the source revision will be removed.
641 * If QUIET is FALSE, then log_properties_copied() is called to log that
642 * properties were copied for revision REV.
644 * Make sure the values of svn:* revision properties use only LF (\n)
645 * line ending style, correcting their values as necessary. The number
646 * of properties that were normalized is returned in *NORMALIZED_COUNT.
649 copy_revprops(svn_ra_session_t *from_session,
650 svn_ra_session_t *to_session,
654 const char *source_prop_encoding,
655 int *normalized_count,
658 apr_pool_t *subpool = svn_pool_create(pool);
659 apr_hash_t *existing_props, *rev_props;
660 int filtered_count = 0;
662 /* Get the list of revision properties on REV of TARGET. We're only interested
663 in the property names, but we'll get the values 'for free'. */
665 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
667 existing_props = NULL;
669 /* Get the list of revision properties on REV of SOURCE. */
670 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
672 /* If necessary, normalize encoding and line ending style and return the count
673 of EOL-normalized properties in int *NORMALIZED_COUNT. */
674 SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
675 source_prop_encoding, pool));
677 /* Copy all but the svn:svnsync properties. */
678 SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
680 /* Delete those properties that were in TARGET but not in SOURCE */
682 SVN_ERR(remove_props_not_in_source(to_session, rev,
683 rev_props, existing_props, pool));
686 SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
688 svn_pool_destroy(subpool);
694 /* Return a subcommand baton allocated from POOL and populated with
695 data from the provided parameters, which include the global
696 OPT_BATON options structure and a handful of other options. Not
697 all parameters are used in all subcommands -- see
698 subcommand_baton_t's definition for details. */
699 static subcommand_baton_t *
700 make_subcommand_baton(opt_baton_t *opt_baton,
702 const char *from_url,
703 svn_revnum_t start_rev,
704 svn_revnum_t end_rev,
707 subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
708 b->config = opt_baton->config;
709 b->source_callbacks.open_tmp_file = open_tmp_file;
710 b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
711 b->sync_callbacks.open_tmp_file = open_tmp_file;
712 b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
713 b->quiet = opt_baton->quiet;
714 b->allow_non_empty = opt_baton->allow_non_empty;
716 b->source_prop_encoding = opt_baton->source_prop_encoding;
717 b->from_url = from_url;
718 b->start_rev = start_rev;
719 b->end_rev = end_rev;
724 open_target_session(svn_ra_session_t **to_session_p,
725 subcommand_baton_t *baton,
729 /*** `svnsync init' ***/
731 /* Initialize the repository associated with RA session TO_SESSION,
732 * using information found in BATON, while the repository is
733 * locked. Implements `with_locked_func_t' interface.
736 do_initialize(svn_ra_session_t *to_session,
737 subcommand_baton_t *baton,
740 svn_ra_session_t *from_session;
741 svn_string_t *from_url;
742 svn_revnum_t latest, from_latest;
743 const char *uuid, *root_url;
744 int normalized_rev_props_count;
746 /* First, sanity check to see that we're copying into a brand new
747 repos. If we aren't, and we aren't being asked to forcibly
748 complete this initialization, that's a bad news. */
749 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
750 if ((latest != 0) && (! baton->allow_non_empty))
751 return svn_error_create
753 _("Destination repository already contains revision history; consider "
754 "using --allow-non-empty if the repository's revisions are known "
755 "to mirror their respective revisions in the source repository"));
757 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
759 if (from_url && (! baton->allow_non_empty))
760 return svn_error_createf
762 _("Destination repository is already synchronizing from '%s'"),
765 /* Now fill in our bookkeeping info in the dest repository. */
767 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
768 &(baton->source_callbacks), baton,
769 baton->config, pool));
770 SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
772 /* If we're doing a partial replay, we have to check first if the server
774 if (strcmp(root_url, baton->from_url) != 0)
776 svn_boolean_t server_supports_partial_replay;
777 svn_error_t *err = svn_ra_has_capability(from_session,
778 &server_supports_partial_replay,
779 SVN_RA_CAPABILITY_PARTIAL_REPLAY,
781 if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
782 return svn_error_trace(err);
784 if (err || !server_supports_partial_replay)
785 return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
789 /* If we're initializing a non-empty destination, we'll make sure
790 that it at least doesn't have more revisions than the source. */
793 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
794 if (from_latest < latest)
795 return svn_error_create
797 _("Destination repository has more revisions than source "
801 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
802 svn_string_create(baton->from_url, pool),
805 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
806 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
807 svn_string_create(uuid, pool), pool));
809 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
810 NULL, svn_string_createf(pool, "%ld", latest),
813 /* Copy all non-svnsync revprops from the LATEST rev in the source
814 repository into the destination, notifying about normalized
815 props, if any. When LATEST is 0, this serves the practical
816 purpose of initializing data that would otherwise be overlooked
817 by the sync process (which is going to begin with r1). When
818 LATEST is not 0, this really serves merely aesthetic and
819 informational purposes, keeping the output of this command
820 consistent while allowing folks to see what the latest revision is. */
821 SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
822 baton->source_prop_encoding, &normalized_rev_props_count,
825 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
827 /* TODO: It would be nice if we could set the dest repos UUID to be
828 equal to the UUID of the source repos, at least optionally. That
829 way people could check out/log/diff using a local fast mirror,
830 but switch --relocate to the actual final repository in order to
831 make changes... But at this time, the RA layer doesn't have a
832 way to set a UUID. */
838 /* SUBCOMMAND: init */
840 initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
842 const char *to_url, *from_url;
843 svn_ra_session_t *to_session;
844 opt_baton_t *opt_baton = b;
845 apr_array_header_t *targets;
846 subcommand_baton_t *baton;
848 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
849 apr_array_make(pool, 0,
850 sizeof(const char *)),
852 if (targets->nelts < 2)
853 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
854 if (targets->nelts > 2)
855 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
857 to_url = APR_ARRAY_IDX(targets, 0, const char *);
858 from_url = APR_ARRAY_IDX(targets, 1, const char *);
860 if (! svn_path_is_url(to_url))
861 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
862 _("Path '%s' is not a URL"), to_url);
863 if (! svn_path_is_url(from_url))
864 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
865 _("Path '%s' is not a URL"), from_url);
867 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
868 SVN_ERR(open_target_session(&to_session, baton, pool));
869 if (opt_baton->disable_locking)
870 SVN_ERR(do_initialize(to_session, baton, pool));
872 SVN_ERR(with_locked(to_session, do_initialize, baton,
873 opt_baton->steal_lock, pool));
880 /*** `svnsync sync' ***/
882 /* Implements `svn_commit_callback2_t' interface. */
884 commit_callback(const svn_commit_info_t *commit_info,
888 subcommand_baton_t *sb = baton;
892 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
893 commit_info->revision));
896 sb->committed_rev = commit_info->revision;
902 /* Set *FROM_SESSION to an RA session associated with the source
903 * repository of the synchronization. If FROM_URL is non-NULL, use it
904 * as the source repository URL; otherwise, determine the source
905 * repository URL by reading svn:sync- properties from the destination
906 * repository (associated with TO_SESSION). Set LAST_MERGED_REV to
907 * the value of the property which records the most recently
908 * synchronized revision.
910 * CALLBACKS is a vtable of RA callbacks to provide when creating
911 * *FROM_SESSION. CONFIG is a configuration hash.
914 open_source_session(svn_ra_session_t **from_session,
915 svn_string_t **last_merged_rev,
916 const char *from_url,
917 svn_ra_session_t *to_session,
918 svn_ra_callbacks2_t *callbacks,
924 svn_string_t *from_url_str, *from_uuid_str;
926 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
928 from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
929 from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
930 *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
932 if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
933 return svn_error_create
935 _("Destination repository has not been initialized"));
937 /* ### TODO: Should we validate that FROM_URL_STR->data matches any
938 provided FROM_URL here? */
940 from_url = from_url_str->data;
942 /* Open the session to copy the revision data. */
943 SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
944 callbacks, baton, config, pool));
949 /* Set *TARGET_SESSION_P to an RA session associated with the target
950 * repository of the synchronization.
953 open_target_session(svn_ra_session_t **target_session_p,
954 subcommand_baton_t *baton,
957 svn_ra_session_t *target_session;
958 SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
959 &(baton->sync_callbacks), baton, baton->config, pool));
960 SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
962 *target_session_p = target_session;
966 /* Replay baton, used during synchronization. */
967 typedef struct replay_baton_t {
968 svn_ra_session_t *from_session;
969 svn_ra_session_t *to_session;
970 /* Extra 'backdoor' session for fetching data *from* the target repo. */
971 svn_ra_session_t *extra_to_session;
972 svn_revnum_t current_revision;
973 subcommand_baton_t *sb;
974 svn_boolean_t has_commit_revprops_capability;
975 int normalized_rev_props_count;
976 int normalized_node_props_count;
980 /* Return a replay baton allocated from POOL and populated with
981 data from the provided parameters. */
983 make_replay_baton(replay_baton_t **baton_p,
984 svn_ra_session_t *from_session,
985 svn_ra_session_t *to_session,
986 subcommand_baton_t *sb, apr_pool_t *pool)
988 replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
989 rb->from_session = from_session;
990 rb->to_session = to_session;
993 SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
995 #ifdef ENABLE_EV2_SHIMS
996 /* Open up the extra baton. Only needed for Ev2 shims. */
997 SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
1001 return SVN_NO_ERROR;
1004 /* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
1005 * property. Implements filter_func_t. Use with filter_props() to filter out
1006 * svn:date and svn:author and svnsync properties.
1008 static svn_boolean_t
1009 filter_exclude_date_author_sync(const char *key)
1011 if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1013 else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1015 else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1016 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1022 /* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
1023 * property. Implements filter_func_t. Use with filter_props() to filter out
1024 * all properties except svn:date and svn:author and svnsync properties.
1026 static svn_boolean_t
1027 filter_include_date_author_sync(const char *key)
1029 return ! filter_exclude_date_author_sync(key);
1033 /* Return TRUE iff KEY is the name of the svn:log property.
1034 * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
1036 static svn_boolean_t
1037 filter_exclude_log(const char *key)
1039 if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1045 /* Return FALSE iff KEY is the name of the svn:log property.
1046 * Implements filter_func_t. Use with filter_props() to only include svn:log.
1048 static svn_boolean_t
1049 filter_include_log(const char *key)
1051 return ! filter_exclude_log(key);
1055 static svn_error_t *
1056 fetch_base_func(const char **filename,
1059 svn_revnum_t base_revision,
1060 apr_pool_t *result_pool,
1061 apr_pool_t *scratch_pool)
1063 struct replay_baton_t *rb = baton;
1064 svn_stream_t *fstream;
1067 if (svn_path_is_url(path))
1068 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1069 else if (path[0] == '/')
1072 if (! SVN_IS_VALID_REVNUM(base_revision))
1073 base_revision = rb->current_revision - 1;
1075 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1076 svn_io_file_del_on_pool_cleanup,
1077 result_pool, scratch_pool));
1079 err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
1080 fstream, NULL, NULL, scratch_pool);
1081 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1083 svn_error_clear(err);
1084 SVN_ERR(svn_stream_close(fstream));
1087 return SVN_NO_ERROR;
1090 return svn_error_trace(err);
1092 SVN_ERR(svn_stream_close(fstream));
1094 return SVN_NO_ERROR;
1097 static svn_error_t *
1098 fetch_props_func(apr_hash_t **props,
1101 svn_revnum_t base_revision,
1102 apr_pool_t *result_pool,
1103 apr_pool_t *scratch_pool)
1105 struct replay_baton_t *rb = baton;
1106 svn_node_kind_t node_kind;
1108 if (svn_path_is_url(path))
1109 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1110 else if (path[0] == '/')
1113 if (! SVN_IS_VALID_REVNUM(base_revision))
1114 base_revision = rb->current_revision - 1;
1116 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1117 &node_kind, scratch_pool));
1119 if (node_kind == svn_node_file)
1121 SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
1122 NULL, NULL, props, result_pool));
1124 else if (node_kind == svn_node_dir)
1126 apr_array_header_t *tmp_props;
1128 SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
1129 base_revision, 0 /* Dirent fields */,
1131 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1132 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1134 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1138 *props = apr_hash_make(result_pool);
1141 return SVN_NO_ERROR;
1144 static svn_error_t *
1145 fetch_kind_func(svn_node_kind_t *kind,
1148 svn_revnum_t base_revision,
1149 apr_pool_t *scratch_pool)
1151 struct replay_baton_t *rb = baton;
1153 if (svn_path_is_url(path))
1154 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1155 else if (path[0] == '/')
1158 if (! SVN_IS_VALID_REVNUM(base_revision))
1159 base_revision = rb->current_revision - 1;
1161 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1162 kind, scratch_pool));
1164 return SVN_NO_ERROR;
1168 static svn_delta_shim_callbacks_t *
1169 get_shim_callbacks(replay_baton_t *rb,
1170 apr_pool_t *result_pool)
1172 svn_delta_shim_callbacks_t *callbacks =
1173 svn_delta_shim_callbacks_default(result_pool);
1175 callbacks->fetch_props_func = fetch_props_func;
1176 callbacks->fetch_kind_func = fetch_kind_func;
1177 callbacks->fetch_base_func = fetch_base_func;
1178 callbacks->fetch_baton = rb;
1184 /* Callback function for svn_ra_replay_range, invoked when starting to parse
1187 static svn_error_t *
1188 replay_rev_started(svn_revnum_t revision,
1190 const svn_delta_editor_t **editor,
1192 apr_hash_t *rev_props,
1195 const svn_delta_editor_t *commit_editor;
1196 const svn_delta_editor_t *cancel_editor;
1197 const svn_delta_editor_t *sync_editor;
1201 replay_baton_t *rb = replay_baton;
1202 apr_hash_t *filtered;
1204 int normalized_count;
1206 /* We set this property so that if we error out for some reason
1207 we can later determine where we were in the process of
1208 merging a revision. If we had committed the change, but we
1209 hadn't finished copying the revprops we need to know that, so
1210 we can go back and finish the job before we move on.
1212 NOTE: We have to set this before we start the commit editor,
1213 because ra_svn doesn't let you change rev props during a
1215 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1216 SVNSYNC_PROP_CURRENTLY_COPYING,
1218 svn_string_createf(pool, "%ld", revision),
1221 /* The actual copy is just a replay hooked up to a commit. Include
1222 all the revision properties from the source repositories, except
1223 'svn:author' and 'svn:date', those are not guaranteed to get
1224 through the editor anyway.
1225 If we're syncing to an non-commit-revprops capable server, filter
1226 out all revprops except svn:log and add them later in
1227 revplay_rev_finished. */
1228 filtered = filter_props(&filtered_count, rev_props,
1229 (rb->has_commit_revprops_capability
1230 ? filter_exclude_date_author_sync
1231 : filter_include_log),
1234 /* svn_ra_get_commit_editor3 requires the log message to be
1235 set. It's possible that we didn't receive 'svn:log' here, so we
1236 have to set it to at least the empty string. If there's a svn:log
1237 property on this revision, we will write the actual value in the
1238 replay_rev_finished callback. */
1239 if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
1240 svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
1241 svn_string_create_empty(pool));
1243 /* If necessary, normalize encoding and line ending style. Add the number
1244 of properties that required EOL normalization to the overall count
1245 in the replay baton. */
1246 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1247 rb->sb->source_prop_encoding, pool));
1248 rb->normalized_rev_props_count += normalized_count;
1250 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
1251 get_shim_callbacks(rb, pool)));
1252 SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1255 commit_callback, rb->sb,
1256 NULL, FALSE, pool));
1258 /* There's one catch though, the diff shows us props we can't send
1259 over the RA interface, so we need an editor that's smart enough
1260 to filter those out for us. */
1261 SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
1262 rb->sb->to_url, rb->sb->source_prop_encoding,
1263 rb->sb->quiet, &sync_editor, &sync_baton,
1264 &(rb->normalized_node_props_count), pool));
1266 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1267 sync_editor, sync_baton,
1271 *editor = cancel_editor;
1272 *edit_baton = cancel_baton;
1274 rb->current_revision = revision;
1275 return SVN_NO_ERROR;
1278 /* Callback function for svn_ra_replay_range, invoked when finishing parsing
1281 static svn_error_t *
1282 replay_rev_finished(svn_revnum_t revision,
1284 const svn_delta_editor_t *editor,
1286 apr_hash_t *rev_props,
1289 apr_pool_t *subpool = svn_pool_create(pool);
1290 replay_baton_t *rb = replay_baton;
1291 apr_hash_t *filtered, *existing_props;
1293 int normalized_count;
1295 SVN_ERR(editor->close_edit(edit_baton, pool));
1297 /* Sanity check that we actually committed the revision we meant to. */
1298 if (rb->sb->committed_rev != revision)
1299 return svn_error_createf
1301 _("Commit created r%ld but should have created r%ld"),
1302 rb->sb->committed_rev, revision);
1304 SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1308 /* Ok, we're done with the data, now we just need to copy the remaining
1309 'svn:date' and 'svn:author' revprops and we're all set.
1310 If the server doesn't support revprops-in-a-commit, we still have to
1311 set all revision properties except svn:log. */
1312 filtered = filter_props(&filtered_count, rev_props,
1313 (rb->has_commit_revprops_capability
1314 ? filter_include_date_author_sync
1315 : filter_exclude_log),
1318 /* If necessary, normalize encoding and line ending style, and add the number
1319 of EOL-normalized properties to the overall count in the replay baton. */
1320 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1321 rb->sb->source_prop_encoding, pool));
1322 rb->normalized_rev_props_count += normalized_count;
1324 SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1327 /* Remove all extra properties in TARGET. */
1328 SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1329 rev_props, existing_props, subpool));
1331 svn_pool_clear(subpool);
1333 /* Ok, we're done, bring the last-merged-rev property up to date. */
1334 SVN_ERR(svn_ra_change_rev_prop2(
1337 SVNSYNC_PROP_LAST_MERGED_REV,
1339 svn_string_create(apr_psprintf(pool, "%ld", revision),
1343 /* And finally drop the currently copying prop, since we're done
1344 with this revision. */
1345 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1346 SVNSYNC_PROP_CURRENTLY_COPYING,
1347 NULL, NULL, subpool));
1349 /* Notify the user that we copied revision properties. */
1350 if (! rb->sb->quiet)
1351 SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1353 svn_pool_destroy(subpool);
1355 return SVN_NO_ERROR;
1358 /* Synchronize the repository associated with RA session TO_SESSION,
1359 * using information found in BATON, while the repository is
1360 * locked. Implements `with_locked_func_t' interface.
1362 static svn_error_t *
1363 do_synchronize(svn_ra_session_t *to_session,
1364 subcommand_baton_t *baton, apr_pool_t *pool)
1366 svn_string_t *last_merged_rev;
1367 svn_revnum_t from_latest;
1368 svn_ra_session_t *from_session;
1369 svn_string_t *currently_copying;
1370 svn_revnum_t to_latest, copying, last_merged;
1371 svn_revnum_t start_revision, end_revision;
1373 int normalized_rev_props_count = 0;
1375 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1376 baton->from_url, to_session,
1377 &(baton->source_callbacks), baton->config,
1380 /* Check to see if we have revprops that still need to be copied for
1381 a prior revision we didn't finish copying. But first, check for
1382 state sanity. Remember, mirroring is not an atomic action,
1383 because revision properties are copied separately from the
1384 revision's contents.
1386 So, any time that currently-copying is not set, then
1387 last-merged-rev should be the HEAD revision of the destination
1388 repository. That is, if we didn't fall over in the middle of a
1389 previous synchronization, then our destination repository should
1390 have exactly as many revisions in it as we've synchronized.
1392 Alternately, if currently-copying *is* set, it must
1393 be either last-merged-rev or last-merged-rev + 1, and the HEAD
1394 revision must be equal to either last-merged-rev or
1395 currently-copying. If this is not the case, somebody has meddled
1396 with the destination without using svnsync.
1399 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1400 ¤tly_copying, pool));
1402 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1404 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1406 if (currently_copying)
1408 copying = SVN_STR_TO_REV(currently_copying->data);
1410 if ((copying < last_merged)
1411 || (copying > (last_merged + 1))
1412 || ((to_latest != last_merged) && (to_latest != copying)))
1414 return svn_error_createf
1416 _("Revision being currently copied (%ld), last merged revision "
1417 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1418 "committed to the destination without using svnsync?"),
1419 copying, last_merged, to_latest);
1421 else if (copying == to_latest)
1423 if (copying > last_merged)
1425 SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
1426 baton->quiet, baton->source_prop_encoding,
1427 &normalized_rev_props_count, pool));
1428 last_merged = copying;
1429 last_merged_rev = svn_string_create
1430 (apr_psprintf(pool, "%ld", last_merged), pool);
1433 /* Now update last merged rev and drop currently changing.
1434 Note that the order here is significant, if we do them
1435 in the wrong order there are race conditions where we
1436 end up not being able to tell if there have been bogus
1437 (i.e. non-svnsync) commits to the dest repository. */
1439 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1440 SVNSYNC_PROP_LAST_MERGED_REV,
1441 NULL, last_merged_rev, pool));
1442 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1443 SVNSYNC_PROP_CURRENTLY_COPYING,
1446 /* If copying > to_latest, then we just fall through to
1447 attempting to copy the revision again. */
1451 if (to_latest != last_merged)
1452 return svn_error_createf(APR_EINVAL, NULL,
1453 _("Destination HEAD (%ld) is not the last "
1454 "merged revision (%ld); have you "
1455 "committed to the destination without "
1457 to_latest, last_merged);
1460 /* Now check to see if there are any revisions to copy. */
1461 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1463 if (from_latest < last_merged)
1464 return SVN_NO_ERROR;
1466 /* Ok, so there are new revisions, iterate over them copying them
1467 into the destination repository. */
1468 SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
1470 /* For compatibility with older svnserve versions, check first if we
1471 support adding revprops to the commit. */
1472 SVN_ERR(svn_ra_has_capability(rb->to_session,
1473 &rb->has_commit_revprops_capability,
1474 SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1477 start_revision = last_merged + 1;
1478 end_revision = from_latest;
1480 SVN_ERR(check_cancel(NULL));
1482 SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1483 0, TRUE, replay_rev_started,
1484 replay_rev_finished, rb, pool));
1486 SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
1487 + normalized_rev_props_count,
1488 rb->normalized_node_props_count,
1492 return SVN_NO_ERROR;
1496 /* SUBCOMMAND: sync */
1497 static svn_error_t *
1498 synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1500 svn_ra_session_t *to_session;
1501 opt_baton_t *opt_baton = b;
1502 apr_array_header_t *targets;
1503 subcommand_baton_t *baton;
1504 const char *to_url, *from_url;
1506 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1507 apr_array_make(pool, 0,
1508 sizeof(const char *)),
1510 if (targets->nelts < 1)
1511 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1512 if (targets->nelts > 2)
1513 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1515 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1516 if (! svn_path_is_url(to_url))
1517 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1518 _("Path '%s' is not a URL"), to_url);
1520 if (targets->nelts == 2)
1522 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1523 if (! svn_path_is_url(from_url))
1524 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1525 _("Path '%s' is not a URL"), from_url);
1529 from_url = NULL; /* we'll read it from the destination repos */
1532 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
1533 SVN_ERR(open_target_session(&to_session, baton, pool));
1534 if (opt_baton->disable_locking)
1535 SVN_ERR(do_synchronize(to_session, baton, pool));
1537 SVN_ERR(with_locked(to_session, do_synchronize, baton,
1538 opt_baton->steal_lock, pool));
1540 return SVN_NO_ERROR;
1545 /*** `svnsync copy-revprops' ***/
1547 /* Copy revision properties to the repository associated with RA
1548 * session TO_SESSION, using information found in BATON, while the
1549 * repository is locked. Implements `with_locked_func_t' interface.
1551 static svn_error_t *
1552 do_copy_revprops(svn_ra_session_t *to_session,
1553 subcommand_baton_t *baton, apr_pool_t *pool)
1555 svn_ra_session_t *from_session;
1556 svn_string_t *last_merged_rev;
1558 svn_revnum_t step = 1;
1559 int normalized_rev_props_count = 0;
1561 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1562 baton->from_url, to_session,
1563 &(baton->source_callbacks), baton->config,
1566 /* An invalid revision means "last-synced" */
1567 if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1568 baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1569 if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1570 baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1572 /* Make sure we have revisions within the valid range. */
1573 if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1574 return svn_error_createf
1576 _("Cannot copy revprops for a revision (%ld) that has not "
1577 "been synchronized yet"), baton->start_rev);
1578 if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1579 return svn_error_createf
1581 _("Cannot copy revprops for a revision (%ld) that has not "
1582 "been synchronized yet"), baton->end_rev);
1584 /* Now, copy all the requested revisions, in the requested order. */
1585 step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1586 for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1588 int normalized_count;
1589 SVN_ERR(check_cancel(NULL));
1590 SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
1591 baton->source_prop_encoding, &normalized_count,
1593 normalized_rev_props_count += normalized_count;
1596 /* Notify about normalized props, if any. */
1597 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
1599 return SVN_NO_ERROR;
1603 /* Set *START_REVNUM to the revision number associated with
1604 START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
1605 represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
1606 the revision number associated with END_REVISION or to
1607 SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
1608 END_REVNUM to the same value as START_REVNUM.
1610 As a special case, if neither START_REVISION nor END_REVISION is
1611 specified, set *START_REVNUM to 0 and set *END_REVNUM to
1614 Freak out if either START_REVISION or END_REVISION represents an
1615 explicit but invalid revision number. */
1616 static svn_error_t *
1617 resolve_revnums(svn_revnum_t *start_revnum,
1618 svn_revnum_t *end_revnum,
1619 svn_opt_revision_t start_revision,
1620 svn_opt_revision_t end_revision)
1622 svn_revnum_t start_rev, end_rev;
1624 /* Special case: neither revision is specified? This is like
1626 if ((start_revision.kind == svn_opt_revision_unspecified) &&
1627 (end_revision.kind == svn_opt_revision_unspecified))
1630 *end_revnum = SVN_INVALID_REVNUM;
1631 return SVN_NO_ERROR;
1634 /* Get the start revision, which must be either HEAD or a number
1635 (which is required to be a valid one). */
1636 if (start_revision.kind == svn_opt_revision_head)
1638 start_rev = SVN_INVALID_REVNUM;
1642 start_rev = start_revision.value.number;
1643 if (! SVN_IS_VALID_REVNUM(start_rev))
1644 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1645 _("Invalid revision number (%ld)"),
1649 /* Get the end revision, which must be unspecified (meaning,
1650 "same as the start_rev"), HEAD, or a number (which is
1651 required to be a valid one). */
1652 if (end_revision.kind == svn_opt_revision_unspecified)
1654 end_rev = start_rev;
1656 else if (end_revision.kind == svn_opt_revision_head)
1658 end_rev = SVN_INVALID_REVNUM;
1662 end_rev = end_revision.value.number;
1663 if (! SVN_IS_VALID_REVNUM(end_rev))
1664 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1665 _("Invalid revision number (%ld)"),
1669 *start_revnum = start_rev;
1670 *end_revnum = end_rev;
1671 return SVN_NO_ERROR;
1675 /* SUBCOMMAND: copy-revprops */
1676 static svn_error_t *
1677 copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1679 svn_ra_session_t *to_session;
1680 opt_baton_t *opt_baton = b;
1681 apr_array_header_t *targets;
1682 subcommand_baton_t *baton;
1683 const char *to_url = NULL;
1684 const char *from_url = NULL;
1685 svn_opt_revision_t start_revision, end_revision;
1686 svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1688 /* There should be either one or two arguments left to parse. */
1689 if (os->argc - os->ind > 2)
1690 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1691 if (os->argc - os->ind < 1)
1692 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1694 /* If there are two args, the last one is either a revision range or
1696 if (os->argc - os->ind == 2)
1698 const char *arg_str = os->argv[os->argc - 1];
1699 const char *utf_arg_str;
1701 SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
1703 if (! svn_path_is_url(utf_arg_str))
1705 /* This is the old "... TO_URL REV[:REV2]" syntax.
1706 Revisions come only from this argument. (We effectively
1707 pop that last argument from the end of the argument list
1708 so svn_opt__args_to_target_array() can do its thang.) */
1711 if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
1712 || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
1713 return svn_error_create(
1714 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1715 _("Cannot specify revisions via both command-line arguments "
1716 "and the --revision (-r) option"));
1718 start_revision.kind = svn_opt_revision_unspecified;
1719 end_revision.kind = svn_opt_revision_unspecified;
1720 if (svn_opt_parse_revision(&start_revision, &end_revision,
1721 arg_str, pool) != 0)
1722 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1723 _("Invalid revision range '%s' provided"),
1726 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1727 start_revision, end_revision));
1729 SVN_ERR(svn_opt__args_to_target_array(
1731 apr_array_make(pool, 1, sizeof(const char *)), pool));
1732 if (targets->nelts != 1)
1733 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1734 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1741 /* This is the "... TO_URL SOURCE_URL" syntax. Revisions
1742 come only from the --revision parameter. */
1743 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1744 opt_baton->start_rev, opt_baton->end_rev));
1746 SVN_ERR(svn_opt__args_to_target_array(
1748 apr_array_make(pool, 2, sizeof(const char *)), pool));
1749 if (targets->nelts < 1)
1750 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1751 if (targets->nelts > 2)
1752 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1753 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1754 if (targets->nelts == 2)
1755 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1760 if (! svn_path_is_url(to_url))
1761 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1762 _("Path '%s' is not a URL"), to_url);
1763 if (from_url && (! svn_path_is_url(from_url)))
1764 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1765 _("Path '%s' is not a URL"), from_url);
1767 baton = make_subcommand_baton(opt_baton, to_url, from_url,
1768 start_rev, end_rev, pool);
1769 SVN_ERR(open_target_session(&to_session, baton, pool));
1770 if (opt_baton->disable_locking)
1771 SVN_ERR(do_copy_revprops(to_session, baton, pool));
1773 SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
1774 opt_baton->steal_lock, pool));
1776 return SVN_NO_ERROR;
1781 /*** `svnsync info' ***/
1784 /* SUBCOMMAND: info */
1785 static svn_error_t *
1786 info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1788 svn_ra_session_t *to_session;
1789 opt_baton_t *opt_baton = b;
1790 apr_array_header_t *targets;
1791 subcommand_baton_t *baton;
1794 svn_string_t *from_url, *from_uuid, *last_merged_rev;
1796 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1797 apr_array_make(pool, 0,
1798 sizeof(const char *)),
1800 if (targets->nelts < 1)
1801 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1802 if (targets->nelts > 1)
1803 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1805 /* Get the mirror repository URL, and verify that it is URL-ish. */
1806 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1807 if (! svn_path_is_url(to_url))
1808 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1809 _("Path '%s' is not a URL"), to_url);
1811 /* Open an RA session to the mirror repository URL. */
1812 baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1813 SVN_ERR(open_target_session(&to_session, baton, pool));
1815 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
1817 from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
1820 return svn_error_createf
1821 (SVN_ERR_BAD_URL, NULL,
1822 _("Repository '%s' is not initialized for synchronization"), to_url);
1824 from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
1825 last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
1827 /* Print the info. */
1828 SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1830 SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1832 if (last_merged_rev)
1833 SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1834 last_merged_rev->data));
1835 return SVN_NO_ERROR;
1840 /*** `svnsync help' ***/
1843 /* SUBCOMMAND: help */
1844 static svn_error_t *
1845 help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1847 opt_baton_t *opt_baton = baton;
1849 const char *header =
1850 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
1851 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1852 "Type 'svnsync --version' to see the program version and RA modules.\n"
1854 "Available subcommands:\n");
1856 const char *ra_desc_start
1857 = _("The following repository access (RA) modules are available:\n\n");
1859 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1862 SVN_ERR(svn_ra_print_modules(version_footer, pool));
1864 SVN_ERR(svn_opt_print_help4(os, "svnsync",
1865 opt_baton ? opt_baton->version : FALSE,
1866 opt_baton ? opt_baton->quiet : FALSE,
1867 /*###opt_state ? opt_state->verbose :*/ FALSE,
1868 version_footer->data, header,
1869 svnsync_cmd_table, svnsync_options, NULL,
1872 return SVN_NO_ERROR;
1880 main(int argc, const char *argv[])
1882 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1883 apr_array_header_t *received_opts;
1884 opt_baton_t opt_baton;
1885 svn_config_t *config;
1886 apr_status_t apr_err;
1891 const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1892 const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1893 apr_array_header_t *config_options = NULL;
1894 const char *source_prop_encoding = NULL;
1895 svn_boolean_t force_interactive = FALSE;
1897 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
1899 return EXIT_FAILURE;
1902 err = check_lib_versions();
1904 return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
1906 /* Create our top-level pool. Use a separate mutexless allocator,
1907 * given this application is single threaded.
1909 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1911 err = svn_ra_initialize(pool);
1913 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1915 /* Initialize the option baton. */
1916 memset(&opt_baton, 0, sizeof(opt_baton));
1917 opt_baton.start_rev.kind = svn_opt_revision_unspecified;
1918 opt_baton.end_rev.kind = svn_opt_revision_unspecified;
1920 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1924 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
1925 svn_pool_destroy(pool);
1926 return EXIT_FAILURE;
1929 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1931 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1937 const char *opt_arg;
1938 svn_error_t* opt_err = NULL;
1940 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
1941 if (APR_STATUS_IS_EOF(apr_err))
1945 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
1946 svn_pool_destroy(pool);
1947 return EXIT_FAILURE;
1950 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1954 case svnsync_opt_non_interactive:
1955 opt_baton.non_interactive = TRUE;
1958 case svnsync_opt_force_interactive:
1959 force_interactive = TRUE;
1962 case svnsync_opt_trust_server_cert:
1963 opt_baton.trust_server_cert = TRUE;
1966 case svnsync_opt_no_auth_cache:
1967 opt_baton.no_auth_cache = TRUE;
1970 case svnsync_opt_auth_username:
1971 opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
1974 case svnsync_opt_auth_password:
1975 opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
1978 case svnsync_opt_source_username:
1979 opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
1982 case svnsync_opt_source_password:
1983 opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
1986 case svnsync_opt_sync_username:
1987 opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
1990 case svnsync_opt_sync_password:
1991 opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
1994 case svnsync_opt_config_dir:
1996 const char *path_utf8;
1997 opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
2000 opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
2003 case svnsync_opt_config_options:
2004 if (!config_options)
2006 apr_array_make(pool, 1,
2007 sizeof(svn_cmdline__config_argument_t*));
2009 err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
2011 err = svn_cmdline__parse_config_option(config_options,
2014 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2017 case svnsync_opt_source_prop_encoding:
2018 opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
2022 case svnsync_opt_disable_locking:
2023 opt_baton.disable_locking = TRUE;
2026 case svnsync_opt_steal_lock:
2027 opt_baton.steal_lock = TRUE;
2030 case svnsync_opt_version:
2031 opt_baton.version = TRUE;
2034 case svnsync_opt_allow_non_empty:
2035 opt_baton.allow_non_empty = TRUE;
2039 opt_baton.quiet = TRUE;
2043 if (svn_opt_parse_revision(&opt_baton.start_rev,
2045 opt_arg, pool) != 0)
2047 const char *utf8_opt_arg;
2048 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
2050 err = svn_error_createf(
2051 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2052 _("Syntax error in revision argument '%s'"),
2054 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2057 /* We only allow numbers and 'HEAD'. */
2058 if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
2059 (opt_baton.start_rev.kind != svn_opt_revision_head))
2060 || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
2061 (opt_baton.end_rev.kind != svn_opt_revision_head) &&
2062 (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
2064 err = svn_error_createf(
2065 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2066 _("Invalid revision range '%s' provided"), opt_arg);
2067 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2073 opt_baton.help = TRUE;
2078 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2079 svn_pool_destroy(pool);
2080 return EXIT_FAILURE;
2085 return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: ");
2089 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2091 /* The --non-interactive and --force-interactive options are mutually
2093 if (opt_baton.non_interactive && force_interactive)
2095 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2096 _("--non-interactive and --force-interactive "
2097 "are mutually exclusive"));
2098 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2101 opt_baton.non_interactive = !svn_cmdline__be_interactive(
2102 opt_baton.non_interactive,
2105 /* Disallow the mixing --username/password with their --source- and
2106 --sync- variants. Treat "--username FOO" as "--source-username
2107 FOO --sync-username FOO"; ditto for "--password FOO". */
2108 if ((username || password)
2109 && (source_username || sync_username
2110 || source_password || sync_password))
2112 err = svn_error_create
2113 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2114 _("Cannot use --username or --password with any of "
2115 "--source-username, --source-password, --sync-username, "
2116 "or --sync-password.\n"));
2117 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2121 source_username = username;
2122 sync_username = username;
2126 source_password = password;
2127 sync_password = password;
2129 opt_baton.source_username = source_username;
2130 opt_baton.source_password = source_password;
2131 opt_baton.sync_username = sync_username;
2132 opt_baton.sync_password = sync_password;
2134 /* Disallow mixing of --steal-lock and --disable-locking. */
2135 if (opt_baton.steal_lock && opt_baton.disable_locking)
2137 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2138 _("--disable-locking and --steal-lock are "
2139 "mutually exclusive"));
2140 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2143 /* --trust-server-cert can only be used with --non-interactive */
2144 if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
2146 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2147 _("--trust-server-cert requires "
2148 "--non-interactive"));
2149 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2152 err = svn_config_ensure(opt_baton.config_dir, pool);
2154 return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
2156 if (subcommand == NULL)
2158 if (os->ind >= os->argc)
2160 if (opt_baton.version)
2162 /* Use the "help" subcommand to handle "--version". */
2163 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2164 { "--version", help_cmd, {0}, "",
2165 {svnsync_opt_version, /* must accept its own option */
2169 subcommand = &pseudo_cmd;
2173 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2174 svn_pool_destroy(pool);
2175 return EXIT_FAILURE;
2180 const char *first_arg = os->argv[os->ind++];
2181 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2183 if (subcommand == NULL)
2185 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2186 svn_pool_destroy(pool);
2187 return EXIT_FAILURE;
2192 for (i = 0; i < received_opts->nelts; ++i)
2194 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2196 if (opt_id == 'h' || opt_id == '?')
2199 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2202 const apr_getopt_option_t *badopt =
2203 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2205 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2206 if (subcommand->name[0] == '-')
2208 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
2212 err = svn_error_createf
2213 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2214 _("Subcommand '%s' doesn't accept option '%s'\n"
2215 "Type 'svnsync help %s' for usage.\n"),
2216 subcommand->name, optstr, subcommand->name);
2217 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2222 err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
2224 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2226 /* Update the options in the config */
2230 svn_cmdline__apply_config_options(opt_baton.config, config_options,
2231 "svnsync: ", "--config-option"));
2234 config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
2236 opt_baton.source_prop_encoding = source_prop_encoding;
2238 apr_signal(SIGINT, signal_handler);
2241 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2242 apr_signal(SIGBREAK, signal_handler);
2246 apr_signal(SIGHUP, signal_handler);
2250 apr_signal(SIGTERM, signal_handler);
2254 /* Disable SIGPIPE generation for the platforms that have it. */
2255 apr_signal(SIGPIPE, SIG_IGN);
2259 /* Disable SIGXFSZ generation for the platforms that have it,
2260 otherwise working with large files when compiled against an APR
2261 that doesn't have large file support will crash the program,
2263 apr_signal(SIGXFSZ, SIG_IGN);
2266 err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton,
2267 opt_baton.non_interactive,
2268 opt_baton.source_username,
2269 opt_baton.source_password,
2270 opt_baton.config_dir,
2271 opt_baton.no_auth_cache,
2272 opt_baton.trust_server_cert,
2277 err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton,
2278 opt_baton.non_interactive,
2279 opt_baton.sync_username,
2280 opt_baton.sync_password,
2281 opt_baton.config_dir,
2282 opt_baton.no_auth_cache,
2283 opt_baton.trust_server_cert,
2288 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2291 /* For argument-related problems, suggest using the 'help'
2293 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2294 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2296 err = svn_error_quick_wrap(err,
2297 _("Try 'svnsync help' for more info"));
2300 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2303 svn_pool_destroy(pool);
2305 return EXIT_SUCCESS;