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                           &currently_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