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