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"
48 static svn_opt_subcommand_t initialize_cmd,
55 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
56 svnsync_opt_force_interactive,
57 svnsync_opt_no_auth_cache,
58 svnsync_opt_auth_username,
59 svnsync_opt_auth_password,
60 svnsync_opt_source_username,
61 svnsync_opt_source_password,
62 svnsync_opt_sync_username,
63 svnsync_opt_sync_password,
64 svnsync_opt_config_dir,
65 svnsync_opt_config_options,
66 svnsync_opt_source_prop_encoding,
67 svnsync_opt_disable_locking,
69 svnsync_opt_trust_server_cert,
70 svnsync_opt_trust_server_cert_failures_src,
71 svnsync_opt_trust_server_cert_failures_dst,
72 svnsync_opt_allow_non_empty,
73 svnsync_opt_skip_unchanged,
74 svnsync_opt_steal_lock
77 #define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
78 svnsync_opt_force_interactive, \
79 svnsync_opt_no_auth_cache, \
80 svnsync_opt_auth_username, \
81 svnsync_opt_auth_password, \
82 svnsync_opt_trust_server_cert, \
83 svnsync_opt_trust_server_cert_failures_src, \
84 svnsync_opt_trust_server_cert_failures_dst, \
85 svnsync_opt_source_username, \
86 svnsync_opt_source_password, \
87 svnsync_opt_sync_username, \
88 svnsync_opt_sync_password, \
89 svnsync_opt_config_dir, \
90 svnsync_opt_config_options
92 static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
94 { "initialize", initialize_cmd, { "init" },
95 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
97 "Initialize a destination repository for synchronization from\n"
98 "another repository.\n"
100 "If the source URL is not the root of a repository, only the\n"
101 "specified part of the repository will be synchronized.\n"
103 "The destination URL must point to the root of a repository which\n"
104 "has been configured to allow revision property changes. In\n"
105 "the general case, the destination repository must contain no\n"
106 "committed revisions. Use --allow-non-empty to override this\n"
107 "restriction, which will cause svnsync to assume that any revisions\n"
108 "already present in the destination repository perfectly mirror\n"
109 "their counterparts in the source repository. (This is useful\n"
110 "when initializing a copy of a repository as a mirror of that same\n"
111 "repository, for example.)\n"
113 "You should not commit to, or make revision property changes in,\n"
114 "the destination repository by any method other than 'svnsync'.\n"
115 "In other words, the destination repository should be a read-only\n"
116 "mirror of the source repository.\n"),
117 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
118 svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
119 svnsync_opt_steal_lock, 'M' } },
120 { "synchronize", synchronize_cmd, { "sync" },
121 N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
123 "Transfer all pending revisions to the destination from the source\n"
124 "with which it was initialized.\n"
126 "If SOURCE_URL is provided, use that as the source repository URL,\n"
127 "ignoring what is recorded in the destination repository as the\n"
128 "source URL. Specifying SOURCE_URL is recommended in particular\n"
129 "if untrusted users/administrators may have write access to the\n"
130 "DEST_URL repository.\n"),
131 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
132 svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } },
133 { "copy-revprops", copy_revprops_cmd, { 0 },
136 " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
137 " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
139 "Copy the revision properties in a given range of revisions to the\n"
140 "destination from the source with which it was initialized. If the\n"
141 "revision range is not specified, it defaults to all revisions in\n"
142 "the DEST_URL repository. Note also that the 'HEAD' revision is the\n"
143 "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
145 "If SOURCE_URL is provided, use that as the source repository URL,\n"
146 "ignoring what is recorded in the destination repository as the\n"
147 "source URL. Specifying SOURCE_URL is recommended in particular\n"
148 "if untrusted users/administrators may have write access to the\n"
149 "DEST_URL repository.\n"
151 "Unless you need to trigger the destination repositoy's revprop\n"
152 "change hooks for all revision properties, it is recommended to use\n"
153 "the --skip-unchanged option for best performance.\n"
155 "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
156 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
157 svnsync_opt_disable_locking, svnsync_opt_steal_lock,
158 svnsync_opt_skip_unchanged, 'M' } },
159 { "info", info_cmd, { 0 },
160 N_("usage: svnsync info DEST_URL\n"
162 "Print information about the synchronization destination repository\n"
163 "located at DEST_URL.\n"),
164 { SVNSYNC_OPTS_DEFAULT } },
165 { "help", help_cmd, { "?", "h" },
166 N_("usage: svnsync help [SUBCOMMAND...]\n"
168 "Describe the usage of this program or its subcommands.\n"),
170 { NULL, NULL, { 0 }, NULL, { 0 } }
173 static const apr_getopt_option_t svnsync_options[] =
176 N_("print as little as possible") },
178 N_("operate on revision ARG (or range ARG1:ARG2)\n"
180 "A revision argument can be one of:\n"
182 " NUMBER revision number\n"
184 " 'HEAD' latest in repository") },
185 {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
186 N_("allow a non-empty destination repository") },
187 {"skip-unchanged", svnsync_opt_skip_unchanged, 0,
188 N_("don't copy unchanged revision properties") },
189 {"non-interactive", svnsync_opt_non_interactive, 0,
190 N_("do no interactive prompting (default is to prompt\n"
192 "only if standard input is a terminal device)")},
193 {"force-interactive", svnsync_opt_force_interactive, 0,
194 N_("do interactive prompting even if standard input\n"
196 "is not a terminal device")},
197 {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
198 N_("do not cache authentication tokens") },
199 {"username", svnsync_opt_auth_username, 1,
200 N_("specify a username ARG (deprecated;\n"
202 "see --source-username and --sync-username)") },
203 {"password", svnsync_opt_auth_password, 1,
204 N_("specify a password ARG (deprecated;\n"
206 "see --source-password and --sync-password)") },
207 {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
208 N_("deprecated; same as\n"
210 "--source-trust-server-cert-failures=unknown-ca\n"
212 "--sync-trust-server-cert-failures=unknown-ca")},
213 {"source-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_src, 1,
214 N_("with --non-interactive, accept SSL\n"
216 "server certificates with failures.\n"
218 "ARG is a comma-separated list of:\n"
220 "- 'unknown-ca' (Unknown Authority)\n"
222 "- 'cn-mismatch' (Hostname mismatch)\n"
224 "- 'expired' (Expired certificate)\n"
226 "- 'not-yet-valid' (Not yet valid certificate)\n"
228 "- 'other' (all other not separately classified\n"
230 " certificate errors).\n"
232 "Applied to the source URL.")},
233 {"sync-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_dst, 1,
236 "--source-trust-server-cert-failures,\n"
238 "but applied to the destination URL.")},
239 {"source-username", svnsync_opt_source_username, 1,
240 N_("connect to source repository with username ARG") },
241 {"source-password", svnsync_opt_source_password, 1,
242 N_("connect to source repository with password ARG") },
243 {"sync-username", svnsync_opt_sync_username, 1,
244 N_("connect to sync repository with username ARG") },
245 {"sync-password", svnsync_opt_sync_password, 1,
246 N_("connect to sync repository with password ARG") },
247 {"config-dir", svnsync_opt_config_dir, 1,
248 N_("read user configuration files from directory ARG")},
249 {"config-option", svnsync_opt_config_options, 1,
250 N_("set user configuration option in the format:\n"
252 " FILE:SECTION:OPTION=[VALUE]\n"
256 " servers:global:http-library=serf")},
257 {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
258 N_("convert translatable properties from encoding ARG\n"
260 "to UTF-8. If not specified, then properties are\n"
262 "presumed to be encoded in UTF-8.")},
263 {"disable-locking", svnsync_opt_disable_locking, 0,
264 N_("Disable built-in locking. Use of this option can\n"
266 "corrupt the mirror unless you ensure that no other\n"
268 "instance of svnsync is running concurrently.")},
269 {"steal-lock", svnsync_opt_steal_lock, 0,
270 N_("Steal locks as necessary. Use, with caution,\n"
272 "if your mirror repository contains stale locks\n"
274 "and is not being concurrently accessed by another\n"
276 "svnsync instance.")},
277 {"memory-cache-size", 'M', 1,
278 N_("size of the extra in-memory cache in MB used to\n"
280 "minimize operations for local 'file' scheme.\n")},
281 {"version", svnsync_opt_version, 0,
282 N_("show program version information")},
284 N_("show help on a subcommand")},
286 N_("show help on a subcommand")},
290 typedef struct opt_baton_t {
291 svn_boolean_t non_interactive;
293 svn_boolean_t trust_server_cert_unknown_ca;
294 svn_boolean_t trust_server_cert_cn_mismatch;
295 svn_boolean_t trust_server_cert_expired;
296 svn_boolean_t trust_server_cert_not_yet_valid;
297 svn_boolean_t trust_server_cert_other_failure;
298 } src_trust, dst_trust;
299 svn_boolean_t no_auth_cache;
300 svn_auth_baton_t *source_auth_baton;
301 svn_auth_baton_t *sync_auth_baton;
302 const char *source_username;
303 const char *source_password;
304 const char *sync_username;
305 const char *sync_password;
306 const char *config_dir;
308 const char *source_prop_encoding;
309 svn_boolean_t disable_locking;
310 svn_boolean_t steal_lock;
312 svn_boolean_t allow_non_empty;
313 svn_boolean_t skip_unchanged;
314 svn_boolean_t version;
316 svn_opt_revision_t start_rev;
317 svn_opt_revision_t end_rev;
323 /*** Helper functions ***/
326 /* Cancellation callback function. */
327 static svn_cancel_func_t check_cancel = 0;
329 /* Check that the version of libraries in use match what we expect. */
331 check_lib_versions(void)
333 static const svn_version_checklist_t checklist[] =
335 { "svn_subr", svn_subr_version },
336 { "svn_delta", svn_delta_version },
337 { "svn_ra", svn_ra_version },
340 SVN_VERSION_DEFINE(my_version);
342 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
346 /* Implements `svn_ra__lock_retry_func_t'. */
348 lock_retry_func(void *baton,
349 const svn_string_t *reposlocktoken,
352 return svn_cmdline_printf(pool,
353 _("Failed to get lock on destination "
354 "repos, currently held by '%s'\n"),
355 reposlocktoken->data);
358 /* Acquire a lock (of sorts) on the repository associated with the
359 * given RA SESSION. This lock is just a revprop change attempt in a
360 * time-delay loop. This function is duplicated by svnrdump in
361 * svnrdump/load_editor.c
364 get_lock(const svn_string_t **lock_string_p,
365 svn_ra_session_t *session,
366 svn_boolean_t steal_lock,
370 svn_boolean_t be_atomic;
371 const svn_string_t *stolen_lock;
373 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
374 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
378 /* Pre-1.7 server. Can't lock without a race condition.
381 err = svn_error_create(
382 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
383 _("Target server does not support atomic revision property "
384 "edits; consider upgrading it to 1.7 or using an external "
386 svn_handle_warning2(stderr, err, "svnsync: ");
387 svn_error_clear(err);
390 err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
391 SVNSYNC_PROP_LOCK, steal_lock,
392 10 /* retries */, lock_retry_func, NULL,
393 check_cancel, NULL, pool);
394 if (!err && stolen_lock)
396 return svn_cmdline_printf(pool,
397 _("Stole lock previously held by '%s'\n"),
404 /* Baton for the various subcommands to share. */
405 typedef struct subcommand_baton_t {
406 /* common to all subcommands */
408 svn_ra_callbacks2_t source_callbacks;
409 svn_ra_callbacks2_t sync_callbacks;
411 svn_boolean_t allow_non_empty;
412 svn_boolean_t skip_unchanged; /* Enable optimization for revprop changes. */
415 /* initialize, synchronize, and copy-revprops only */
416 const char *source_prop_encoding;
418 /* initialize only */
419 const char *from_url;
421 /* synchronize only */
422 svn_revnum_t committed_rev;
424 /* copy-revprops only */
425 svn_revnum_t start_rev;
426 svn_revnum_t end_rev;
428 } subcommand_baton_t;
430 typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
431 subcommand_baton_t *baton,
435 /* Lock the repository associated with RA SESSION, then execute the
436 * given FUNC/BATON pair while holding the lock. Finally, drop the
437 * lock once it finishes.
440 with_locked(svn_ra_session_t *session,
441 with_locked_func_t func,
442 subcommand_baton_t *baton,
443 svn_boolean_t steal_lock,
446 const svn_string_t *lock_string;
449 SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
451 err = func(session, baton, pool);
452 return svn_error_compose_create(err,
453 svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
458 /* Callback function for the RA session's open_tmp_file()
462 open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
464 return svn_io_open_unique_file3(fp, NULL, NULL,
465 svn_io_file_del_on_pool_cleanup,
470 /* Return SVN_NO_ERROR iff URL identifies the root directory of the
471 * repository associated with RA session SESS.
474 check_if_session_is_at_repos_root(svn_ra_session_t *sess,
478 const char *sess_root;
480 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
482 if (strcmp(url, sess_root) == 0)
485 return svn_error_createf
487 _("Session is rooted at '%s' but the repos root is '%s'"),
492 /* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
493 * revision REV of the repository associated with RA session SESSION.
495 * For REV zero, don't remove properties with the "svn:sync-" prefix.
497 * All allocations will be done in a subpool of POOL.
500 remove_props_not_in_source(svn_ra_session_t *session,
502 apr_hash_t *source_props,
503 apr_hash_t *target_props,
506 apr_pool_t *subpool = svn_pool_create(pool);
507 apr_hash_index_t *hi;
509 for (hi = apr_hash_first(pool, target_props);
511 hi = apr_hash_next(hi))
513 const char *propname = apr_hash_this_key(hi);
515 svn_pool_clear(subpool);
517 if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
518 sizeof(SVNSYNC_PROP_PREFIX) - 1))
521 /* Delete property if the name can't be found in SOURCE_PROPS. */
522 if (! svn_hash_gets(source_props, propname))
523 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
527 svn_pool_destroy(subpool);
532 /* Filter callback function.
533 * Takes a property name KEY, and is expected to return TRUE if the property
534 * should be filtered out (ie. not be copied to the target list), or FALSE if
537 typedef svn_boolean_t (*filter_func_t)(const char *key);
539 /* Make a new set of properties, by copying those properties in PROPS for which
540 * the filter FILTER returns FALSE.
542 * The number of properties not copied will be stored in FILTERED_COUNT.
544 * The returned set of properties is allocated from POOL.
547 filter_props(int *filtered_count, apr_hash_t *props,
548 filter_func_t filter,
551 apr_hash_index_t *hi;
552 apr_hash_t *filtered = apr_hash_make(pool);
555 for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
557 const char *propname = apr_hash_this_key(hi);
558 void *propval = apr_hash_this_val(hi);
560 /* Copy all properties:
561 - not matching the exclude pattern if provided OR
562 - matching the include pattern if provided */
563 if (!filter || !filter(propname))
565 svn_hash_sets(filtered, propname, propval);
569 *filtered_count += 1;
577 /* Write the set of revision properties REV_PROPS to revision REV to the
578 * repository associated with RA session SESSION.
579 * Omit any properties whose names are in the svnsync property name space,
580 * and set *FILTERED_COUNT to the number of properties thus omitted.
581 * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
583 * If OLD_REV_PROPS is not NULL, skip all properties that did not change.
584 * Note that this implies that hook scripts won't be triggered anymore for
585 * those revprops that did not change.
587 * All allocations will be done in a subpool of POOL.
590 write_revprops(int *filtered_count,
591 svn_ra_session_t *session,
593 apr_hash_t *rev_props,
594 apr_hash_t *old_rev_props,
597 apr_pool_t *subpool = svn_pool_create(pool);
598 apr_hash_index_t *hi;
602 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
604 const char *propname = apr_hash_this_key(hi);
605 const svn_string_t *propval = apr_hash_this_val(hi);
607 svn_pool_clear(subpool);
609 if (strncmp(propname, SVNSYNC_PROP_PREFIX,
610 sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
614 /* Skip the RA call for any no-op propset. */
615 const svn_string_t *old_value = svn_hash_gets(old_rev_props,
617 if ((!old_value && !propval)
618 || (old_value && propval
619 && svn_string_compare(old_value, propval)))
623 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
628 *filtered_count += 1;
632 svn_pool_destroy(subpool);
639 log_properties_copied(svn_boolean_t syncprops_found,
644 SVN_ERR(svn_cmdline_printf(pool,
645 _("Copied properties for revision %ld "
646 "(%s* properties skipped).\n"),
647 rev, SVNSYNC_PROP_PREFIX));
649 SVN_ERR(svn_cmdline_printf(pool,
650 _("Copied properties for revision %ld.\n"),
656 /* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
657 * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
658 * endings, if either of those numbers is non-zero. */
660 log_properties_normalized(int normalized_rev_props_count,
661 int normalized_node_props_count,
664 if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
665 SVN_ERR(svn_cmdline_printf(pool,
666 _("NOTE: Normalized %s* properties "
667 "to LF line endings (%d rev-props, "
668 "%d node-props).\n"),
670 normalized_rev_props_count,
671 normalized_node_props_count));
676 /* Copy all the revision properties, except for those that have the
677 * "svn:sync-" prefix, from revision REV of the repository associated
678 * with RA session FROM_SESSION, to the repository associated with RA
679 * session TO_SESSION.
681 * If SYNC is TRUE, then properties on the destination revision that
682 * do not exist on the source revision will be removed.
684 * If SKIP_UNCHANGED is TRUE, skip any no-op revprop changes. This also
685 * prevents hook scripts from firing for those unchanged revprops. Has
686 * no effect if SYNC is FALSE.
688 * If QUIET is FALSE, then log_properties_copied() is called to log that
689 * properties were copied for revision REV.
691 * Make sure the values of svn:* revision properties use only LF (\n)
692 * line ending style, correcting their values as necessary. The number
693 * of properties that were normalized is returned in *NORMALIZED_COUNT.
696 copy_revprops(svn_ra_session_t *from_session,
697 svn_ra_session_t *to_session,
700 svn_boolean_t skip_unchanged,
702 const char *source_prop_encoding,
703 int *normalized_count,
706 apr_pool_t *subpool = svn_pool_create(pool);
707 apr_hash_t *existing_props, *rev_props;
708 int filtered_count = 0;
710 /* Get the list of revision properties on REV of TARGET. We're only interested
711 in the property names, but we'll get the values 'for free'. */
713 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
715 existing_props = NULL;
717 /* Get the list of revision properties on REV of SOURCE. */
718 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
720 /* If necessary, normalize encoding and line ending style and return the count
721 of EOL-normalized properties in int *NORMALIZED_COUNT. */
722 SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
723 source_prop_encoding, pool));
725 /* Copy all but the svn:svnsync properties. */
726 SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props,
727 skip_unchanged ? existing_props : NULL, pool));
729 /* Delete those properties that were in TARGET but not in SOURCE */
731 SVN_ERR(remove_props_not_in_source(to_session, rev,
732 rev_props, existing_props, pool));
735 SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
737 svn_pool_destroy(subpool);
743 /* Return a subcommand baton allocated from POOL and populated with
744 data from the provided parameters, which include the global
745 OPT_BATON options structure and a handful of other options. Not
746 all parameters are used in all subcommands -- see
747 subcommand_baton_t's definition for details. */
748 static subcommand_baton_t *
749 make_subcommand_baton(opt_baton_t *opt_baton,
751 const char *from_url,
752 svn_revnum_t start_rev,
753 svn_revnum_t end_rev,
756 subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
757 b->config = opt_baton->config;
758 b->source_callbacks.open_tmp_file = open_tmp_file;
759 b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
760 b->sync_callbacks.open_tmp_file = open_tmp_file;
761 b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
762 b->quiet = opt_baton->quiet;
763 b->skip_unchanged = opt_baton->skip_unchanged;
764 b->allow_non_empty = opt_baton->allow_non_empty;
766 b->source_prop_encoding = opt_baton->source_prop_encoding;
767 b->from_url = from_url;
768 b->start_rev = start_rev;
769 b->end_rev = end_rev;
774 open_target_session(svn_ra_session_t **to_session_p,
775 subcommand_baton_t *baton,
779 /*** `svnsync init' ***/
781 /* Initialize the repository associated with RA session TO_SESSION,
782 * using information found in BATON.
784 * Implements `with_locked_func_t' interface. The caller has
785 * acquired a lock on the repository if locking is needed.
788 do_initialize(svn_ra_session_t *to_session,
789 subcommand_baton_t *baton,
792 svn_ra_session_t *from_session;
793 svn_string_t *from_url;
794 svn_revnum_t latest, from_latest;
795 const char *uuid, *root_url;
796 int normalized_rev_props_count;
798 /* First, sanity check to see that we're copying into a brand new
799 repos. If we aren't, and we aren't being asked to forcibly
800 complete this initialization, that's a bad news. */
801 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
802 if ((latest != 0) && (! baton->allow_non_empty))
803 return svn_error_create
805 _("Destination repository already contains revision history; consider "
806 "using --allow-non-empty if the repository's revisions are known "
807 "to mirror their respective revisions in the source repository"));
809 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
811 if (from_url && (! baton->allow_non_empty))
812 return svn_error_createf
814 _("Destination repository is already synchronizing from '%s'"),
817 /* Now fill in our bookkeeping info in the dest repository. */
819 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
820 &(baton->source_callbacks), baton,
821 baton->config, pool));
822 SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
824 /* If we're doing a partial replay, we have to check first if the server
826 if (strcmp(root_url, baton->from_url) != 0)
828 svn_boolean_t server_supports_partial_replay;
829 svn_error_t *err = svn_ra_has_capability(from_session,
830 &server_supports_partial_replay,
831 SVN_RA_CAPABILITY_PARTIAL_REPLAY,
833 if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
834 return svn_error_trace(err);
836 if (err || !server_supports_partial_replay)
837 return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
841 /* If we're initializing a non-empty destination, we'll make sure
842 that it at least doesn't have more revisions than the source. */
845 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
846 if (from_latest < latest)
847 return svn_error_create
849 _("Destination repository has more revisions than source "
853 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
854 svn_string_create(baton->from_url, pool),
857 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
858 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
859 svn_string_create(uuid, pool), pool));
861 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
862 NULL, svn_string_createf(pool, "%ld", latest),
865 /* Copy all non-svnsync revprops from the LATEST rev in the source
866 repository into the destination, notifying about normalized
867 props, if any. When LATEST is 0, this serves the practical
868 purpose of initializing data that would otherwise be overlooked
869 by the sync process (which is going to begin with r1). When
870 LATEST is not 0, this really serves merely aesthetic and
871 informational purposes, keeping the output of this command
872 consistent while allowing folks to see what the latest revision is. */
873 SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, FALSE,
874 baton->quiet, baton->source_prop_encoding,
875 &normalized_rev_props_count, pool));
877 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
879 /* TODO: It would be nice if we could set the dest repos UUID to be
880 equal to the UUID of the source repos, at least optionally. That
881 way people could check out/log/diff using a local fast mirror,
882 but switch --relocate to the actual final repository in order to
883 make changes... But at this time, the RA layer doesn't have a
884 way to set a UUID. */
890 /* SUBCOMMAND: init */
892 initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
894 const char *to_url, *from_url;
895 svn_ra_session_t *to_session;
896 opt_baton_t *opt_baton = b;
897 apr_array_header_t *targets;
898 subcommand_baton_t *baton;
900 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
901 apr_array_make(pool, 0,
902 sizeof(const char *)),
904 if (targets->nelts < 2)
905 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
906 if (targets->nelts > 2)
907 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
909 to_url = APR_ARRAY_IDX(targets, 0, const char *);
910 from_url = APR_ARRAY_IDX(targets, 1, const char *);
912 if (! svn_path_is_url(to_url))
913 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
914 _("Path '%s' is not a URL"), to_url);
915 if (! svn_path_is_url(from_url))
916 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
917 _("Path '%s' is not a URL"), from_url);
919 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
920 SVN_ERR(open_target_session(&to_session, baton, pool));
921 if (opt_baton->disable_locking)
922 SVN_ERR(do_initialize(to_session, baton, pool));
924 SVN_ERR(with_locked(to_session, do_initialize, baton,
925 opt_baton->steal_lock, pool));
932 /*** `svnsync sync' ***/
934 /* Implements `svn_commit_callback2_t' interface. */
936 commit_callback(const svn_commit_info_t *commit_info,
940 subcommand_baton_t *sb = baton;
944 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
945 commit_info->revision));
948 sb->committed_rev = commit_info->revision;
954 /* Set *FROM_SESSION to an RA session associated with the source
955 * repository of the synchronization. If FROM_URL is non-NULL, use it
956 * as the source repository URL; otherwise, determine the source
957 * repository URL by reading svn:sync- properties from the destination
958 * repository (associated with TO_SESSION). Set LAST_MERGED_REV to
959 * the value of the property which records the most recently
960 * synchronized revision.
962 * CALLBACKS is a vtable of RA callbacks to provide when creating
963 * *FROM_SESSION. CONFIG is a configuration hash.
966 open_source_session(svn_ra_session_t **from_session,
967 svn_string_t **last_merged_rev,
968 const char *from_url,
969 svn_ra_session_t *to_session,
970 svn_ra_callbacks2_t *callbacks,
976 svn_string_t *from_url_str, *from_uuid_str;
978 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
980 from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
981 from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
982 *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
984 if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
985 return svn_error_create
987 _("Destination repository has not been initialized"));
989 /* ### TODO: Should we validate that FROM_URL_STR->data matches any
990 provided FROM_URL here? */
992 SVN_ERR(svn_opt__arg_canonicalize_url(&from_url, from_url_str->data,
995 /* Open the session to copy the revision data. */
996 SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
997 callbacks, baton, config, pool));
1002 /* Set *TARGET_SESSION_P to an RA session associated with the target
1003 * repository of the synchronization.
1005 static svn_error_t *
1006 open_target_session(svn_ra_session_t **target_session_p,
1007 subcommand_baton_t *baton,
1010 svn_ra_session_t *target_session;
1011 SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
1012 &(baton->sync_callbacks), baton, baton->config, pool));
1013 SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
1015 *target_session_p = target_session;
1016 return SVN_NO_ERROR;
1019 /* Replay baton, used during synchronization. */
1020 typedef struct replay_baton_t {
1021 svn_ra_session_t *from_session;
1022 svn_ra_session_t *to_session;
1023 svn_revnum_t current_revision;
1024 subcommand_baton_t *sb;
1025 svn_boolean_t has_commit_revprops_capability;
1026 svn_boolean_t has_atomic_revprops_capability;
1027 int normalized_rev_props_count;
1028 int normalized_node_props_count;
1029 const char *to_root;
1031 #ifdef ENABLE_EV2_SHIMS
1032 /* Extra 'backdoor' session for fetching data *from* the target repo. */
1033 svn_ra_session_t *extra_to_session;
1037 /* Return a replay baton allocated from POOL and populated with
1038 data from the provided parameters. */
1039 static svn_error_t *
1040 make_replay_baton(replay_baton_t **baton_p,
1041 svn_ra_session_t *from_session,
1042 svn_ra_session_t *to_session,
1043 subcommand_baton_t *sb, apr_pool_t *pool)
1045 replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
1046 rb->from_session = from_session;
1047 rb->to_session = to_session;
1050 SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
1052 #ifdef ENABLE_EV2_SHIMS
1053 /* Open up the extra baton. Only needed for Ev2 shims. */
1054 SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
1058 return SVN_NO_ERROR;
1061 /* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
1062 * property. Implements filter_func_t. Use with filter_props() to filter out
1063 * svn:date and svn:author and svnsync properties.
1065 static svn_boolean_t
1066 filter_exclude_date_author_sync(const char *key)
1068 if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1070 else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1072 else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1073 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1079 /* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
1080 * property. Implements filter_func_t. Use with filter_props() to filter out
1081 * all properties except svn:date and svn:author and svnsync properties.
1083 static svn_boolean_t
1084 filter_include_date_author_sync(const char *key)
1086 return ! filter_exclude_date_author_sync(key);
1090 /* Return TRUE iff KEY is the name of the svn:log property.
1091 * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
1093 static svn_boolean_t
1094 filter_exclude_log(const char *key)
1096 if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1102 /* Return FALSE iff KEY is the name of the svn:log property.
1103 * Implements filter_func_t. Use with filter_props() to only include svn:log.
1105 static svn_boolean_t
1106 filter_include_log(const char *key)
1108 return ! filter_exclude_log(key);
1111 #ifdef ENABLE_EV2_SHIMS
1112 static svn_error_t *
1113 fetch_base_func(const char **filename,
1116 svn_revnum_t base_revision,
1117 apr_pool_t *result_pool,
1118 apr_pool_t *scratch_pool)
1120 struct replay_baton_t *rb = baton;
1121 svn_stream_t *fstream;
1124 if (svn_path_is_url(path))
1125 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1126 else if (path[0] == '/')
1129 if (! SVN_IS_VALID_REVNUM(base_revision))
1130 base_revision = rb->current_revision - 1;
1132 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1133 svn_io_file_del_on_pool_cleanup,
1134 result_pool, scratch_pool));
1136 err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
1137 fstream, NULL, NULL, scratch_pool);
1138 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1140 svn_error_clear(err);
1141 SVN_ERR(svn_stream_close(fstream));
1144 return SVN_NO_ERROR;
1147 return svn_error_trace(err);
1149 SVN_ERR(svn_stream_close(fstream));
1151 return SVN_NO_ERROR;
1154 static svn_error_t *
1155 fetch_props_func(apr_hash_t **props,
1158 svn_revnum_t base_revision,
1159 apr_pool_t *result_pool,
1160 apr_pool_t *scratch_pool)
1162 struct replay_baton_t *rb = baton;
1163 svn_node_kind_t node_kind;
1165 if (svn_path_is_url(path))
1166 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1167 else if (path[0] == '/')
1170 if (! SVN_IS_VALID_REVNUM(base_revision))
1171 base_revision = rb->current_revision - 1;
1173 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1174 &node_kind, scratch_pool));
1176 if (node_kind == svn_node_file)
1178 SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
1179 NULL, NULL, props, result_pool));
1181 else if (node_kind == svn_node_dir)
1183 apr_array_header_t *tmp_props;
1185 SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
1186 base_revision, 0 /* Dirent fields */,
1188 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1189 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1191 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1195 *props = apr_hash_make(result_pool);
1198 return SVN_NO_ERROR;
1201 static svn_error_t *
1202 fetch_kind_func(svn_node_kind_t *kind,
1205 svn_revnum_t base_revision,
1206 apr_pool_t *scratch_pool)
1208 struct replay_baton_t *rb = baton;
1210 if (svn_path_is_url(path))
1211 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1212 else if (path[0] == '/')
1215 if (! SVN_IS_VALID_REVNUM(base_revision))
1216 base_revision = rb->current_revision - 1;
1218 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1219 kind, scratch_pool));
1221 return SVN_NO_ERROR;
1225 static svn_delta_shim_callbacks_t *
1226 get_shim_callbacks(replay_baton_t *rb,
1227 apr_pool_t *result_pool)
1229 svn_delta_shim_callbacks_t *callbacks =
1230 svn_delta_shim_callbacks_default(result_pool);
1232 callbacks->fetch_props_func = fetch_props_func;
1233 callbacks->fetch_kind_func = fetch_kind_func;
1234 callbacks->fetch_base_func = fetch_base_func;
1235 callbacks->fetch_baton = rb;
1242 /* Callback function for svn_ra_replay_range, invoked when starting to parse
1245 static svn_error_t *
1246 replay_rev_started(svn_revnum_t revision,
1248 const svn_delta_editor_t **editor,
1250 apr_hash_t *rev_props,
1253 const svn_delta_editor_t *commit_editor;
1254 const svn_delta_editor_t *cancel_editor;
1255 const svn_delta_editor_t *sync_editor;
1259 replay_baton_t *rb = replay_baton;
1260 apr_hash_t *filtered;
1262 int normalized_count;
1264 /* We set this property so that if we error out for some reason
1265 we can later determine where we were in the process of
1266 merging a revision. If we had committed the change, but we
1267 hadn't finished copying the revprops we need to know that, so
1268 we can go back and finish the job before we move on.
1270 NOTE: We have to set this before we start the commit editor,
1271 because ra_svn doesn't let you change rev props during a
1273 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1274 SVNSYNC_PROP_CURRENTLY_COPYING,
1276 svn_string_createf(pool, "%ld", revision),
1279 /* The actual copy is just a replay hooked up to a commit. Include
1280 all the revision properties from the source repositories, except
1281 'svn:author' and 'svn:date', those are not guaranteed to get
1282 through the editor anyway.
1283 If we're syncing to an non-commit-revprops capable server, filter
1284 out all revprops except svn:log and add them later in
1285 revplay_rev_finished. */
1286 filtered = filter_props(&filtered_count, rev_props,
1287 (rb->has_commit_revprops_capability
1288 ? filter_exclude_date_author_sync
1289 : filter_include_log),
1292 /* svn_ra_get_commit_editor3 requires the log message to be
1293 set. It's possible that we didn't receive 'svn:log' here, so we
1294 have to set it to at least the empty string. If there's a svn:log
1295 property on this revision, we will write the actual value in the
1296 replay_rev_finished callback. */
1297 if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
1298 svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
1299 svn_string_create_empty(pool));
1301 /* If necessary, normalize encoding and line ending style. Add the number
1302 of properties that required EOL normalization to the overall count
1303 in the replay baton. */
1304 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1305 rb->sb->source_prop_encoding, pool));
1306 rb->normalized_rev_props_count += normalized_count;
1308 #ifdef ENABLE_EV2_SHIMS
1309 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
1310 get_shim_callbacks(rb, pool)));
1312 SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1315 commit_callback, rb->sb,
1316 NULL, FALSE, pool));
1318 /* There's one catch though, the diff shows us props we can't send
1319 over the RA interface, so we need an editor that's smart enough
1320 to filter those out for us. */
1321 SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
1322 rb->sb->to_url, rb->sb->source_prop_encoding,
1323 rb->sb->quiet, &sync_editor, &sync_baton,
1324 &(rb->normalized_node_props_count), pool));
1326 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1327 sync_editor, sync_baton,
1331 *editor = cancel_editor;
1332 *edit_baton = cancel_baton;
1334 rb->current_revision = revision;
1335 return SVN_NO_ERROR;
1338 /* Callback function for svn_ra_replay_range, invoked when finishing parsing
1341 static svn_error_t *
1342 replay_rev_finished(svn_revnum_t revision,
1344 const svn_delta_editor_t *editor,
1346 apr_hash_t *rev_props,
1349 apr_pool_t *subpool = svn_pool_create(pool);
1350 replay_baton_t *rb = replay_baton;
1351 apr_hash_t *filtered, *existing_props;
1353 int normalized_count;
1354 const svn_string_t *rev_str;
1356 SVN_ERR(editor->close_edit(edit_baton, pool));
1358 /* Sanity check that we actually committed the revision we meant to. */
1359 if (rb->sb->committed_rev != revision)
1360 return svn_error_createf
1362 _("Commit created r%ld but should have created r%ld"),
1363 rb->sb->committed_rev, revision);
1365 SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1369 /* Ok, we're done with the data, now we just need to copy the remaining
1370 'svn:date' and 'svn:author' revprops and we're all set.
1371 If the server doesn't support revprops-in-a-commit, we still have to
1372 set all revision properties except svn:log. */
1373 filtered = filter_props(&filtered_count, rev_props,
1374 (rb->has_commit_revprops_capability
1375 ? filter_include_date_author_sync
1376 : filter_exclude_log),
1379 /* If necessary, normalize encoding and line ending style, and add the number
1380 of EOL-normalized properties to the overall count in the replay baton. */
1381 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1382 rb->sb->source_prop_encoding, pool));
1383 rb->normalized_rev_props_count += normalized_count;
1385 SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1388 /* Remove all extra properties in TARGET. */
1389 SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1390 rev_props, existing_props, subpool));
1392 svn_pool_clear(subpool);
1394 rev_str = svn_string_createf(subpool, "%ld", revision);
1396 /* Ok, we're done, bring the last-merged-rev property up to date. */
1397 SVN_ERR(svn_ra_change_rev_prop2(
1400 SVNSYNC_PROP_LAST_MERGED_REV,
1405 /* And finally drop the currently copying prop, since we're done
1406 with this revision. */
1407 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1408 SVNSYNC_PROP_CURRENTLY_COPYING,
1409 rb->has_atomic_revprops_capability
1413 /* Notify the user that we copied revision properties. */
1414 if (! rb->sb->quiet)
1415 SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1417 svn_pool_destroy(subpool);
1419 return SVN_NO_ERROR;
1422 /* Synchronize the repository associated with RA session TO_SESSION,
1423 * using information found in BATON.
1425 * Implements `with_locked_func_t' interface. The caller has
1426 * acquired a lock on the repository if locking is needed.
1428 static svn_error_t *
1429 do_synchronize(svn_ra_session_t *to_session,
1430 subcommand_baton_t *baton, apr_pool_t *pool)
1432 svn_string_t *last_merged_rev;
1433 svn_revnum_t from_latest;
1434 svn_ra_session_t *from_session;
1435 svn_string_t *currently_copying;
1436 svn_revnum_t to_latest, copying, last_merged;
1437 svn_revnum_t start_revision, end_revision;
1439 int normalized_rev_props_count = 0;
1441 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1442 baton->from_url, to_session,
1443 &(baton->source_callbacks), baton->config,
1446 /* Check to see if we have revprops that still need to be copied for
1447 a prior revision we didn't finish copying. But first, check for
1448 state sanity. Remember, mirroring is not an atomic action,
1449 because revision properties are copied separately from the
1450 revision's contents.
1452 So, any time that currently-copying is not set, then
1453 last-merged-rev should be the HEAD revision of the destination
1454 repository. That is, if we didn't fall over in the middle of a
1455 previous synchronization, then our destination repository should
1456 have exactly as many revisions in it as we've synchronized.
1458 Alternately, if currently-copying *is* set, it must
1459 be either last-merged-rev or last-merged-rev + 1, and the HEAD
1460 revision must be equal to either last-merged-rev or
1461 currently-copying. If this is not the case, somebody has meddled
1462 with the destination without using svnsync.
1465 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1466 ¤tly_copying, pool));
1468 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1470 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1472 if (currently_copying)
1474 copying = SVN_STR_TO_REV(currently_copying->data);
1476 if ((copying < last_merged)
1477 || (copying > (last_merged + 1))
1478 || ((to_latest != last_merged) && (to_latest != copying)))
1480 return svn_error_createf
1482 _("Revision being currently copied (%ld), last merged revision "
1483 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1484 "committed to the destination without using svnsync?"),
1485 copying, last_merged, to_latest);
1487 else if (copying == to_latest)
1489 if (copying > last_merged)
1491 SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
1492 baton->skip_unchanged, baton->quiet,
1493 baton->source_prop_encoding,
1494 &normalized_rev_props_count, pool));
1495 last_merged = copying;
1496 last_merged_rev = svn_string_create
1497 (apr_psprintf(pool, "%ld", last_merged), pool);
1500 /* Now update last merged rev and drop currently changing.
1501 Note that the order here is significant, if we do them
1502 in the wrong order there are race conditions where we
1503 end up not being able to tell if there have been bogus
1504 (i.e. non-svnsync) commits to the dest repository. */
1506 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1507 SVNSYNC_PROP_LAST_MERGED_REV,
1508 NULL, last_merged_rev, pool));
1509 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1510 SVNSYNC_PROP_CURRENTLY_COPYING,
1513 /* If copying > to_latest, then we just fall through to
1514 attempting to copy the revision again. */
1518 if (to_latest != last_merged)
1519 return svn_error_createf(APR_EINVAL, NULL,
1520 _("Destination HEAD (%ld) is not the last "
1521 "merged revision (%ld); have you "
1522 "committed to the destination without "
1524 to_latest, last_merged);
1527 /* Now check to see if there are any revisions to copy. */
1528 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1530 if (from_latest <= last_merged)
1531 return SVN_NO_ERROR;
1533 /* Ok, so there are new revisions, iterate over them copying them
1534 into the destination repository. */
1535 SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
1537 /* For compatibility with older svnserve versions, check first if we
1538 support adding revprops to the commit. */
1539 SVN_ERR(svn_ra_has_capability(rb->to_session,
1540 &rb->has_commit_revprops_capability,
1541 SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1544 SVN_ERR(svn_ra_has_capability(rb->to_session,
1545 &rb->has_atomic_revprops_capability,
1546 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1549 start_revision = last_merged + 1;
1550 end_revision = from_latest;
1552 SVN_ERR(check_cancel(NULL));
1554 SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1555 0, TRUE, replay_rev_started,
1556 replay_rev_finished, rb, pool));
1558 SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
1559 + normalized_rev_props_count,
1560 rb->normalized_node_props_count,
1564 return SVN_NO_ERROR;
1568 /* SUBCOMMAND: sync */
1569 static svn_error_t *
1570 synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1572 svn_ra_session_t *to_session;
1573 opt_baton_t *opt_baton = b;
1574 apr_array_header_t *targets;
1575 subcommand_baton_t *baton;
1576 const char *to_url, *from_url;
1578 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1579 apr_array_make(pool, 0,
1580 sizeof(const char *)),
1582 if (targets->nelts < 1)
1583 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1584 if (targets->nelts > 2)
1585 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1587 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1588 if (! svn_path_is_url(to_url))
1589 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1590 _("Path '%s' is not a URL"), to_url);
1592 if (targets->nelts == 2)
1594 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1595 if (! svn_path_is_url(from_url))
1596 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1597 _("Path '%s' is not a URL"), from_url);
1601 from_url = NULL; /* we'll read it from the destination repos */
1604 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
1605 SVN_ERR(open_target_session(&to_session, baton, pool));
1606 if (opt_baton->disable_locking)
1607 SVN_ERR(do_synchronize(to_session, baton, pool));
1609 SVN_ERR(with_locked(to_session, do_synchronize, baton,
1610 opt_baton->steal_lock, pool));
1612 return SVN_NO_ERROR;
1617 /*** `svnsync copy-revprops' ***/
1619 /* Copy revision properties to the repository associated with RA
1620 * session TO_SESSION, using information found in BATON.
1622 * Implements `with_locked_func_t' interface. The caller has
1623 * acquired a lock on the repository if locking is needed.
1625 static svn_error_t *
1626 do_copy_revprops(svn_ra_session_t *to_session,
1627 subcommand_baton_t *baton, apr_pool_t *pool)
1629 svn_ra_session_t *from_session;
1630 svn_string_t *last_merged_rev;
1632 svn_revnum_t step = 1;
1633 int normalized_rev_props_count = 0;
1635 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1636 baton->from_url, to_session,
1637 &(baton->source_callbacks), baton->config,
1640 /* An invalid revision means "last-synced" */
1641 if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1642 baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1643 if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1644 baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1646 /* Make sure we have revisions within the valid range. */
1647 if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1648 return svn_error_createf
1650 _("Cannot copy revprops for a revision (%ld) that has not "
1651 "been synchronized yet"), baton->start_rev);
1652 if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1653 return svn_error_createf
1655 _("Cannot copy revprops for a revision (%ld) that has not "
1656 "been synchronized yet"), baton->end_rev);
1658 /* Now, copy all the requested revisions, in the requested order. */
1659 step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1660 for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1662 int normalized_count;
1663 SVN_ERR(check_cancel(NULL));
1664 SVN_ERR(copy_revprops(from_session, to_session, i, TRUE,
1665 baton->skip_unchanged, baton->quiet,
1666 baton->source_prop_encoding, &normalized_count,
1668 normalized_rev_props_count += normalized_count;
1671 /* Notify about normalized props, if any. */
1672 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
1674 return SVN_NO_ERROR;
1678 /* Set *START_REVNUM to the revision number associated with
1679 START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
1680 represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
1681 the revision number associated with END_REVISION or to
1682 SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
1683 END_REVNUM to the same value as START_REVNUM.
1685 As a special case, if neither START_REVISION nor END_REVISION is
1686 specified, set *START_REVNUM to 0 and set *END_REVNUM to
1689 Freak out if either START_REVISION or END_REVISION represents an
1690 explicit but invalid revision number. */
1691 static svn_error_t *
1692 resolve_revnums(svn_revnum_t *start_revnum,
1693 svn_revnum_t *end_revnum,
1694 svn_opt_revision_t start_revision,
1695 svn_opt_revision_t end_revision)
1697 svn_revnum_t start_rev, end_rev;
1699 /* Special case: neither revision is specified? This is like
1701 if ((start_revision.kind == svn_opt_revision_unspecified) &&
1702 (end_revision.kind == svn_opt_revision_unspecified))
1705 *end_revnum = SVN_INVALID_REVNUM;
1706 return SVN_NO_ERROR;
1709 /* Get the start revision, which must be either HEAD or a number
1710 (which is required to be a valid one). */
1711 if (start_revision.kind == svn_opt_revision_head)
1713 start_rev = SVN_INVALID_REVNUM;
1717 start_rev = start_revision.value.number;
1718 if (! SVN_IS_VALID_REVNUM(start_rev))
1719 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1720 _("Invalid revision number (%ld)"),
1724 /* Get the end revision, which must be unspecified (meaning,
1725 "same as the start_rev"), HEAD, or a number (which is
1726 required to be a valid one). */
1727 if (end_revision.kind == svn_opt_revision_unspecified)
1729 end_rev = start_rev;
1731 else if (end_revision.kind == svn_opt_revision_head)
1733 end_rev = SVN_INVALID_REVNUM;
1737 end_rev = end_revision.value.number;
1738 if (! SVN_IS_VALID_REVNUM(end_rev))
1739 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1740 _("Invalid revision number (%ld)"),
1744 *start_revnum = start_rev;
1745 *end_revnum = end_rev;
1746 return SVN_NO_ERROR;
1750 /* SUBCOMMAND: copy-revprops */
1751 static svn_error_t *
1752 copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1754 svn_ra_session_t *to_session;
1755 opt_baton_t *opt_baton = b;
1756 apr_array_header_t *targets;
1757 subcommand_baton_t *baton;
1758 const char *to_url = NULL;
1759 const char *from_url = NULL;
1760 svn_opt_revision_t start_revision, end_revision;
1761 svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1763 /* There should be either one or two arguments left to parse. */
1764 if (os->argc - os->ind > 2)
1765 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1766 if (os->argc - os->ind < 1)
1767 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1769 /* If there are two args, the last one is either a revision range or
1771 if (os->argc - os->ind == 2)
1773 const char *arg_str;
1775 SVN_ERR(svn_utf_cstring_to_utf8(&arg_str, os->argv[os->argc - 1],
1778 if (! svn_path_is_url(arg_str))
1780 /* This is the old "... TO_URL REV[:REV2]" syntax.
1781 Revisions come only from this argument. (We effectively
1782 pop that last argument from the end of the argument list
1783 so svn_opt__args_to_target_array() can do its thang.) */
1786 if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
1787 || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
1788 return svn_error_create(
1789 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1790 _("Cannot specify revisions via both command-line arguments "
1791 "and the --revision (-r) option"));
1793 start_revision.kind = svn_opt_revision_unspecified;
1794 end_revision.kind = svn_opt_revision_unspecified;
1795 if (svn_opt_parse_revision(&start_revision, &end_revision,
1796 arg_str, pool) != 0)
1797 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1798 _("Invalid revision range '%s' provided"),
1801 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1802 start_revision, end_revision));
1804 SVN_ERR(svn_opt__args_to_target_array(
1806 apr_array_make(pool, 1, sizeof(const char *)), pool));
1807 if (targets->nelts != 1)
1808 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1809 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1816 /* This is the "... TO_URL SOURCE_URL" syntax. Revisions
1817 come only from the --revision parameter. */
1818 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1819 opt_baton->start_rev, opt_baton->end_rev));
1821 SVN_ERR(svn_opt__args_to_target_array(
1823 apr_array_make(pool, 2, sizeof(const char *)), pool));
1824 if (targets->nelts < 1)
1825 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1826 if (targets->nelts > 2)
1827 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1828 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1829 if (targets->nelts == 2)
1830 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1835 if (! svn_path_is_url(to_url))
1836 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1837 _("Path '%s' is not a URL"), to_url);
1838 if (from_url && (! svn_path_is_url(from_url)))
1839 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1840 _("Path '%s' is not a URL"), from_url);
1842 baton = make_subcommand_baton(opt_baton, to_url, from_url,
1843 start_rev, end_rev, pool);
1844 SVN_ERR(open_target_session(&to_session, baton, pool));
1845 if (opt_baton->disable_locking)
1846 SVN_ERR(do_copy_revprops(to_session, baton, pool));
1848 SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
1849 opt_baton->steal_lock, pool));
1851 return SVN_NO_ERROR;
1856 /*** `svnsync info' ***/
1859 /* SUBCOMMAND: info */
1860 static svn_error_t *
1861 info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1863 svn_ra_session_t *to_session;
1864 opt_baton_t *opt_baton = b;
1865 apr_array_header_t *targets;
1866 subcommand_baton_t *baton;
1869 svn_string_t *from_url, *from_uuid, *last_merged_rev;
1871 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1872 apr_array_make(pool, 0,
1873 sizeof(const char *)),
1875 if (targets->nelts < 1)
1876 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1877 if (targets->nelts > 1)
1878 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1880 /* Get the mirror repository URL, and verify that it is URL-ish. */
1881 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1882 if (! svn_path_is_url(to_url))
1883 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1884 _("Path '%s' is not a URL"), to_url);
1886 /* Open an RA session to the mirror repository URL. */
1887 baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1888 SVN_ERR(open_target_session(&to_session, baton, pool));
1890 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
1892 from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
1895 return svn_error_createf
1896 (SVN_ERR_BAD_URL, NULL,
1897 _("Repository '%s' is not initialized for synchronization"), to_url);
1899 from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
1900 last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
1902 /* Print the info. */
1903 SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1905 SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1907 if (last_merged_rev)
1908 SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1909 last_merged_rev->data));
1910 return SVN_NO_ERROR;
1915 /*** `svnsync help' ***/
1918 /* SUBCOMMAND: help */
1919 static svn_error_t *
1920 help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1922 opt_baton_t *opt_baton = baton;
1924 const char *header =
1925 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
1926 "Subversion repository replication tool.\n"
1927 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1928 "Type 'svnsync --version' to see the program version and RA modules.\n"
1930 "Available subcommands:\n");
1932 const char *ra_desc_start
1933 = _("The following repository access (RA) modules are available:\n\n");
1935 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1938 SVN_ERR(svn_ra_print_modules(version_footer, pool));
1940 SVN_ERR(svn_opt_print_help4(os, "svnsync",
1941 opt_baton ? opt_baton->version : FALSE,
1942 opt_baton ? opt_baton->quiet : FALSE,
1943 /*###opt_state ? opt_state->verbose :*/ FALSE,
1944 version_footer->data, header,
1945 svnsync_cmd_table, svnsync_options, NULL,
1948 return SVN_NO_ERROR;
1956 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1957 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1958 * return SVN_NO_ERROR.
1960 static svn_error_t *
1961 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1963 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1964 apr_array_header_t *received_opts;
1965 opt_baton_t opt_baton;
1966 svn_config_t *config;
1967 apr_status_t apr_err;
1971 const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1972 const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1973 apr_array_header_t *config_options = NULL;
1974 const char *source_prop_encoding = NULL;
1975 svn_boolean_t force_interactive = FALSE;
1977 /* Check library versions */
1978 SVN_ERR(check_lib_versions());
1980 SVN_ERR(svn_ra_initialize(pool));
1982 /* Initialize the option baton. */
1983 memset(&opt_baton, 0, sizeof(opt_baton));
1984 opt_baton.start_rev.kind = svn_opt_revision_unspecified;
1985 opt_baton.end_rev.kind = svn_opt_revision_unspecified;
1987 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1991 SVN_ERR(help_cmd(NULL, NULL, pool));
1992 *exit_code = EXIT_FAILURE;
1993 return SVN_NO_ERROR;
1996 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2002 const char *opt_arg;
2003 svn_error_t* opt_err = NULL;
2005 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
2006 if (APR_STATUS_IS_EOF(apr_err))
2010 SVN_ERR(help_cmd(NULL, NULL, pool));
2011 *exit_code = EXIT_FAILURE;
2012 return SVN_NO_ERROR;
2015 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2019 case svnsync_opt_non_interactive:
2020 opt_baton.non_interactive = TRUE;
2023 case svnsync_opt_force_interactive:
2024 force_interactive = TRUE;
2027 case svnsync_opt_trust_server_cert: /* backwards compat */
2028 opt_baton.src_trust.trust_server_cert_unknown_ca = TRUE;
2029 opt_baton.dst_trust.trust_server_cert_unknown_ca = TRUE;
2032 case svnsync_opt_trust_server_cert_failures_src:
2033 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2034 SVN_ERR(svn_cmdline__parse_trust_options(
2035 &opt_baton.src_trust.trust_server_cert_unknown_ca,
2036 &opt_baton.src_trust.trust_server_cert_cn_mismatch,
2037 &opt_baton.src_trust.trust_server_cert_expired,
2038 &opt_baton.src_trust.trust_server_cert_not_yet_valid,
2039 &opt_baton.src_trust.trust_server_cert_other_failure,
2043 case svnsync_opt_trust_server_cert_failures_dst:
2044 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2045 SVN_ERR(svn_cmdline__parse_trust_options(
2046 &opt_baton.dst_trust.trust_server_cert_unknown_ca,
2047 &opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2048 &opt_baton.dst_trust.trust_server_cert_expired,
2049 &opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2050 &opt_baton.dst_trust.trust_server_cert_other_failure,
2054 case svnsync_opt_no_auth_cache:
2055 opt_baton.no_auth_cache = TRUE;
2058 case svnsync_opt_auth_username:
2059 opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
2062 case svnsync_opt_auth_password:
2063 opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
2066 case svnsync_opt_source_username:
2067 opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
2070 case svnsync_opt_source_password:
2071 opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
2074 case svnsync_opt_sync_username:
2075 opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
2078 case svnsync_opt_sync_password:
2079 opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
2082 case svnsync_opt_config_dir:
2085 opt_err = svn_utf_cstring_to_utf8(&path, opt_arg, pool);
2088 opt_baton.config_dir = svn_dirent_internal_style(path, pool);
2091 case svnsync_opt_config_options:
2092 if (!config_options)
2094 apr_array_make(pool, 1,
2095 sizeof(svn_cmdline__config_argument_t*));
2097 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2098 SVN_ERR(svn_cmdline__parse_config_option(config_options,
2099 opt_arg, "svnsync: ",
2103 case svnsync_opt_source_prop_encoding:
2104 opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
2108 case svnsync_opt_disable_locking:
2109 opt_baton.disable_locking = TRUE;
2112 case svnsync_opt_steal_lock:
2113 opt_baton.steal_lock = TRUE;
2116 case svnsync_opt_version:
2117 opt_baton.version = TRUE;
2120 case svnsync_opt_allow_non_empty:
2121 opt_baton.allow_non_empty = TRUE;
2124 case svnsync_opt_skip_unchanged:
2125 opt_baton.skip_unchanged = TRUE;
2129 opt_baton.quiet = TRUE;
2133 if (svn_opt_parse_revision(&opt_baton.start_rev,
2135 opt_arg, pool) != 0)
2137 const char *utf8_opt_arg;
2138 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2139 return svn_error_createf(
2140 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2141 _("Syntax error in revision argument '%s'"),
2145 /* We only allow numbers and 'HEAD'. */
2146 if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
2147 (opt_baton.start_rev.kind != svn_opt_revision_head))
2148 || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
2149 (opt_baton.end_rev.kind != svn_opt_revision_head) &&
2150 (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
2152 return svn_error_createf(
2153 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2154 _("Invalid revision range '%s' provided"), opt_arg);
2159 if (!config_options)
2161 apr_array_make(pool, 1,
2162 sizeof(svn_cmdline__config_argument_t*));
2164 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2165 SVN_ERR(svn_cmdline__parse_config_option(
2168 "config:miscellany:memory-cache-size=%s",
2170 NULL /* won't be used */,
2176 opt_baton.help = TRUE;
2181 SVN_ERR(help_cmd(NULL, NULL, pool));
2182 *exit_code = EXIT_FAILURE;
2183 return SVN_NO_ERROR;
2192 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2194 /* The --non-interactive and --force-interactive options are mutually
2196 if (opt_baton.non_interactive && force_interactive)
2198 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2199 _("--non-interactive and --force-interactive "
2200 "are mutually exclusive"));
2203 opt_baton.non_interactive = !svn_cmdline__be_interactive(
2204 opt_baton.non_interactive,
2207 /* Disallow the mixing --username/password with their --source- and
2208 --sync- variants. Treat "--username FOO" as "--source-username
2209 FOO --sync-username FOO"; ditto for "--password FOO". */
2210 if ((username || password)
2211 && (source_username || sync_username
2212 || source_password || sync_password))
2214 return svn_error_create
2215 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2216 _("Cannot use --username or --password with any of "
2217 "--source-username, --source-password, --sync-username, "
2218 "or --sync-password.\n"));
2222 source_username = username;
2223 sync_username = username;
2227 source_password = password;
2228 sync_password = password;
2230 opt_baton.source_username = source_username;
2231 opt_baton.source_password = source_password;
2232 opt_baton.sync_username = sync_username;
2233 opt_baton.sync_password = sync_password;
2235 /* Disallow mixing of --steal-lock and --disable-locking. */
2236 if (opt_baton.steal_lock && opt_baton.disable_locking)
2238 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2239 _("--disable-locking and --steal-lock are "
2240 "mutually exclusive"));
2243 /* --trust-* can only be used with --non-interactive */
2244 if (!opt_baton.non_interactive)
2246 if (opt_baton.src_trust.trust_server_cert_unknown_ca
2247 || opt_baton.src_trust.trust_server_cert_cn_mismatch
2248 || opt_baton.src_trust.trust_server_cert_expired
2249 || opt_baton.src_trust.trust_server_cert_not_yet_valid
2250 || opt_baton.src_trust.trust_server_cert_other_failure
2251 || opt_baton.dst_trust.trust_server_cert_unknown_ca
2252 || opt_baton.dst_trust.trust_server_cert_cn_mismatch
2253 || opt_baton.dst_trust.trust_server_cert_expired
2254 || opt_baton.dst_trust.trust_server_cert_not_yet_valid
2255 || opt_baton.dst_trust.trust_server_cert_other_failure)
2256 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2257 _("--source-trust-server-cert-failures "
2259 "--sync-trust-server-cert-failures require "
2260 "--non-interactive"));
2263 SVN_ERR(svn_config_ensure(opt_baton.config_dir, pool));
2265 if (subcommand == NULL)
2267 if (os->ind >= os->argc)
2269 if (opt_baton.version)
2271 /* Use the "help" subcommand to handle "--version". */
2272 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2273 { "--version", help_cmd, {0}, "",
2274 {svnsync_opt_version, /* must accept its own option */
2278 subcommand = &pseudo_cmd;
2282 SVN_ERR(help_cmd(NULL, NULL, pool));
2283 *exit_code = EXIT_FAILURE;
2284 return SVN_NO_ERROR;
2289 const char *first_arg;
2291 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
2293 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2295 if (subcommand == NULL)
2297 SVN_ERR(help_cmd(NULL, NULL, pool));
2298 *exit_code = EXIT_FAILURE;
2299 return SVN_NO_ERROR;
2304 for (i = 0; i < received_opts->nelts; ++i)
2306 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2308 if (opt_id == 'h' || opt_id == '?')
2311 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2314 const apr_getopt_option_t *badopt =
2315 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2317 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2318 if (subcommand->name[0] == '-')
2320 SVN_ERR(help_cmd(NULL, NULL, pool));
2324 return svn_error_createf
2325 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2326 _("Subcommand '%s' doesn't accept option '%s'\n"
2327 "Type 'svnsync help %s' for usage.\n"),
2328 subcommand->name, optstr, subcommand->name);
2333 SVN_ERR(svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool));
2335 /* Update the options in the config */
2339 svn_cmdline__apply_config_options(opt_baton.config, config_options,
2340 "svnsync: ", "--config-option"));
2343 config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
2345 opt_baton.source_prop_encoding = source_prop_encoding;
2347 check_cancel = svn_cmdline__setup_cancellation_handler();
2349 err = svn_cmdline_create_auth_baton2(
2350 &opt_baton.source_auth_baton,
2351 opt_baton.non_interactive,
2352 opt_baton.source_username,
2353 opt_baton.source_password,
2354 opt_baton.config_dir,
2355 opt_baton.no_auth_cache,
2356 opt_baton.src_trust.trust_server_cert_unknown_ca,
2357 opt_baton.src_trust.trust_server_cert_cn_mismatch,
2358 opt_baton.src_trust.trust_server_cert_expired,
2359 opt_baton.src_trust.trust_server_cert_not_yet_valid,
2360 opt_baton.src_trust.trust_server_cert_other_failure,
2365 err = svn_cmdline_create_auth_baton2(
2366 &opt_baton.sync_auth_baton,
2367 opt_baton.non_interactive,
2368 opt_baton.sync_username,
2369 opt_baton.sync_password,
2370 opt_baton.config_dir,
2371 opt_baton.no_auth_cache,
2372 opt_baton.dst_trust.trust_server_cert_unknown_ca,
2373 opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2374 opt_baton.dst_trust.trust_server_cert_expired,
2375 opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2376 opt_baton.dst_trust.trust_server_cert_other_failure,
2381 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2384 /* For argument-related problems, suggest using the 'help'
2386 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2387 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2389 err = svn_error_quick_wrap(err,
2390 _("Try 'svnsync help' for more info"));
2396 return SVN_NO_ERROR;
2400 main(int argc, const char *argv[])
2403 int exit_code = EXIT_SUCCESS;
2406 /* Initialize the app. */
2407 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
2408 return EXIT_FAILURE;
2410 /* Create our top-level pool. Use a separate mutexless allocator,
2411 * given this application is single threaded.
2413 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2415 err = sub_main(&exit_code, argc, argv, pool);
2417 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2418 but this makes sure that output is not silently lost if it fails. */
2419 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2423 exit_code = EXIT_FAILURE;
2424 svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
2427 svn_pool_destroy(pool);
2429 svn_cmdline__cancellation_exit();