1 /*
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
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
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
18 * under the License.
19 * ====================================================================
20 */
21
22 #include "svn_hash.h"
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"
28 #include "svn_path.h"
29 #include "svn_props.h"
30 #include "svn_auth.h"
31 #include "svn_opt.h"
32 #include "svn_ra.h"
33 #include "svn_utf.h"
34 #include "svn_subst.h"
35 #include "svn_string.h"
36 #include "svn_version.h"
37
38 #include "private/svn_opt_private.h"
39 #include "private/svn_ra_private.h"
40 #include "private/svn_cmdline_private.h"
41
42 #include "sync.h"
43
44 #include "svn_private_config.h"
45
46 #include <apr_signal.h>
47 #include <apr_uuid.h>
48
49 static svn_opt_subcommand_t initialize_cmd,
50 synchronize_cmd,
51 copy_revprops_cmd,
52 info_cmd,
53 help_cmd;
54
55 enum svnsync__opt {
56 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
57 svnsync_opt_force_interactive,
58 svnsync_opt_no_auth_cache,
59 svnsync_opt_auth_username,
60 svnsync_opt_auth_password,
61 svnsync_opt_source_username,
62 svnsync_opt_source_password,
63 svnsync_opt_sync_username,
64 svnsync_opt_sync_password,
65 svnsync_opt_config_dir,
66 svnsync_opt_config_options,
67 svnsync_opt_source_prop_encoding,
68 svnsync_opt_disable_locking,
69 svnsync_opt_version,
70 svnsync_opt_trust_server_cert,
71 svnsync_opt_trust_server_cert_failures_src,
72 svnsync_opt_trust_server_cert_failures_dst,
73 svnsync_opt_allow_non_empty,
74 svnsync_opt_steal_lock
75 };
76
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
91
92 static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
93 {
94 { "initialize", initialize_cmd, { "init" },
95 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
96 "\n"
97 "Initialize a destination repository for synchronization from\n"
98 "another repository.\n"
99 "\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"
102 "\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"
112 "\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"
122 "\n"
123 "Transfer all pending revisions to the destination from the source\n"
124 "with which it was initialized.\n"
125 "\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 },
134 N_("usage:\n"
135 "\n"
136 " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
137 " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
138 "\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"
144 "\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"
150 "\n"
151 "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
152 { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
153 svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } },
154 { "info", info_cmd, { 0 },
155 N_("usage: svnsync info DEST_URL\n"
156 "\n"
157 "Print information about the synchronization destination repository\n"
158 "located at DEST_URL.\n"),
159 { SVNSYNC_OPTS_DEFAULT } },
160 { "help", help_cmd, { "?", "h" },
161 N_("usage: svnsync help [SUBCOMMAND...]\n"
162 "\n"
163 "Describe the usage of this program or its subcommands.\n"),
164 { 0 } },
165 { NULL, NULL, { 0 }, NULL, { 0 } }
166 };
167
168 static const apr_getopt_option_t svnsync_options[] =
169 {
170 {"quiet", 'q', 0,
171 N_("print as little as possible") },
172 {"revision", 'r', 1,
173 N_("operate on revision ARG (or range ARG1:ARG2)\n"
174 " "
175 "A revision argument can be one of:\n"
176 " "
177 " NUMBER revision number\n"
178 " "
179 " 'HEAD' latest in repository") },
180 {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
181 N_("allow a non-empty destination repository") },
182 {"non-interactive", svnsync_opt_non_interactive, 0,
183 N_("do no interactive prompting (default is to prompt\n"
184 " "
185 "only if standard input is a terminal device)")},
186 {"force-interactive", svnsync_opt_force_interactive, 0,
187 N_("do interactive prompting even if standard input\n"
188 " "
189 "is not a terminal device")},
190 {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
191 N_("do not cache authentication tokens") },
192 {"username", svnsync_opt_auth_username, 1,
193 N_("specify a username ARG (deprecated;\n"
194 " "
195 "see --source-username and --sync-username)") },
196 {"password", svnsync_opt_auth_password, 1,
197 N_("specify a password ARG (deprecated;\n"
198 " "
199 "see --source-password and --sync-password)") },
200 {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
201 N_("deprecated; same as\n"
202 " "
203 "--source-trust-server-cert-failures=unknown-ca\n"
204 " "
205 "--sync-trust-server-cert-failures=unknown-ca")},
206 {"source-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_src, 1,
207 N_("with --non-interactive, accept SSL\n"
208 " "
209 "server certificates with failures.\n"
210 " "
211 "ARG is a comma-separated list of:\n"
212 " "
213 "- 'unknown-ca' (Unknown Authority)\n"
214 " "
215 "- 'cn-mismatch' (Hostname mismatch)\n"
216 " "
217 "- 'expired' (Expired certificate)\n"
218 " "
219 "- 'not-yet-valid' (Not yet valid certificate)\n"
220 " "
221 "- 'other' (all other not separately classified\n"
222 " "
223 " certificate errors).\n"
224 " "
225 "Applied to the source URL.")},
226 {"sync-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_dst, 1,
227 N_("Like\n"
228 " "
229 "--source-trust-server-cert-failures,\n"
230 " "
231 "but applied to the destination URL.")},
232 {"source-username", svnsync_opt_source_username, 1,
233 N_("connect to source repository with username ARG") },
234 {"source-password", svnsync_opt_source_password, 1,
235 N_("connect to source repository with password ARG") },
236 {"sync-username", svnsync_opt_sync_username, 1,
237 N_("connect to sync repository with username ARG") },
238 {"sync-password", svnsync_opt_sync_password, 1,
239 N_("connect to sync repository with password ARG") },
240 {"config-dir", svnsync_opt_config_dir, 1,
241 N_("read user configuration files from directory ARG")},
242 {"config-option", svnsync_opt_config_options, 1,
243 N_("set user configuration option in the format:\n"
244 " "
245 " FILE:SECTION:OPTION=[VALUE]\n"
246 " "
247 "For example:\n"
248 " "
249 " servers:global:http-library=serf")},
250 {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
251 N_("convert translatable properties from encoding ARG\n"
252 " "
253 "to UTF-8. If not specified, then properties are\n"
254 " "
255 "presumed to be encoded in UTF-8.")},
256 {"disable-locking", svnsync_opt_disable_locking, 0,
257 N_("Disable built-in locking. Use of this option can\n"
258 " "
259 "corrupt the mirror unless you ensure that no other\n"
260 " "
261 "instance of svnsync is running concurrently.")},
262 {"steal-lock", svnsync_opt_steal_lock, 0,
263 N_("Steal locks as necessary. Use, with caution,\n"
264 " "
265 "if your mirror repository contains stale locks\n"
266 " "
267 "and is not being concurrently accessed by another\n"
268 " "
269 "svnsync instance.")},
270 {"memory-cache-size", 'M', 1,
271 N_("size of the extra in-memory cache in MB used to\n"
272 " "
273 "minimize operations for local 'file' scheme.\n")},
274 {"version", svnsync_opt_version, 0,
275 N_("show program version information")},
276 {"help", 'h', 0,
277 N_("show help on a subcommand")},
278 {NULL, '?', 0,
279 N_("show help on a subcommand")},
280 { 0, 0, 0, 0 }
281 };
282
283 typedef struct opt_baton_t {
284 svn_boolean_t non_interactive;
285 struct {
286 svn_boolean_t trust_server_cert_unknown_ca;
287 svn_boolean_t trust_server_cert_cn_mismatch;
288 svn_boolean_t trust_server_cert_expired;
289 svn_boolean_t trust_server_cert_not_yet_valid;
290 svn_boolean_t trust_server_cert_other_failure;
291 } src_trust, dst_trust;
292 svn_boolean_t no_auth_cache;
293 svn_auth_baton_t *source_auth_baton;
294 svn_auth_baton_t *sync_auth_baton;
295 const char *source_username;
296 const char *source_password;
297 const char *sync_username;
298 const char *sync_password;
299 const char *config_dir;
300 apr_hash_t *config;
301 const char *source_prop_encoding;
302 svn_boolean_t disable_locking;
303 svn_boolean_t steal_lock;
304 svn_boolean_t quiet;
305 svn_boolean_t allow_non_empty;
306 svn_boolean_t version;
307 svn_boolean_t help;
308 svn_opt_revision_t start_rev;
309 svn_opt_revision_t end_rev;
310 } opt_baton_t;
311
312
313
314
315 /*** Helper functions ***/
316
317
318 /* Global record of whether the user has requested cancellation. */
319 static volatile sig_atomic_t cancelled = FALSE;
320
321
322 /* Callback function for apr_signal(). */
323 static void
signal_handler(int signum)324 signal_handler(int signum)
325 {
326 apr_signal(signum, SIG_IGN);
327 cancelled = TRUE;
328 }
329
330
331 /* Cancellation callback function. */
332 static svn_error_t *
check_cancel(void * baton)333 check_cancel(void *baton)
334 {
335 if (cancelled)
336 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
337 else
338 return SVN_NO_ERROR;
339 }
340
341
342 /* Check that the version of libraries in use match what we expect. */
343 static svn_error_t *
check_lib_versions(void)344 check_lib_versions(void)
345 {
346 static const svn_version_checklist_t checklist[] =
347 {
348 { "svn_subr", svn_subr_version },
349 { "svn_delta", svn_delta_version },
350 { "svn_ra", svn_ra_version },
351 { NULL, NULL }
352 };
353 SVN_VERSION_DEFINE(my_version);
354
355 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
356 }
357
358
359 /* Implements `svn_ra__lock_retry_func_t'. */
360 static svn_error_t *
lock_retry_func(void * baton,const svn_string_t * reposlocktoken,apr_pool_t * pool)361 lock_retry_func(void *baton,
362 const svn_string_t *reposlocktoken,
363 apr_pool_t *pool)
364 {
365 return svn_cmdline_printf(pool,
366 _("Failed to get lock on destination "
367 "repos, currently held by '%s'\n"),
368 reposlocktoken->data);
369 }
370
371 /* Acquire a lock (of sorts) on the repository associated with the
372 * given RA SESSION. This lock is just a revprop change attempt in a
373 * time-delay loop. This function is duplicated by svnrdump in
374 * svnrdump/load_editor.c
375 */
376 static svn_error_t *
get_lock(const svn_string_t ** lock_string_p,svn_ra_session_t * session,svn_boolean_t steal_lock,apr_pool_t * pool)377 get_lock(const svn_string_t **lock_string_p,
378 svn_ra_session_t *session,
379 svn_boolean_t steal_lock,
380 apr_pool_t *pool)
381 {
382 svn_error_t *err;
383 svn_boolean_t be_atomic;
384 const svn_string_t *stolen_lock;
385
386 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
387 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
388 pool));
389 if (! be_atomic)
390 {
391 /* Pre-1.7 server. Can't lock without a race condition.
392 See issue #3546.
393 */
394 err = svn_error_create(
395 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
396 _("Target server does not support atomic revision property "
397 "edits; consider upgrading it to 1.7 or using an external "
398 "locking program"));
399 svn_handle_warning2(stderr, err, "svnsync: ");
400 svn_error_clear(err);
401 }
402
403 err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
404 SVNSYNC_PROP_LOCK, steal_lock,
405 10 /* retries */, lock_retry_func, NULL,
406 check_cancel, NULL, pool);
407 if (!err && stolen_lock)
408 {
409 return svn_cmdline_printf(pool,
410 _("Stole lock previously held by '%s'\n"),
411 stolen_lock->data);
412 }
413 return err;
414 }
415
416
417 /* Baton for the various subcommands to share. */
418 typedef struct subcommand_baton_t {
419 /* common to all subcommands */
420 apr_hash_t *config;
421 svn_ra_callbacks2_t source_callbacks;
422 svn_ra_callbacks2_t sync_callbacks;
423 svn_boolean_t quiet;
424 svn_boolean_t allow_non_empty;
425 const char *to_url;
426
427 /* initialize, synchronize, and copy-revprops only */
428 const char *source_prop_encoding;
429
430 /* initialize only */
431 const char *from_url;
432
433 /* synchronize only */
434 svn_revnum_t committed_rev;
435
436 /* copy-revprops only */
437 svn_revnum_t start_rev;
438 svn_revnum_t end_rev;
439
440 } subcommand_baton_t;
441
442 typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
443 subcommand_baton_t *baton,
444 apr_pool_t *pool);
445
446
447 /* Lock the repository associated with RA SESSION, then execute the
448 * given FUNC/BATON pair while holding the lock. Finally, drop the
449 * lock once it finishes.
450 */
451 static svn_error_t *
with_locked(svn_ra_session_t * session,with_locked_func_t func,subcommand_baton_t * baton,svn_boolean_t steal_lock,apr_pool_t * pool)452 with_locked(svn_ra_session_t *session,
453 with_locked_func_t func,
454 subcommand_baton_t *baton,
455 svn_boolean_t steal_lock,
456 apr_pool_t *pool)
457 {
458 const svn_string_t *lock_string;
459 svn_error_t *err;
460
461 SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
462
463 err = func(session, baton, pool);
464 return svn_error_compose_create(err,
465 svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
466 lock_string, pool));
467 }
468
469
470 /* Callback function for the RA session's open_tmp_file()
471 * requirements.
472 */
473 static svn_error_t *
open_tmp_file(apr_file_t ** fp,void * callback_baton,apr_pool_t * pool)474 open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
475 {
476 return svn_io_open_unique_file3(fp, NULL, NULL,
477 svn_io_file_del_on_pool_cleanup,
478 pool, pool);
479 }
480
481
482 /* Return SVN_NO_ERROR iff URL identifies the root directory of the
483 * repository associated with RA session SESS.
484 */
485 static svn_error_t *
check_if_session_is_at_repos_root(svn_ra_session_t * sess,const char * url,apr_pool_t * pool)486 check_if_session_is_at_repos_root(svn_ra_session_t *sess,
487 const char *url,
488 apr_pool_t *pool)
489 {
490 const char *sess_root;
491
492 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
493
494 if (strcmp(url, sess_root) == 0)
495 return SVN_NO_ERROR;
496 else
497 return svn_error_createf
498 (APR_EINVAL, NULL,
499 _("Session is rooted at '%s' but the repos root is '%s'"),
500 url, sess_root);
501 }
502
503
504 /* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
505 * revision REV of the repository associated with RA session SESSION.
506 *
507 * For REV zero, don't remove properties with the "svn:sync-" prefix.
508 *
509 * All allocations will be done in a subpool of POOL.
510 */
511 static svn_error_t *
remove_props_not_in_source(svn_ra_session_t * session,svn_revnum_t rev,apr_hash_t * source_props,apr_hash_t * target_props,apr_pool_t * pool)512 remove_props_not_in_source(svn_ra_session_t *session,
513 svn_revnum_t rev,
514 apr_hash_t *source_props,
515 apr_hash_t *target_props,
516 apr_pool_t *pool)
517 {
518 apr_pool_t *subpool = svn_pool_create(pool);
519 apr_hash_index_t *hi;
520
521 for (hi = apr_hash_first(pool, target_props);
522 hi;
523 hi = apr_hash_next(hi))
524 {
525 const char *propname = apr_hash_this_key(hi);
526
527 svn_pool_clear(subpool);
528
529 if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
530 sizeof(SVNSYNC_PROP_PREFIX) - 1))
531 continue;
532
533 /* Delete property if the name can't be found in SOURCE_PROPS. */
534 if (! svn_hash_gets(source_props, propname))
535 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
536 NULL, subpool));
537 }
538
539 svn_pool_destroy(subpool);
540
541 return SVN_NO_ERROR;
542 }
543
544 /* Filter callback function.
545 * Takes a property name KEY, and is expected to return TRUE if the property
546 * should be filtered out (ie. not be copied to the target list), or FALSE if
547 * not.
548 */
549 typedef svn_boolean_t (*filter_func_t)(const char *key);
550
551 /* Make a new set of properties, by copying those properties in PROPS for which
552 * the filter FILTER returns FALSE.
553 *
554 * The number of properties not copied will be stored in FILTERED_COUNT.
555 *
556 * The returned set of properties is allocated from POOL.
557 */
558 static apr_hash_t *
filter_props(int * filtered_count,apr_hash_t * props,filter_func_t filter,apr_pool_t * pool)559 filter_props(int *filtered_count, apr_hash_t *props,
560 filter_func_t filter,
561 apr_pool_t *pool)
562 {
563 apr_hash_index_t *hi;
564 apr_hash_t *filtered = apr_hash_make(pool);
565 *filtered_count = 0;
566
567 for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
568 {
569 const char *propname = apr_hash_this_key(hi);
570 void *propval = apr_hash_this_val(hi);
571
572 /* Copy all properties:
573 - not matching the exclude pattern if provided OR
574 - matching the include pattern if provided */
575 if (!filter || !filter(propname))
576 {
577 svn_hash_sets(filtered, propname, propval);
578 }
579 else
580 {
581 *filtered_count += 1;
582 }
583 }
584
585 return filtered;
586 }
587
588
589 /* Write the set of revision properties REV_PROPS to revision REV to the
590 * repository associated with RA session SESSION.
591 * Omit any properties whose names are in the svnsync property name space,
592 * and set *FILTERED_COUNT to the number of properties thus omitted.
593 * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
594 *
595 * All allocations will be done in a subpool of POOL.
596 */
597 static svn_error_t *
write_revprops(int * filtered_count,svn_ra_session_t * session,svn_revnum_t rev,apr_hash_t * rev_props,apr_pool_t * pool)598 write_revprops(int *filtered_count,
599 svn_ra_session_t *session,
600 svn_revnum_t rev,
601 apr_hash_t *rev_props,
602 apr_pool_t *pool)
603 {
604 apr_pool_t *subpool = svn_pool_create(pool);
605 apr_hash_index_t *hi;
606
607 *filtered_count = 0;
608
609 for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
610 {
611 const char *propname = apr_hash_this_key(hi);
612 const svn_string_t *propval = apr_hash_this_val(hi);
613
614 svn_pool_clear(subpool);
615
616 if (strncmp(propname, SVNSYNC_PROP_PREFIX,
617 sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
618 {
619 SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
620 propval, subpool));
621 }
622 else
623 {
624 *filtered_count += 1;
625 }
626 }
627
628 svn_pool_destroy(subpool);
629
630 return SVN_NO_ERROR;
631 }
632
633
634 static svn_error_t *
log_properties_copied(svn_boolean_t syncprops_found,svn_revnum_t rev,apr_pool_t * pool)635 log_properties_copied(svn_boolean_t syncprops_found,
636 svn_revnum_t rev,
637 apr_pool_t *pool)
638 {
639 if (syncprops_found)
640 SVN_ERR(svn_cmdline_printf(pool,
641 _("Copied properties for revision %ld "
642 "(%s* properties skipped).\n"),
643 rev, SVNSYNC_PROP_PREFIX));
644 else
645 SVN_ERR(svn_cmdline_printf(pool,
646 _("Copied properties for revision %ld.\n"),
647 rev));
648
649 return SVN_NO_ERROR;
650 }
651
652 /* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
653 * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
654 * endings, if either of those numbers is non-zero. */
655 static svn_error_t *
log_properties_normalized(int normalized_rev_props_count,int normalized_node_props_count,apr_pool_t * pool)656 log_properties_normalized(int normalized_rev_props_count,
657 int normalized_node_props_count,
658 apr_pool_t *pool)
659 {
660 if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
661 SVN_ERR(svn_cmdline_printf(pool,
662 _("NOTE: Normalized %s* properties "
663 "to LF line endings (%d rev-props, "
664 "%d node-props).\n"),
665 SVN_PROP_PREFIX,
666 normalized_rev_props_count,
667 normalized_node_props_count));
668 return SVN_NO_ERROR;
669 }
670
671
672 /* Copy all the revision properties, except for those that have the
673 * "svn:sync-" prefix, from revision REV of the repository associated
674 * with RA session FROM_SESSION, to the repository associated with RA
675 * session TO_SESSION.
676 *
677 * If SYNC is TRUE, then properties on the destination revision that
678 * do not exist on the source revision will be removed.
679 *
680 * If QUIET is FALSE, then log_properties_copied() is called to log that
681 * properties were copied for revision REV.
682 *
683 * Make sure the values of svn:* revision properties use only LF (\n)
684 * line ending style, correcting their values as necessary. The number
685 * of properties that were normalized is returned in *NORMALIZED_COUNT.
686 */
687 static svn_error_t *
copy_revprops(svn_ra_session_t * from_session,svn_ra_session_t * to_session,svn_revnum_t rev,svn_boolean_t sync,svn_boolean_t quiet,const char * source_prop_encoding,int * normalized_count,apr_pool_t * pool)688 copy_revprops(svn_ra_session_t *from_session,
689 svn_ra_session_t *to_session,
690 svn_revnum_t rev,
691 svn_boolean_t sync,
692 svn_boolean_t quiet,
693 const char *source_prop_encoding,
694 int *normalized_count,
695 apr_pool_t *pool)
696 {
697 apr_pool_t *subpool = svn_pool_create(pool);
698 apr_hash_t *existing_props, *rev_props;
699 int filtered_count = 0;
700
701 /* Get the list of revision properties on REV of TARGET. We're only interested
702 in the property names, but we'll get the values 'for free'. */
703 if (sync)
704 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
705 else
706 existing_props = NULL;
707
708 /* Get the list of revision properties on REV of SOURCE. */
709 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
710
711 /* If necessary, normalize encoding and line ending style and return the count
712 of EOL-normalized properties in int *NORMALIZED_COUNT. */
713 SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
714 source_prop_encoding, pool));
715
716 /* Copy all but the svn:svnsync properties. */
717 SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
718
719 /* Delete those properties that were in TARGET but not in SOURCE */
720 if (sync)
721 SVN_ERR(remove_props_not_in_source(to_session, rev,
722 rev_props, existing_props, pool));
723
724 if (! quiet)
725 SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
726
727 svn_pool_destroy(subpool);
728
729 return SVN_NO_ERROR;
730 }
731
732
733 /* Return a subcommand baton allocated from POOL and populated with
734 data from the provided parameters, which include the global
735 OPT_BATON options structure and a handful of other options. Not
736 all parameters are used in all subcommands -- see
737 subcommand_baton_t's definition for details. */
738 static subcommand_baton_t *
make_subcommand_baton(opt_baton_t * opt_baton,const char * to_url,const char * from_url,svn_revnum_t start_rev,svn_revnum_t end_rev,apr_pool_t * pool)739 make_subcommand_baton(opt_baton_t *opt_baton,
740 const char *to_url,
741 const char *from_url,
742 svn_revnum_t start_rev,
743 svn_revnum_t end_rev,
744 apr_pool_t *pool)
745 {
746 subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
747 b->config = opt_baton->config;
748 b->source_callbacks.open_tmp_file = open_tmp_file;
749 b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
750 b->sync_callbacks.open_tmp_file = open_tmp_file;
751 b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
752 b->quiet = opt_baton->quiet;
753 b->allow_non_empty = opt_baton->allow_non_empty;
754 b->to_url = to_url;
755 b->source_prop_encoding = opt_baton->source_prop_encoding;
756 b->from_url = from_url;
757 b->start_rev = start_rev;
758 b->end_rev = end_rev;
759 return b;
760 }
761
762 static svn_error_t *
763 open_target_session(svn_ra_session_t **to_session_p,
764 subcommand_baton_t *baton,
765 apr_pool_t *pool);
766
767
768 /*** `svnsync init' ***/
769
770 /* Initialize the repository associated with RA session TO_SESSION,
771 * using information found in BATON.
772 *
773 * Implements `with_locked_func_t' interface. The caller has
774 * acquired a lock on the repository if locking is needed.
775 */
776 static svn_error_t *
do_initialize(svn_ra_session_t * to_session,subcommand_baton_t * baton,apr_pool_t * pool)777 do_initialize(svn_ra_session_t *to_session,
778 subcommand_baton_t *baton,
779 apr_pool_t *pool)
780 {
781 svn_ra_session_t *from_session;
782 svn_string_t *from_url;
783 svn_revnum_t latest, from_latest;
784 const char *uuid, *root_url;
785 int normalized_rev_props_count;
786
787 /* First, sanity check to see that we're copying into a brand new
788 repos. If we aren't, and we aren't being asked to forcibly
789 complete this initialization, that's a bad news. */
790 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
791 if ((latest != 0) && (! baton->allow_non_empty))
792 return svn_error_create
793 (APR_EINVAL, NULL,
794 _("Destination repository already contains revision history; consider "
795 "using --allow-non-empty if the repository's revisions are known "
796 "to mirror their respective revisions in the source repository"));
797
798 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
799 &from_url, pool));
800 if (from_url && (! baton->allow_non_empty))
801 return svn_error_createf
802 (APR_EINVAL, NULL,
803 _("Destination repository is already synchronizing from '%s'"),
804 from_url->data);
805
806 /* Now fill in our bookkeeping info in the dest repository. */
807
808 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
809 &(baton->source_callbacks), baton,
810 baton->config, pool));
811 SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
812
813 /* If we're doing a partial replay, we have to check first if the server
814 supports this. */
815 if (strcmp(root_url, baton->from_url) != 0)
816 {
817 svn_boolean_t server_supports_partial_replay;
818 svn_error_t *err = svn_ra_has_capability(from_session,
819 &server_supports_partial_replay,
820 SVN_RA_CAPABILITY_PARTIAL_REPLAY,
821 pool);
822 if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
823 return svn_error_trace(err);
824
825 if (err || !server_supports_partial_replay)
826 return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
827 NULL);
828 }
829
830 /* If we're initializing a non-empty destination, we'll make sure
831 that it at least doesn't have more revisions than the source. */
832 if (latest != 0)
833 {
834 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
835 if (from_latest < latest)
836 return svn_error_create
837 (APR_EINVAL, NULL,
838 _("Destination repository has more revisions than source "
839 "repository"));
840 }
841
842 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
843 svn_string_create(baton->from_url, pool),
844 pool));
845
846 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
847 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
848 svn_string_create(uuid, pool), pool));
849
850 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
851 NULL, svn_string_createf(pool, "%ld", latest),
852 pool));
853
854 /* Copy all non-svnsync revprops from the LATEST rev in the source
855 repository into the destination, notifying about normalized
856 props, if any. When LATEST is 0, this serves the practical
857 purpose of initializing data that would otherwise be overlooked
858 by the sync process (which is going to begin with r1). When
859 LATEST is not 0, this really serves merely aesthetic and
860 informational purposes, keeping the output of this command
861 consistent while allowing folks to see what the latest revision is. */
862 SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
863 baton->source_prop_encoding, &normalized_rev_props_count,
864 pool));
865
866 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
867
868 /* TODO: It would be nice if we could set the dest repos UUID to be
869 equal to the UUID of the source repos, at least optionally. That
870 way people could check out/log/diff using a local fast mirror,
871 but switch --relocate to the actual final repository in order to
872 make changes... But at this time, the RA layer doesn't have a
873 way to set a UUID. */
874
875 return SVN_NO_ERROR;
876 }
877
878
879 /* SUBCOMMAND: init */
880 static svn_error_t *
initialize_cmd(apr_getopt_t * os,void * b,apr_pool_t * pool)881 initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
882 {
883 const char *to_url, *from_url;
884 svn_ra_session_t *to_session;
885 opt_baton_t *opt_baton = b;
886 apr_array_header_t *targets;
887 subcommand_baton_t *baton;
888
889 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
890 apr_array_make(pool, 0,
891 sizeof(const char *)),
892 pool));
893 if (targets->nelts < 2)
894 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
895 if (targets->nelts > 2)
896 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
897
898 to_url = APR_ARRAY_IDX(targets, 0, const char *);
899 from_url = APR_ARRAY_IDX(targets, 1, const char *);
900
901 if (! svn_path_is_url(to_url))
902 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
903 _("Path '%s' is not a URL"), to_url);
904 if (! svn_path_is_url(from_url))
905 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
906 _("Path '%s' is not a URL"), from_url);
907
908 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
909 SVN_ERR(open_target_session(&to_session, baton, pool));
910 if (opt_baton->disable_locking)
911 SVN_ERR(do_initialize(to_session, baton, pool));
912 else
913 SVN_ERR(with_locked(to_session, do_initialize, baton,
914 opt_baton->steal_lock, pool));
915
916 return SVN_NO_ERROR;
917 }
918
919
920
921 /*** `svnsync sync' ***/
922
923 /* Implements `svn_commit_callback2_t' interface. */
924 static svn_error_t *
commit_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)925 commit_callback(const svn_commit_info_t *commit_info,
926 void *baton,
927 apr_pool_t *pool)
928 {
929 subcommand_baton_t *sb = baton;
930
931 if (! sb->quiet)
932 {
933 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
934 commit_info->revision));
935 }
936
937 sb->committed_rev = commit_info->revision;
938
939 return SVN_NO_ERROR;
940 }
941
942
943 /* Set *FROM_SESSION to an RA session associated with the source
944 * repository of the synchronization. If FROM_URL is non-NULL, use it
945 * as the source repository URL; otherwise, determine the source
946 * repository URL by reading svn:sync- properties from the destination
947 * repository (associated with TO_SESSION). Set LAST_MERGED_REV to
948 * the value of the property which records the most recently
949 * synchronized revision.
950 *
951 * CALLBACKS is a vtable of RA callbacks to provide when creating
952 * *FROM_SESSION. CONFIG is a configuration hash.
953 */
954 static svn_error_t *
open_source_session(svn_ra_session_t ** from_session,svn_string_t ** last_merged_rev,const char * from_url,svn_ra_session_t * to_session,svn_ra_callbacks2_t * callbacks,apr_hash_t * config,void * baton,apr_pool_t * pool)955 open_source_session(svn_ra_session_t **from_session,
956 svn_string_t **last_merged_rev,
957 const char *from_url,
958 svn_ra_session_t *to_session,
959 svn_ra_callbacks2_t *callbacks,
960 apr_hash_t *config,
961 void *baton,
962 apr_pool_t *pool)
963 {
964 apr_hash_t *props;
965 svn_string_t *from_url_str, *from_uuid_str;
966
967 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
968
969 from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
970 from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
971 *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
972
973 if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
974 return svn_error_create
975 (APR_EINVAL, NULL,
976 _("Destination repository has not been initialized"));
977
978 /* ### TODO: Should we validate that FROM_URL_STR->data matches any
979 provided FROM_URL here? */
980 if (! from_url)
981 SVN_ERR(svn_opt__arg_canonicalize_url(&from_url, from_url_str->data,
982 pool));
983
984 /* Open the session to copy the revision data. */
985 SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
986 callbacks, baton, config, pool));
987
988 return SVN_NO_ERROR;
989 }
990
991 /* Set *TARGET_SESSION_P to an RA session associated with the target
992 * repository of the synchronization.
993 */
994 static svn_error_t *
open_target_session(svn_ra_session_t ** target_session_p,subcommand_baton_t * baton,apr_pool_t * pool)995 open_target_session(svn_ra_session_t **target_session_p,
996 subcommand_baton_t *baton,
997 apr_pool_t *pool)
998 {
999 svn_ra_session_t *target_session;
1000 SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
1001 &(baton->sync_callbacks), baton, baton->config, pool));
1002 SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
1003
1004 *target_session_p = target_session;
1005 return SVN_NO_ERROR;
1006 }
1007
1008 /* Replay baton, used during synchronization. */
1009 typedef struct replay_baton_t {
1010 svn_ra_session_t *from_session;
1011 svn_ra_session_t *to_session;
1012 svn_revnum_t current_revision;
1013 subcommand_baton_t *sb;
1014 svn_boolean_t has_commit_revprops_capability;
1015 svn_boolean_t has_atomic_revprops_capability;
1016 int normalized_rev_props_count;
1017 int normalized_node_props_count;
1018 const char *to_root;
1019
1020 #ifdef ENABLE_EV2_SHIMS
1021 /* Extra 'backdoor' session for fetching data *from* the target repo. */
1022 svn_ra_session_t *extra_to_session;
1023 #endif
1024 } replay_baton_t;
1025
1026 /* Return a replay baton allocated from POOL and populated with
1027 data from the provided parameters. */
1028 static svn_error_t *
make_replay_baton(replay_baton_t ** baton_p,svn_ra_session_t * from_session,svn_ra_session_t * to_session,subcommand_baton_t * sb,apr_pool_t * pool)1029 make_replay_baton(replay_baton_t **baton_p,
1030 svn_ra_session_t *from_session,
1031 svn_ra_session_t *to_session,
1032 subcommand_baton_t *sb, apr_pool_t *pool)
1033 {
1034 replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
1035 rb->from_session = from_session;
1036 rb->to_session = to_session;
1037 rb->sb = sb;
1038
1039 SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
1040
1041 #ifdef ENABLE_EV2_SHIMS
1042 /* Open up the extra baton. Only needed for Ev2 shims. */
1043 SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
1044 #endif
1045
1046 *baton_p = rb;
1047 return SVN_NO_ERROR;
1048 }
1049
1050 /* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
1051 * property. Implements filter_func_t. Use with filter_props() to filter out
1052 * svn:date and svn:author and svnsync properties.
1053 */
1054 static svn_boolean_t
filter_exclude_date_author_sync(const char * key)1055 filter_exclude_date_author_sync(const char *key)
1056 {
1057 if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1058 return TRUE;
1059 else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1060 return TRUE;
1061 else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1062 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1063 return TRUE;
1064
1065 return FALSE;
1066 }
1067
1068 /* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
1069 * property. Implements filter_func_t. Use with filter_props() to filter out
1070 * all properties except svn:date and svn:author and svnsync properties.
1071 */
1072 static svn_boolean_t
filter_include_date_author_sync(const char * key)1073 filter_include_date_author_sync(const char *key)
1074 {
1075 return ! filter_exclude_date_author_sync(key);
1076 }
1077
1078
1079 /* Return TRUE iff KEY is the name of the svn:log property.
1080 * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
1081 */
1082 static svn_boolean_t
filter_exclude_log(const char * key)1083 filter_exclude_log(const char *key)
1084 {
1085 if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1086 return TRUE;
1087 else
1088 return FALSE;
1089 }
1090
1091 /* Return FALSE iff KEY is the name of the svn:log property.
1092 * Implements filter_func_t. Use with filter_props() to only include svn:log.
1093 */
1094 static svn_boolean_t
filter_include_log(const char * key)1095 filter_include_log(const char *key)
1096 {
1097 return ! filter_exclude_log(key);
1098 }
1099
1100 #ifdef ENABLE_EV2_SHIMS
1101 static svn_error_t *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1102 fetch_base_func(const char **filename,
1103 void *baton,
1104 const char *path,
1105 svn_revnum_t base_revision,
1106 apr_pool_t *result_pool,
1107 apr_pool_t *scratch_pool)
1108 {
1109 struct replay_baton_t *rb = baton;
1110 svn_stream_t *fstream;
1111 svn_error_t *err;
1112
1113 if (svn_path_is_url(path))
1114 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1115 else if (path[0] == '/')
1116 path += 1;
1117
1118 if (! SVN_IS_VALID_REVNUM(base_revision))
1119 base_revision = rb->current_revision - 1;
1120
1121 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1122 svn_io_file_del_on_pool_cleanup,
1123 result_pool, scratch_pool));
1124
1125 err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
1126 fstream, NULL, NULL, scratch_pool);
1127 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1128 {
1129 svn_error_clear(err);
1130 SVN_ERR(svn_stream_close(fstream));
1131
1132 *filename = NULL;
1133 return SVN_NO_ERROR;
1134 }
1135 else if (err)
1136 return svn_error_trace(err);
1137
1138 SVN_ERR(svn_stream_close(fstream));
1139
1140 return SVN_NO_ERROR;
1141 }
1142
1143 static svn_error_t *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1144 fetch_props_func(apr_hash_t **props,
1145 void *baton,
1146 const char *path,
1147 svn_revnum_t base_revision,
1148 apr_pool_t *result_pool,
1149 apr_pool_t *scratch_pool)
1150 {
1151 struct replay_baton_t *rb = baton;
1152 svn_node_kind_t node_kind;
1153
1154 if (svn_path_is_url(path))
1155 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1156 else if (path[0] == '/')
1157 path += 1;
1158
1159 if (! SVN_IS_VALID_REVNUM(base_revision))
1160 base_revision = rb->current_revision - 1;
1161
1162 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1163 &node_kind, scratch_pool));
1164
1165 if (node_kind == svn_node_file)
1166 {
1167 SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
1168 NULL, NULL, props, result_pool));
1169 }
1170 else if (node_kind == svn_node_dir)
1171 {
1172 apr_array_header_t *tmp_props;
1173
1174 SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
1175 base_revision, 0 /* Dirent fields */,
1176 result_pool));
1177 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1178 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1179 result_pool));
1180 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1181 }
1182 else
1183 {
1184 *props = apr_hash_make(result_pool);
1185 }
1186
1187 return SVN_NO_ERROR;
1188 }
1189
1190 static svn_error_t *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)1191 fetch_kind_func(svn_node_kind_t *kind,
1192 void *baton,
1193 const char *path,
1194 svn_revnum_t base_revision,
1195 apr_pool_t *scratch_pool)
1196 {
1197 struct replay_baton_t *rb = baton;
1198
1199 if (svn_path_is_url(path))
1200 path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1201 else if (path[0] == '/')
1202 path += 1;
1203
1204 if (! SVN_IS_VALID_REVNUM(base_revision))
1205 base_revision = rb->current_revision - 1;
1206
1207 SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1208 kind, scratch_pool));
1209
1210 return SVN_NO_ERROR;
1211 }
1212
1213
1214 static svn_delta_shim_callbacks_t *
get_shim_callbacks(replay_baton_t * rb,apr_pool_t * result_pool)1215 get_shim_callbacks(replay_baton_t *rb,
1216 apr_pool_t *result_pool)
1217 {
1218 svn_delta_shim_callbacks_t *callbacks =
1219 svn_delta_shim_callbacks_default(result_pool);
1220
1221 callbacks->fetch_props_func = fetch_props_func;
1222 callbacks->fetch_kind_func = fetch_kind_func;
1223 callbacks->fetch_base_func = fetch_base_func;
1224 callbacks->fetch_baton = rb;
1225
1226 return callbacks;
1227 }
1228 #endif
1229
1230
1231 /* Callback function for svn_ra_replay_range, invoked when starting to parse
1232 * a replay report.
1233 */
1234 static svn_error_t *
replay_rev_started(svn_revnum_t revision,void * replay_baton,const svn_delta_editor_t ** editor,void ** edit_baton,apr_hash_t * rev_props,apr_pool_t * pool)1235 replay_rev_started(svn_revnum_t revision,
1236 void *replay_baton,
1237 const svn_delta_editor_t **editor,
1238 void **edit_baton,
1239 apr_hash_t *rev_props,
1240 apr_pool_t *pool)
1241 {
1242 const svn_delta_editor_t *commit_editor;
1243 const svn_delta_editor_t *cancel_editor;
1244 const svn_delta_editor_t *sync_editor;
1245 void *commit_baton;
1246 void *cancel_baton;
1247 void *sync_baton;
1248 replay_baton_t *rb = replay_baton;
1249 apr_hash_t *filtered;
1250 int filtered_count;
1251 int normalized_count;
1252
1253 /* We set this property so that if we error out for some reason
1254 we can later determine where we were in the process of
1255 merging a revision. If we had committed the change, but we
1256 hadn't finished copying the revprops we need to know that, so
1257 we can go back and finish the job before we move on.
1258
1259 NOTE: We have to set this before we start the commit editor,
1260 because ra_svn doesn't let you change rev props during a
1261 commit. */
1262 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1263 SVNSYNC_PROP_CURRENTLY_COPYING,
1264 NULL,
1265 svn_string_createf(pool, "%ld", revision),
1266 pool));
1267
1268 /* The actual copy is just a replay hooked up to a commit. Include
1269 all the revision properties from the source repositories, except
1270 'svn:author' and 'svn:date', those are not guaranteed to get
1271 through the editor anyway.
1272 If we're syncing to an non-commit-revprops capable server, filter
1273 out all revprops except svn:log and add them later in
1274 revplay_rev_finished. */
1275 filtered = filter_props(&filtered_count, rev_props,
1276 (rb->has_commit_revprops_capability
1277 ? filter_exclude_date_author_sync
1278 : filter_include_log),
1279 pool);
1280
1281 /* svn_ra_get_commit_editor3 requires the log message to be
1282 set. It's possible that we didn't receive 'svn:log' here, so we
1283 have to set it to at least the empty string. If there's a svn:log
1284 property on this revision, we will write the actual value in the
1285 replay_rev_finished callback. */
1286 if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
1287 svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
1288 svn_string_create_empty(pool));
1289
1290 /* If necessary, normalize encoding and line ending style. Add the number
1291 of properties that required EOL normalization to the overall count
1292 in the replay baton. */
1293 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1294 rb->sb->source_prop_encoding, pool));
1295 rb->normalized_rev_props_count += normalized_count;
1296
1297 #ifdef ENABLE_EV2_SHIMS
1298 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
1299 get_shim_callbacks(rb, pool)));
1300 #endif
1301 SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1302 &commit_baton,
1303 filtered,
1304 commit_callback, rb->sb,
1305 NULL, FALSE, pool));
1306
1307 /* There's one catch though, the diff shows us props we can't send
1308 over the RA interface, so we need an editor that's smart enough
1309 to filter those out for us. */
1310 SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
1311 rb->sb->to_url, rb->sb->source_prop_encoding,
1312 rb->sb->quiet, &sync_editor, &sync_baton,
1313 &(rb->normalized_node_props_count), pool));
1314
1315 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1316 sync_editor, sync_baton,
1317 &cancel_editor,
1318 &cancel_baton,
1319 pool));
1320 *editor = cancel_editor;
1321 *edit_baton = cancel_baton;
1322
1323 rb->current_revision = revision;
1324 return SVN_NO_ERROR;
1325 }
1326
1327 /* Callback function for svn_ra_replay_range, invoked when finishing parsing
1328 * a replay report.
1329 */
1330 static svn_error_t *
replay_rev_finished(svn_revnum_t revision,void * replay_baton,const svn_delta_editor_t * editor,void * edit_baton,apr_hash_t * rev_props,apr_pool_t * pool)1331 replay_rev_finished(svn_revnum_t revision,
1332 void *replay_baton,
1333 const svn_delta_editor_t *editor,
1334 void *edit_baton,
1335 apr_hash_t *rev_props,
1336 apr_pool_t *pool)
1337 {
1338 apr_pool_t *subpool = svn_pool_create(pool);
1339 replay_baton_t *rb = replay_baton;
1340 apr_hash_t *filtered, *existing_props;
1341 int filtered_count;
1342 int normalized_count;
1343 const svn_string_t *rev_str;
1344
1345 SVN_ERR(editor->close_edit(edit_baton, pool));
1346
1347 /* Sanity check that we actually committed the revision we meant to. */
1348 if (rb->sb->committed_rev != revision)
1349 return svn_error_createf
1350 (APR_EINVAL, NULL,
1351 _("Commit created r%ld but should have created r%ld"),
1352 rb->sb->committed_rev, revision);
1353
1354 SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1355 subpool));
1356
1357
1358 /* Ok, we're done with the data, now we just need to copy the remaining
1359 'svn:date' and 'svn:author' revprops and we're all set.
1360 If the server doesn't support revprops-in-a-commit, we still have to
1361 set all revision properties except svn:log. */
1362 filtered = filter_props(&filtered_count, rev_props,
1363 (rb->has_commit_revprops_capability
1364 ? filter_include_date_author_sync
1365 : filter_exclude_log),
1366 subpool);
1367
1368 /* If necessary, normalize encoding and line ending style, and add the number
1369 of EOL-normalized properties to the overall count in the replay baton. */
1370 SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1371 rb->sb->source_prop_encoding, pool));
1372 rb->normalized_rev_props_count += normalized_count;
1373
1374 SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1375 subpool));
1376
1377 /* Remove all extra properties in TARGET. */
1378 SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1379 rev_props, existing_props, subpool));
1380
1381 svn_pool_clear(subpool);
1382
1383 rev_str = svn_string_createf(subpool, "%ld", revision);
1384
1385 /* Ok, we're done, bring the last-merged-rev property up to date. */
1386 SVN_ERR(svn_ra_change_rev_prop2(
1387 rb->to_session,
1388 0,
1389 SVNSYNC_PROP_LAST_MERGED_REV,
1390 NULL,
1391 rev_str,
1392 subpool));
1393
1394 /* And finally drop the currently copying prop, since we're done
1395 with this revision. */
1396 SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1397 SVNSYNC_PROP_CURRENTLY_COPYING,
1398 rb->has_atomic_revprops_capability
1399 ? &rev_str : NULL,
1400 NULL, subpool));
1401
1402 /* Notify the user that we copied revision properties. */
1403 if (! rb->sb->quiet)
1404 SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1405
1406 svn_pool_destroy(subpool);
1407
1408 return SVN_NO_ERROR;
1409 }
1410
1411 /* Synchronize the repository associated with RA session TO_SESSION,
1412 * using information found in BATON.
1413 *
1414 * Implements `with_locked_func_t' interface. The caller has
1415 * acquired a lock on the repository if locking is needed.
1416 */
1417 static svn_error_t *
do_synchronize(svn_ra_session_t * to_session,subcommand_baton_t * baton,apr_pool_t * pool)1418 do_synchronize(svn_ra_session_t *to_session,
1419 subcommand_baton_t *baton, apr_pool_t *pool)
1420 {
1421 svn_string_t *last_merged_rev;
1422 svn_revnum_t from_latest;
1423 svn_ra_session_t *from_session;
1424 svn_string_t *currently_copying;
1425 svn_revnum_t to_latest, copying, last_merged;
1426 svn_revnum_t start_revision, end_revision;
1427 replay_baton_t *rb;
1428 int normalized_rev_props_count = 0;
1429
1430 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1431 baton->from_url, to_session,
1432 &(baton->source_callbacks), baton->config,
1433 baton, pool));
1434
1435 /* Check to see if we have revprops that still need to be copied for
1436 a prior revision we didn't finish copying. But first, check for
1437 state sanity. Remember, mirroring is not an atomic action,
1438 because revision properties are copied separately from the
1439 revision's contents.
1440
1441 So, any time that currently-copying is not set, then
1442 last-merged-rev should be the HEAD revision of the destination
1443 repository. That is, if we didn't fall over in the middle of a
1444 previous synchronization, then our destination repository should
1445 have exactly as many revisions in it as we've synchronized.
1446
1447 Alternately, if currently-copying *is* set, it must
1448 be either last-merged-rev or last-merged-rev + 1, and the HEAD
1449 revision must be equal to either last-merged-rev or
1450 currently-copying. If this is not the case, somebody has meddled
1451 with the destination without using svnsync.
1452 */
1453
1454 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1455 ¤tly_copying, pool));
1456
1457 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1458
1459 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1460
1461 if (currently_copying)
1462 {
1463 copying = SVN_STR_TO_REV(currently_copying->data);
1464
1465 if ((copying < last_merged)
1466 || (copying > (last_merged + 1))
1467 || ((to_latest != last_merged) && (to_latest != copying)))
1468 {
1469 return svn_error_createf
1470 (APR_EINVAL, NULL,
1471 _("Revision being currently copied (%ld), last merged revision "
1472 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1473 "committed to the destination without using svnsync?"),
1474 copying, last_merged, to_latest);
1475 }
1476 else if (copying == to_latest)
1477 {
1478 if (copying > last_merged)
1479 {
1480 SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
1481 baton->quiet, baton->source_prop_encoding,
1482 &normalized_rev_props_count, pool));
1483 last_merged = copying;
1484 last_merged_rev = svn_string_create
1485 (apr_psprintf(pool, "%ld", last_merged), pool);
1486 }
1487
1488 /* Now update last merged rev and drop currently changing.
1489 Note that the order here is significant, if we do them
1490 in the wrong order there are race conditions where we
1491 end up not being able to tell if there have been bogus
1492 (i.e. non-svnsync) commits to the dest repository. */
1493
1494 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1495 SVNSYNC_PROP_LAST_MERGED_REV,
1496 NULL, last_merged_rev, pool));
1497 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1498 SVNSYNC_PROP_CURRENTLY_COPYING,
1499 NULL, NULL, pool));
1500 }
1501 /* If copying > to_latest, then we just fall through to
1502 attempting to copy the revision again. */
1503 }
1504 else
1505 {
1506 if (to_latest != last_merged)
1507 return svn_error_createf(APR_EINVAL, NULL,
1508 _("Destination HEAD (%ld) is not the last "
1509 "merged revision (%ld); have you "
1510 "committed to the destination without "
1511 "using svnsync?"),
1512 to_latest, last_merged);
1513 }
1514
1515 /* Now check to see if there are any revisions to copy. */
1516 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1517
1518 if (from_latest < last_merged)
1519 return SVN_NO_ERROR;
1520
1521 /* Ok, so there are new revisions, iterate over them copying them
1522 into the destination repository. */
1523 SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
1524
1525 /* For compatibility with older svnserve versions, check first if we
1526 support adding revprops to the commit. */
1527 SVN_ERR(svn_ra_has_capability(rb->to_session,
1528 &rb->has_commit_revprops_capability,
1529 SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1530 pool));
1531
1532 SVN_ERR(svn_ra_has_capability(rb->to_session,
1533 &rb->has_atomic_revprops_capability,
1534 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1535 pool));
1536
1537 start_revision = last_merged + 1;
1538 end_revision = from_latest;
1539
1540 SVN_ERR(check_cancel(NULL));
1541
1542 SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1543 0, TRUE, replay_rev_started,
1544 replay_rev_finished, rb, pool));
1545
1546 SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
1547 + normalized_rev_props_count,
1548 rb->normalized_node_props_count,
1549 pool));
1550
1551
1552 return SVN_NO_ERROR;
1553 }
1554
1555
1556 /* SUBCOMMAND: sync */
1557 static svn_error_t *
synchronize_cmd(apr_getopt_t * os,void * b,apr_pool_t * pool)1558 synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1559 {
1560 svn_ra_session_t *to_session;
1561 opt_baton_t *opt_baton = b;
1562 apr_array_header_t *targets;
1563 subcommand_baton_t *baton;
1564 const char *to_url, *from_url;
1565
1566 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1567 apr_array_make(pool, 0,
1568 sizeof(const char *)),
1569 pool));
1570 if (targets->nelts < 1)
1571 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1572 if (targets->nelts > 2)
1573 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1574
1575 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1576 if (! svn_path_is_url(to_url))
1577 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1578 _("Path '%s' is not a URL"), to_url);
1579
1580 if (targets->nelts == 2)
1581 {
1582 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1583 if (! svn_path_is_url(from_url))
1584 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1585 _("Path '%s' is not a URL"), from_url);
1586 }
1587 else
1588 {
1589 from_url = NULL; /* we'll read it from the destination repos */
1590 }
1591
1592 baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
1593 SVN_ERR(open_target_session(&to_session, baton, pool));
1594 if (opt_baton->disable_locking)
1595 SVN_ERR(do_synchronize(to_session, baton, pool));
1596 else
1597 SVN_ERR(with_locked(to_session, do_synchronize, baton,
1598 opt_baton->steal_lock, pool));
1599
1600 return SVN_NO_ERROR;
1601 }
1602
1603
1604
1605 /*** `svnsync copy-revprops' ***/
1606
1607 /* Copy revision properties to the repository associated with RA
1608 * session TO_SESSION, using information found in BATON.
1609 *
1610 * Implements `with_locked_func_t' interface. The caller has
1611 * acquired a lock on the repository if locking is needed.
1612 */
1613 static svn_error_t *
do_copy_revprops(svn_ra_session_t * to_session,subcommand_baton_t * baton,apr_pool_t * pool)1614 do_copy_revprops(svn_ra_session_t *to_session,
1615 subcommand_baton_t *baton, apr_pool_t *pool)
1616 {
1617 svn_ra_session_t *from_session;
1618 svn_string_t *last_merged_rev;
1619 svn_revnum_t i;
1620 svn_revnum_t step = 1;
1621 int normalized_rev_props_count = 0;
1622
1623 SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1624 baton->from_url, to_session,
1625 &(baton->source_callbacks), baton->config,
1626 baton, pool));
1627
1628 /* An invalid revision means "last-synced" */
1629 if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1630 baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1631 if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1632 baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1633
1634 /* Make sure we have revisions within the valid range. */
1635 if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1636 return svn_error_createf
1637 (APR_EINVAL, NULL,
1638 _("Cannot copy revprops for a revision (%ld) that has not "
1639 "been synchronized yet"), baton->start_rev);
1640 if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1641 return svn_error_createf
1642 (APR_EINVAL, NULL,
1643 _("Cannot copy revprops for a revision (%ld) that has not "
1644 "been synchronized yet"), baton->end_rev);
1645
1646 /* Now, copy all the requested revisions, in the requested order. */
1647 step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1648 for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1649 {
1650 int normalized_count;
1651 SVN_ERR(check_cancel(NULL));
1652 SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
1653 baton->source_prop_encoding, &normalized_count,
1654 pool));
1655 normalized_rev_props_count += normalized_count;
1656 }
1657
1658 /* Notify about normalized props, if any. */
1659 SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
1660
1661 return SVN_NO_ERROR;
1662 }
1663
1664
1665 /* Set *START_REVNUM to the revision number associated with
1666 START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
1667 represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
1668 the revision number associated with END_REVISION or to
1669 SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
1670 END_REVNUM to the same value as START_REVNUM.
1671
1672 As a special case, if neither START_REVISION nor END_REVISION is
1673 specified, set *START_REVNUM to 0 and set *END_REVNUM to
1674 SVN_INVALID_REVNUM.
1675
1676 Freak out if either START_REVISION or END_REVISION represents an
1677 explicit but invalid revision number. */
1678 static svn_error_t *
resolve_revnums(svn_revnum_t * start_revnum,svn_revnum_t * end_revnum,svn_opt_revision_t start_revision,svn_opt_revision_t end_revision)1679 resolve_revnums(svn_revnum_t *start_revnum,
1680 svn_revnum_t *end_revnum,
1681 svn_opt_revision_t start_revision,
1682 svn_opt_revision_t end_revision)
1683 {
1684 svn_revnum_t start_rev, end_rev;
1685
1686 /* Special case: neither revision is specified? This is like
1687 -r0:HEAD. */
1688 if ((start_revision.kind == svn_opt_revision_unspecified) &&
1689 (end_revision.kind == svn_opt_revision_unspecified))
1690 {
1691 *start_revnum = 0;
1692 *end_revnum = SVN_INVALID_REVNUM;
1693 return SVN_NO_ERROR;
1694 }
1695
1696 /* Get the start revision, which must be either HEAD or a number
1697 (which is required to be a valid one). */
1698 if (start_revision.kind == svn_opt_revision_head)
1699 {
1700 start_rev = SVN_INVALID_REVNUM;
1701 }
1702 else
1703 {
1704 start_rev = start_revision.value.number;
1705 if (! SVN_IS_VALID_REVNUM(start_rev))
1706 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1707 _("Invalid revision number (%ld)"),
1708 start_rev);
1709 }
1710
1711 /* Get the end revision, which must be unspecified (meaning,
1712 "same as the start_rev"), HEAD, or a number (which is
1713 required to be a valid one). */
1714 if (end_revision.kind == svn_opt_revision_unspecified)
1715 {
1716 end_rev = start_rev;
1717 }
1718 else if (end_revision.kind == svn_opt_revision_head)
1719 {
1720 end_rev = SVN_INVALID_REVNUM;
1721 }
1722 else
1723 {
1724 end_rev = end_revision.value.number;
1725 if (! SVN_IS_VALID_REVNUM(end_rev))
1726 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1727 _("Invalid revision number (%ld)"),
1728 end_rev);
1729 }
1730
1731 *start_revnum = start_rev;
1732 *end_revnum = end_rev;
1733 return SVN_NO_ERROR;
1734 }
1735
1736
1737 /* SUBCOMMAND: copy-revprops */
1738 static svn_error_t *
copy_revprops_cmd(apr_getopt_t * os,void * b,apr_pool_t * pool)1739 copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1740 {
1741 svn_ra_session_t *to_session;
1742 opt_baton_t *opt_baton = b;
1743 apr_array_header_t *targets;
1744 subcommand_baton_t *baton;
1745 const char *to_url = NULL;
1746 const char *from_url = NULL;
1747 svn_opt_revision_t start_revision, end_revision;
1748 svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1749
1750 /* There should be either one or two arguments left to parse. */
1751 if (os->argc - os->ind > 2)
1752 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1753 if (os->argc - os->ind < 1)
1754 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1755
1756 /* If there are two args, the last one is either a revision range or
1757 the source URL. */
1758 if (os->argc - os->ind == 2)
1759 {
1760 const char *arg_str = os->argv[os->argc - 1];
1761 const char *utf_arg_str;
1762
1763 SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
1764
1765 if (! svn_path_is_url(utf_arg_str))
1766 {
1767 /* This is the old "... TO_URL REV[:REV2]" syntax.
1768 Revisions come only from this argument. (We effectively
1769 pop that last argument from the end of the argument list
1770 so svn_opt__args_to_target_array() can do its thang.) */
1771 os->argc--;
1772
1773 if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
1774 || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
1775 return svn_error_create(
1776 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1777 _("Cannot specify revisions via both command-line arguments "
1778 "and the --revision (-r) option"));
1779
1780 start_revision.kind = svn_opt_revision_unspecified;
1781 end_revision.kind = svn_opt_revision_unspecified;
1782 if (svn_opt_parse_revision(&start_revision, &end_revision,
1783 arg_str, pool) != 0)
1784 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1785 _("Invalid revision range '%s' provided"),
1786 arg_str);
1787
1788 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1789 start_revision, end_revision));
1790
1791 SVN_ERR(svn_opt__args_to_target_array(
1792 &targets, os,
1793 apr_array_make(pool, 1, sizeof(const char *)), pool));
1794 if (targets->nelts != 1)
1795 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1796 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1797 from_url = NULL;
1798 }
1799 }
1800
1801 if (! to_url)
1802 {
1803 /* This is the "... TO_URL SOURCE_URL" syntax. Revisions
1804 come only from the --revision parameter. */
1805 SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1806 opt_baton->start_rev, opt_baton->end_rev));
1807
1808 SVN_ERR(svn_opt__args_to_target_array(
1809 &targets, os,
1810 apr_array_make(pool, 2, sizeof(const char *)), pool));
1811 if (targets->nelts < 1)
1812 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1813 if (targets->nelts > 2)
1814 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1815 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1816 if (targets->nelts == 2)
1817 from_url = APR_ARRAY_IDX(targets, 1, const char *);
1818 else
1819 from_url = NULL;
1820 }
1821
1822 if (! svn_path_is_url(to_url))
1823 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1824 _("Path '%s' is not a URL"), to_url);
1825 if (from_url && (! svn_path_is_url(from_url)))
1826 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1827 _("Path '%s' is not a URL"), from_url);
1828
1829 baton = make_subcommand_baton(opt_baton, to_url, from_url,
1830 start_rev, end_rev, pool);
1831 SVN_ERR(open_target_session(&to_session, baton, pool));
1832 if (opt_baton->disable_locking)
1833 SVN_ERR(do_copy_revprops(to_session, baton, pool));
1834 else
1835 SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
1836 opt_baton->steal_lock, pool));
1837
1838 return SVN_NO_ERROR;
1839 }
1840
1841
1842
1843 /*** `svnsync info' ***/
1844
1845
1846 /* SUBCOMMAND: info */
1847 static svn_error_t *
info_cmd(apr_getopt_t * os,void * b,apr_pool_t * pool)1848 info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1849 {
1850 svn_ra_session_t *to_session;
1851 opt_baton_t *opt_baton = b;
1852 apr_array_header_t *targets;
1853 subcommand_baton_t *baton;
1854 const char *to_url;
1855 apr_hash_t *props;
1856 svn_string_t *from_url, *from_uuid, *last_merged_rev;
1857
1858 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1859 apr_array_make(pool, 0,
1860 sizeof(const char *)),
1861 pool));
1862 if (targets->nelts < 1)
1863 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1864 if (targets->nelts > 1)
1865 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1866
1867 /* Get the mirror repository URL, and verify that it is URL-ish. */
1868 to_url = APR_ARRAY_IDX(targets, 0, const char *);
1869 if (! svn_path_is_url(to_url))
1870 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1871 _("Path '%s' is not a URL"), to_url);
1872
1873 /* Open an RA session to the mirror repository URL. */
1874 baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1875 SVN_ERR(open_target_session(&to_session, baton, pool));
1876
1877 SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
1878
1879 from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
1880
1881 if (! from_url)
1882 return svn_error_createf
1883 (SVN_ERR_BAD_URL, NULL,
1884 _("Repository '%s' is not initialized for synchronization"), to_url);
1885
1886 from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
1887 last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
1888
1889 /* Print the info. */
1890 SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1891 if (from_uuid)
1892 SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1893 from_uuid->data));
1894 if (last_merged_rev)
1895 SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1896 last_merged_rev->data));
1897 return SVN_NO_ERROR;
1898 }
1899
1900
1901
1902 /*** `svnsync help' ***/
1903
1904
1905 /* SUBCOMMAND: help */
1906 static svn_error_t *
help_cmd(apr_getopt_t * os,void * baton,apr_pool_t * pool)1907 help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1908 {
1909 opt_baton_t *opt_baton = baton;
1910
1911 const char *header =
1912 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
1913 "Subversion repository replication tool.\n"
1914 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1915 "Type 'svnsync --version' to see the program version and RA modules.\n"
1916 "\n"
1917 "Available subcommands:\n");
1918
1919 const char *ra_desc_start
1920 = _("The following repository access (RA) modules are available:\n\n");
1921
1922 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1923 pool);
1924
1925 SVN_ERR(svn_ra_print_modules(version_footer, pool));
1926
1927 SVN_ERR(svn_opt_print_help4(os, "svnsync",
1928 opt_baton ? opt_baton->version : FALSE,
1929 opt_baton ? opt_baton->quiet : FALSE,
1930 /*###opt_state ? opt_state->verbose :*/ FALSE,
1931 version_footer->data, header,
1932 svnsync_cmd_table, svnsync_options, NULL,
1933 NULL, pool));
1934
1935 return SVN_NO_ERROR;
1936 }
1937
1938
1939
1940 /*** Main ***/
1941
1942 /*
1943 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1944 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1945 * return SVN_NO_ERROR.
1946 */
1947 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)1948 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1949 {
1950 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1951 apr_array_header_t *received_opts;
1952 opt_baton_t opt_baton;
1953 svn_config_t *config;
1954 apr_status_t apr_err;
1955 apr_getopt_t *os;
1956 svn_error_t *err;
1957 int opt_id, i;
1958 const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1959 const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1960 apr_array_header_t *config_options = NULL;
1961 const char *source_prop_encoding = NULL;
1962 svn_boolean_t force_interactive = FALSE;
1963
1964 /* Check library versions */
1965 SVN_ERR(check_lib_versions());
1966
1967 SVN_ERR(svn_ra_initialize(pool));
1968
1969 /* Initialize the option baton. */
1970 memset(&opt_baton, 0, sizeof(opt_baton));
1971 opt_baton.start_rev.kind = svn_opt_revision_unspecified;
1972 opt_baton.end_rev.kind = svn_opt_revision_unspecified;
1973
1974 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1975
1976 if (argc <= 1)
1977 {
1978 SVN_ERR(help_cmd(NULL, NULL, pool));
1979 *exit_code = EXIT_FAILURE;
1980 return SVN_NO_ERROR;
1981 }
1982
1983 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1984
1985 os->interleave = 1;
1986
1987 for (;;)
1988 {
1989 const char *opt_arg;
1990 svn_error_t* opt_err = NULL;
1991
1992 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
1993 if (APR_STATUS_IS_EOF(apr_err))
1994 break;
1995 else if (apr_err)
1996 {
1997 SVN_ERR(help_cmd(NULL, NULL, pool));
1998 *exit_code = EXIT_FAILURE;
1999 return SVN_NO_ERROR;
2000 }
2001
2002 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2003
2004 switch (opt_id)
2005 {
2006 case svnsync_opt_non_interactive:
2007 opt_baton.non_interactive = TRUE;
2008 break;
2009
2010 case svnsync_opt_force_interactive:
2011 force_interactive = TRUE;
2012 break;
2013
2014 case svnsync_opt_trust_server_cert: /* backwards compat */
2015 opt_baton.src_trust.trust_server_cert_unknown_ca = TRUE;
2016 opt_baton.dst_trust.trust_server_cert_unknown_ca = TRUE;
2017 break;
2018
2019 case svnsync_opt_trust_server_cert_failures_src:
2020 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2021 SVN_ERR(svn_cmdline__parse_trust_options(
2022 &opt_baton.src_trust.trust_server_cert_unknown_ca,
2023 &opt_baton.src_trust.trust_server_cert_cn_mismatch,
2024 &opt_baton.src_trust.trust_server_cert_expired,
2025 &opt_baton.src_trust.trust_server_cert_not_yet_valid,
2026 &opt_baton.src_trust.trust_server_cert_other_failure,
2027 opt_arg, pool));
2028 break;
2029
2030 case svnsync_opt_trust_server_cert_failures_dst:
2031 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2032 SVN_ERR(svn_cmdline__parse_trust_options(
2033 &opt_baton.dst_trust.trust_server_cert_unknown_ca,
2034 &opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2035 &opt_baton.dst_trust.trust_server_cert_expired,
2036 &opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2037 &opt_baton.dst_trust.trust_server_cert_other_failure,
2038 opt_arg, pool));
2039 break;
2040
2041 case svnsync_opt_no_auth_cache:
2042 opt_baton.no_auth_cache = TRUE;
2043 break;
2044
2045 case svnsync_opt_auth_username:
2046 opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
2047 break;
2048
2049 case svnsync_opt_auth_password:
2050 opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
2051 break;
2052
2053 case svnsync_opt_source_username:
2054 opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
2055 break;
2056
2057 case svnsync_opt_source_password:
2058 opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
2059 break;
2060
2061 case svnsync_opt_sync_username:
2062 opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
2063 break;
2064
2065 case svnsync_opt_sync_password:
2066 opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
2067 break;
2068
2069 case svnsync_opt_config_dir:
2070 {
2071 const char *path_utf8;
2072 opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
2073
2074 if (!opt_err)
2075 opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
2076 }
2077 break;
2078 case svnsync_opt_config_options:
2079 if (!config_options)
2080 config_options =
2081 apr_array_make(pool, 1,
2082 sizeof(svn_cmdline__config_argument_t*));
2083
2084 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2085 SVN_ERR(svn_cmdline__parse_config_option(config_options,
2086 opt_arg, "svnsync: ",
2087 pool));
2088 break;
2089
2090 case svnsync_opt_source_prop_encoding:
2091 opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
2092 pool);
2093 break;
2094
2095 case svnsync_opt_disable_locking:
2096 opt_baton.disable_locking = TRUE;
2097 break;
2098
2099 case svnsync_opt_steal_lock:
2100 opt_baton.steal_lock = TRUE;
2101 break;
2102
2103 case svnsync_opt_version:
2104 opt_baton.version = TRUE;
2105 break;
2106
2107 case svnsync_opt_allow_non_empty:
2108 opt_baton.allow_non_empty = TRUE;
2109 break;
2110
2111 case 'q':
2112 opt_baton.quiet = TRUE;
2113 break;
2114
2115 case 'r':
2116 if (svn_opt_parse_revision(&opt_baton.start_rev,
2117 &opt_baton.end_rev,
2118 opt_arg, pool) != 0)
2119 {
2120 const char *utf8_opt_arg;
2121 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2122 return svn_error_createf(
2123 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2124 _("Syntax error in revision argument '%s'"),
2125 utf8_opt_arg);
2126 }
2127
2128 /* We only allow numbers and 'HEAD'. */
2129 if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
2130 (opt_baton.start_rev.kind != svn_opt_revision_head))
2131 || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
2132 (opt_baton.end_rev.kind != svn_opt_revision_head) &&
2133 (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
2134 {
2135 return svn_error_createf(
2136 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2137 _("Invalid revision range '%s' provided"), opt_arg);
2138 }
2139 break;
2140
2141 case 'M':
2142 if (!config_options)
2143 config_options =
2144 apr_array_make(pool, 1,
2145 sizeof(svn_cmdline__config_argument_t*));
2146
2147 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2148 SVN_ERR(svn_cmdline__parse_config_option(
2149 config_options,
2150 apr_psprintf(pool,
2151 "config:miscellany:memory-cache-size=%s",
2152 opt_arg),
2153 NULL /* won't be used */,
2154 pool));
2155 break;
2156
2157 case '?':
2158 case 'h':
2159 opt_baton.help = TRUE;
2160 break;
2161
2162 default:
2163 {
2164 SVN_ERR(help_cmd(NULL, NULL, pool));
2165 *exit_code = EXIT_FAILURE;
2166 return SVN_NO_ERROR;
2167 }
2168 }
2169
2170 if (opt_err)
2171 return opt_err;
2172 }
2173
2174 if (opt_baton.help)
2175 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2176
2177 /* The --non-interactive and --force-interactive options are mutually
2178 * exclusive. */
2179 if (opt_baton.non_interactive && force_interactive)
2180 {
2181 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2182 _("--non-interactive and --force-interactive "
2183 "are mutually exclusive"));
2184 }
2185 else
2186 opt_baton.non_interactive = !svn_cmdline__be_interactive(
2187 opt_baton.non_interactive,
2188 force_interactive);
2189
2190 /* Disallow the mixing --username/password with their --source- and
2191 --sync- variants. Treat "--username FOO" as "--source-username
2192 FOO --sync-username FOO"; ditto for "--password FOO". */
2193 if ((username || password)
2194 && (source_username || sync_username
2195 || source_password || sync_password))
2196 {
2197 return svn_error_create
2198 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2199 _("Cannot use --username or --password with any of "
2200 "--source-username, --source-password, --sync-username, "
2201 "or --sync-password.\n"));
2202 }
2203 if (username)
2204 {
2205 source_username = username;
2206 sync_username = username;
2207 }
2208 if (password)
2209 {
2210 source_password = password;
2211 sync_password = password;
2212 }
2213 opt_baton.source_username = source_username;
2214 opt_baton.source_password = source_password;
2215 opt_baton.sync_username = sync_username;
2216 opt_baton.sync_password = sync_password;
2217
2218 /* Disallow mixing of --steal-lock and --disable-locking. */
2219 if (opt_baton.steal_lock && opt_baton.disable_locking)
2220 {
2221 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2222 _("--disable-locking and --steal-lock are "
2223 "mutually exclusive"));
2224 }
2225
2226 /* --trust-* can only be used with --non-interactive */
2227 if (!opt_baton.non_interactive)
2228 {
2229 if (opt_baton.src_trust.trust_server_cert_unknown_ca
2230 || opt_baton.src_trust.trust_server_cert_cn_mismatch
2231 || opt_baton.src_trust.trust_server_cert_expired
2232 || opt_baton.src_trust.trust_server_cert_not_yet_valid
2233 || opt_baton.src_trust.trust_server_cert_other_failure
2234 || opt_baton.dst_trust.trust_server_cert_unknown_ca
2235 || opt_baton.dst_trust.trust_server_cert_cn_mismatch
2236 || opt_baton.dst_trust.trust_server_cert_expired
2237 || opt_baton.dst_trust.trust_server_cert_not_yet_valid
2238 || opt_baton.dst_trust.trust_server_cert_other_failure)
2239 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2240 _("--source-trust-server-cert-failures "
2241 "and "
2242 "--sync-trust-server-cert-failures require "
2243 "--non-interactive"));
2244 }
2245
2246 SVN_ERR(svn_config_ensure(opt_baton.config_dir, pool));
2247
2248 if (subcommand == NULL)
2249 {
2250 if (os->ind >= os->argc)
2251 {
2252 if (opt_baton.version)
2253 {
2254 /* Use the "help" subcommand to handle "--version". */
2255 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2256 { "--version", help_cmd, {0}, "",
2257 {svnsync_opt_version, /* must accept its own option */
2258 'q', /* --quiet */
2259 } };
2260
2261 subcommand = &pseudo_cmd;
2262 }
2263 else
2264 {
2265 SVN_ERR(help_cmd(NULL, NULL, pool));
2266 *exit_code = EXIT_FAILURE;
2267 return SVN_NO_ERROR;
2268 }
2269 }
2270 else
2271 {
2272 const char *first_arg = os->argv[os->ind++];
2273 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2274 first_arg);
2275 if (subcommand == NULL)
2276 {
2277 SVN_ERR(help_cmd(NULL, NULL, pool));
2278 *exit_code = EXIT_FAILURE;
2279 return SVN_NO_ERROR;
2280 }
2281 }
2282 }
2283
2284 for (i = 0; i < received_opts->nelts; ++i)
2285 {
2286 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2287
2288 if (opt_id == 'h' || opt_id == '?')
2289 continue;
2290
2291 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2292 {
2293 const char *optstr;
2294 const apr_getopt_option_t *badopt =
2295 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2296 pool);
2297 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2298 if (subcommand->name[0] == '-')
2299 {
2300 SVN_ERR(help_cmd(NULL, NULL, pool));
2301 }
2302 else
2303 {
2304 return svn_error_createf
2305 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2306 _("Subcommand '%s' doesn't accept option '%s'\n"
2307 "Type 'svnsync help %s' for usage.\n"),
2308 subcommand->name, optstr, subcommand->name);
2309 }
2310 }
2311 }
2312
2313 SVN_ERR(svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool));
2314
2315 /* Update the options in the config */
2316 if (config_options)
2317 {
2318 svn_error_clear(
2319 svn_cmdline__apply_config_options(opt_baton.config, config_options,
2320 "svnsync: ", "--config-option"));
2321 }
2322
2323 config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
2324
2325 opt_baton.source_prop_encoding = source_prop_encoding;
2326
2327 apr_signal(SIGINT, signal_handler);
2328
2329 #ifdef SIGBREAK
2330 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2331 apr_signal(SIGBREAK, signal_handler);
2332 #endif
2333
2334 #ifdef SIGHUP
2335 apr_signal(SIGHUP, signal_handler);
2336 #endif
2337
2338 #ifdef SIGTERM
2339 apr_signal(SIGTERM, signal_handler);
2340 #endif
2341
2342 #ifdef SIGPIPE
2343 /* Disable SIGPIPE generation for the platforms that have it. */
2344 apr_signal(SIGPIPE, SIG_IGN);
2345 #endif
2346
2347 #ifdef SIGXFSZ
2348 /* Disable SIGXFSZ generation for the platforms that have it,
2349 otherwise working with large files when compiled against an APR
2350 that doesn't have large file support will crash the program,
2351 which is uncool. */
2352 apr_signal(SIGXFSZ, SIG_IGN);
2353 #endif
2354
2355 err = svn_cmdline_create_auth_baton2(
2356 &opt_baton.source_auth_baton,
2357 opt_baton.non_interactive,
2358 opt_baton.source_username,
2359 opt_baton.source_password,
2360 opt_baton.config_dir,
2361 opt_baton.no_auth_cache,
2362 opt_baton.src_trust.trust_server_cert_unknown_ca,
2363 opt_baton.src_trust.trust_server_cert_cn_mismatch,
2364 opt_baton.src_trust.trust_server_cert_expired,
2365 opt_baton.src_trust.trust_server_cert_not_yet_valid,
2366 opt_baton.src_trust.trust_server_cert_other_failure,
2367 config,
2368 check_cancel, NULL,
2369 pool);
2370 if (! err)
2371 err = svn_cmdline_create_auth_baton2(
2372 &opt_baton.sync_auth_baton,
2373 opt_baton.non_interactive,
2374 opt_baton.sync_username,
2375 opt_baton.sync_password,
2376 opt_baton.config_dir,
2377 opt_baton.no_auth_cache,
2378 opt_baton.dst_trust.trust_server_cert_unknown_ca,
2379 opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2380 opt_baton.dst_trust.trust_server_cert_expired,
2381 opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2382 opt_baton.dst_trust.trust_server_cert_other_failure,
2383 config,
2384 check_cancel, NULL,
2385 pool);
2386 if (! err)
2387 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2388 if (err)
2389 {
2390 /* For argument-related problems, suggest using the 'help'
2391 subcommand. */
2392 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2393 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2394 {
2395 err = svn_error_quick_wrap(err,
2396 _("Try 'svnsync help' for more info"));
2397 }
2398
2399 return err;
2400 }
2401
2402 return SVN_NO_ERROR;
2403 }
2404
2405 int
main(int argc,const char * argv[])2406 main(int argc, const char *argv[])
2407 {
2408 apr_pool_t *pool;
2409 int exit_code = EXIT_SUCCESS;
2410 svn_error_t *err;
2411
2412 /* Initialize the app. */
2413 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
2414 return EXIT_FAILURE;
2415
2416 /* Create our top-level pool. Use a separate mutexless allocator,
2417 * given this application is single threaded.
2418 */
2419 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2420
2421 err = sub_main(&exit_code, argc, argv, pool);
2422
2423 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2424 but this makes sure that output is not silently lost if it fails. */
2425 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2426
2427 if (err)
2428 {
2429 exit_code = EXIT_FAILURE;
2430 svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
2431 }
2432
2433 svn_pool_destroy(pool);
2434 return exit_code;
2435 }
2436