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