~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/svnsync/svnsync.c

  • Committer: Package Import Robot
  • Author(s): James McCoy, Peter Samuelson, James McCoy
  • Date: 2014-01-12 19:48:33 UTC
  • mfrom: (0.2.10)
  • Revision ID: package-import@ubuntu.com-20140112194833-w3axfwksn296jn5x
Tags: 1.8.5-1
[ Peter Samuelson ]
* New upstream release.  (Closes: #725787) Rediff patches:
  - Remove apr-abi1 (applied upstream), rename apr-abi2 to apr-abi
  - Remove loosen-sqlite-version-check (shouldn't be needed)
  - Remove java-osgi-metadata (applied upstream)
  - svnmucc prompts for a changelog if none is provided. (Closes: #507430)
  - Remove fix-bdb-version-detection, upstream uses "apu-config --dbm-libs"
  - Remove ruby-test-wc (applied upstream)
  - Fix “svn diff -r N file” when file has svn:mime-type set.
    (Closes: #734163)
  - Support specifying an encoding for mod_dav_svn's environment in which
    hooks are run.  (Closes: #601544)
  - Fix ordering of “svnadmin dump” paths with certain APR versions.
    (Closes: #687291)
  - Provide a better error message when authentication fails with an
    svn+ssh:// URL.  (Closes: #273874)
  - Updated Polish translations.  (Closes: #690815)

[ James McCoy ]
* Remove all traces of libneon, replaced by libserf.
* patches/sqlite_3.8.x_workaround: Upstream fix for wc-queries-test test
  failurse.
* Run configure with --with-apache-libexecdir, which allows removing part of
  patches/rpath.
* Re-enable auth-test as upstream has fixed the problem of picking up
  libraries from the environment rather than the build tree.
  (Closes: #654172)
* Point LD_LIBRARY_PATH at the built auth libraries when running the svn
  command during the build.  (Closes: #678224)
* Add a NEWS entry describing how to configure mod_dav_svn to understand
  UTF-8.  (Closes: #566148)
* Remove ancient transitional package, libsvn-ruby.
* Enable compatibility with Sqlite3 versions back to Wheezy.
* Enable hardening flags.  (Closes: #734918)
* patches/build-fixes: Enable verbose build logs.
* Build against the default ruby version.  (Closes: #722393)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
#include "private/svn_subr_private.h"
 
42
 
 
43
#include "sync.h"
 
44
 
 
45
#include "svn_private_config.h"
 
46
 
 
47
#include <apr_signal.h>
 
48
#include <apr_uuid.h>
 
49
 
 
50
static svn_opt_subcommand_t initialize_cmd,
 
51
                            synchronize_cmd,
 
52
                            copy_revprops_cmd,
 
53
                            info_cmd,
 
54
                            help_cmd;
 
55
 
 
56
enum svnsync__opt {
 
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,
 
70
  svnsync_opt_version,
 
71
  svnsync_opt_trust_server_cert,
 
72
  svnsync_opt_allow_non_empty,
 
73
  svnsync_opt_steal_lock
 
74
};
 
75
 
 
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
 
88
 
 
89
static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
 
90
  {
 
91
    { "initialize", initialize_cmd, { "init" },
 
92
      N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
 
93
         "\n"
 
94
         "Initialize a destination repository for synchronization from\n"
 
95
         "another repository.\n"
 
96
         "\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"
 
99
         "\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"
 
109
         "\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"
 
119
         "\n"
 
120
         "Transfer all pending revisions to the destination from the source\n"
 
121
         "with which it was initialized.\n"
 
122
         "\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 },
 
131
      N_("usage:\n"
 
132
         "\n"
 
133
         "    1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
 
134
         "    2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
 
135
         "\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"
 
141
         "\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"
 
147
         "\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"
 
153
         "\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"
 
159
         "\n"
 
160
         "Describe the usage of this program or its subcommands.\n"),
 
161
      { 0 } },
 
162
    { NULL, NULL, { 0 }, NULL, { 0 } }
 
163
  };
 
164
 
 
165
static const apr_getopt_option_t svnsync_options[] =
 
166
  {
 
167
    {"quiet",          'q', 0,
 
168
                       N_("print as little as possible") },
 
169
    {"revision",       'r', 1,
 
170
                       N_("operate on revision ARG (or range ARG1:ARG2)\n"
 
171
                          "                             "
 
172
                          "A revision argument can be one of:\n"
 
173
                          "                             "
 
174
                          "    NUMBER       revision number\n"
 
175
                          "                             "
 
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"
 
181
                          "                             "
 
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"
 
185
                         "                             "
 
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"
 
191
                          "                             "
 
192
                          "see --source-username and --sync-username)") },
 
193
    {"password",       svnsync_opt_auth_password, 1,
 
194
                       N_("specify a password ARG (deprecated;\n"
 
195
                          "                             "
 
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"
 
199
                          "                             "
 
200
                          "certificate authorities without prompting (but only\n"
 
201
                          "                             "
 
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"
 
215
                          "                             "
 
216
                          "    FILE:SECTION:OPTION=[VALUE]\n"
 
217
                          "                             "
 
218
                          "For example:\n"
 
219
                          "                             "
 
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"
 
223
                          "                             "
 
224
                          "to UTF-8. If not specified, then properties are\n"
 
225
                          "                             "
 
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"
 
229
                          "                             "
 
230
                          "corrupt the mirror unless you ensure that no other\n"
 
231
                          "                             "
 
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"
 
235
                          "                             "
 
236
                          "if your mirror repository contains stale locks\n"
 
237
                          "                             "
 
238
                          "and is not being concurrently accessed by another\n"
 
239
                          "                             "
 
240
                          "svnsync instance.")},
 
241
    {"version",        svnsync_opt_version, 0,
 
242
                       N_("show program version information")},
 
243
    {"help",           'h', 0,
 
244
                       N_("show help on a subcommand")},
 
245
    {NULL,             '?', 0,
 
246
                       N_("show help on a subcommand")},
 
247
    { 0, 0, 0, 0 }
 
248
  };
 
249
 
 
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;
 
261
  apr_hash_t *config;
 
262
  const char *source_prop_encoding;
 
263
  svn_boolean_t disable_locking;
 
264
  svn_boolean_t steal_lock;
 
265
  svn_boolean_t quiet;
 
266
  svn_boolean_t allow_non_empty;
 
267
  svn_boolean_t version;
 
268
  svn_boolean_t help;
 
269
  svn_opt_revision_t start_rev;
 
270
  svn_opt_revision_t end_rev;
 
271
} opt_baton_t;
 
272
 
 
273
 
 
274
 
 
275
 
 
276
/*** Helper functions ***/
 
277
 
 
278
 
 
279
/* Global record of whether the user has requested cancellation. */
 
280
static volatile sig_atomic_t cancelled = FALSE;
 
281
 
 
282
 
 
283
/* Callback function for apr_signal(). */
 
284
static void
 
285
signal_handler(int signum)
 
286
{
 
287
  apr_signal(signum, SIG_IGN);
 
288
  cancelled = TRUE;
 
289
}
 
290
 
 
291
 
 
292
/* Cancellation callback function. */
 
293
static svn_error_t *
 
294
check_cancel(void *baton)
 
295
{
 
296
  if (cancelled)
 
297
    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
 
298
  else
 
299
    return SVN_NO_ERROR;
 
300
}
 
301
 
 
302
 
 
303
/* Check that the version of libraries in use match what we expect. */
 
304
static svn_error_t *
 
305
check_lib_versions(void)
 
306
{
 
307
  static const svn_version_checklist_t checklist[] =
 
308
    {
 
309
      { "svn_subr",  svn_subr_version },
 
310
      { "svn_delta", svn_delta_version },
 
311
      { "svn_ra",    svn_ra_version },
 
312
      { NULL, NULL }
 
313
    };
 
314
  SVN_VERSION_DEFINE(my_version);
 
315
 
 
316
  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
 
317
}
 
318
 
 
319
 
 
320
/* Implements `svn_ra__lock_retry_func_t'. */
 
321
static svn_error_t *
 
322
lock_retry_func(void *baton,
 
323
                const svn_string_t *reposlocktoken,
 
324
                apr_pool_t *pool)
 
325
{
 
326
  return svn_cmdline_printf(pool,
 
327
                            _("Failed to get lock on destination "
 
328
                              "repos, currently held by '%s'\n"),
 
329
                            reposlocktoken->data);
 
330
}
 
331
 
 
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
 
336
 */
 
337
static svn_error_t *
 
338
get_lock(const svn_string_t **lock_string_p,
 
339
         svn_ra_session_t *session,
 
340
         svn_boolean_t steal_lock,
 
341
         apr_pool_t *pool)
 
342
{
 
343
  svn_error_t *err;
 
344
  svn_boolean_t be_atomic;
 
345
  const svn_string_t *stolen_lock;
 
346
 
 
347
  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
 
348
                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
 
349
                                pool));
 
350
  if (! be_atomic)
 
351
    {
 
352
      /* Pre-1.7 server.  Can't lock without a race condition.
 
353
         See issue #3546.
 
354
       */
 
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 "
 
359
                "locking program"));
 
360
      svn_handle_warning2(stderr, err, "svnsync: ");
 
361
      svn_error_clear(err);
 
362
    }
 
363
 
 
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)
 
369
    {
 
370
      return svn_cmdline_printf(pool,
 
371
                                _("Stole lock previously held by '%s'\n"),
 
372
                                stolen_lock->data);
 
373
    }
 
374
  return err;
 
375
}
 
376
 
 
377
 
 
378
/* Baton for the various subcommands to share. */
 
379
typedef struct subcommand_baton_t {
 
380
  /* common to all subcommands */
 
381
  apr_hash_t *config;
 
382
  svn_ra_callbacks2_t source_callbacks;
 
383
  svn_ra_callbacks2_t sync_callbacks;
 
384
  svn_boolean_t quiet;
 
385
  svn_boolean_t allow_non_empty;
 
386
  const char *to_url;
 
387
 
 
388
  /* initialize, synchronize, and copy-revprops only */
 
389
  const char *source_prop_encoding;
 
390
 
 
391
  /* initialize only */
 
392
  const char *from_url;
 
393
 
 
394
  /* synchronize only */
 
395
  svn_revnum_t committed_rev;
 
396
 
 
397
  /* copy-revprops only */
 
398
  svn_revnum_t start_rev;
 
399
  svn_revnum_t end_rev;
 
400
 
 
401
} subcommand_baton_t;
 
402
 
 
403
typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
 
404
                                           subcommand_baton_t *baton,
 
405
                                           apr_pool_t *pool);
 
406
 
 
407
 
 
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.
 
411
 */
 
412
static svn_error_t *
 
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,
 
417
            apr_pool_t *pool)
 
418
{
 
419
  const svn_string_t *lock_string;
 
420
  svn_error_t *err;
 
421
 
 
422
  SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
 
423
 
 
424
  err = func(session, baton, pool);
 
425
  return svn_error_compose_create(err,
 
426
             svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
 
427
                                              lock_string, pool));
 
428
}
 
429
 
 
430
 
 
431
/* Callback function for the RA session's open_tmp_file()
 
432
 * requirements.
 
433
 */
 
434
static svn_error_t *
 
435
open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
 
436
{
 
437
  return svn_io_open_unique_file3(fp, NULL, NULL,
 
438
                                  svn_io_file_del_on_pool_cleanup,
 
439
                                  pool, pool);
 
440
}
 
441
 
 
442
 
 
443
/* Return SVN_NO_ERROR iff URL identifies the root directory of the
 
444
 * repository associated with RA session SESS.
 
445
 */
 
446
static svn_error_t *
 
447
check_if_session_is_at_repos_root(svn_ra_session_t *sess,
 
448
                                  const char *url,
 
449
                                  apr_pool_t *pool)
 
450
{
 
451
  const char *sess_root;
 
452
 
 
453
  SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
 
454
 
 
455
  if (strcmp(url, sess_root) == 0)
 
456
    return SVN_NO_ERROR;
 
457
  else
 
458
    return svn_error_createf
 
459
      (APR_EINVAL, NULL,
 
460
       _("Session is rooted at '%s' but the repos root is '%s'"),
 
461
       url, sess_root);
 
462
}
 
463
 
 
464
 
 
465
/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
 
466
 * revision REV of the repository associated with RA session SESSION.
 
467
 *
 
468
 * For REV zero, don't remove properties with the "svn:sync-" prefix.
 
469
 *
 
470
 * All allocations will be done in a subpool of POOL.
 
471
 */
 
472
static svn_error_t *
 
473
remove_props_not_in_source(svn_ra_session_t *session,
 
474
                           svn_revnum_t rev,
 
475
                           apr_hash_t *source_props,
 
476
                           apr_hash_t *target_props,
 
477
                           apr_pool_t *pool)
 
478
{
 
479
  apr_pool_t *subpool = svn_pool_create(pool);
 
480
  apr_hash_index_t *hi;
 
481
 
 
482
  for (hi = apr_hash_first(pool, target_props);
 
483
       hi;
 
484
       hi = apr_hash_next(hi))
 
485
    {
 
486
      const char *propname = svn__apr_hash_index_key(hi);
 
487
 
 
488
      svn_pool_clear(subpool);
 
489
 
 
490
      if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
 
491
                               sizeof(SVNSYNC_PROP_PREFIX) - 1))
 
492
        continue;
 
493
 
 
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,
 
497
                                        NULL, subpool));
 
498
    }
 
499
 
 
500
  svn_pool_destroy(subpool);
 
501
 
 
502
  return SVN_NO_ERROR;
 
503
}
 
504
 
 
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
 
508
 * not.
 
509
 */
 
510
typedef svn_boolean_t (*filter_func_t)(const char *key);
 
511
 
 
512
/* Make a new set of properties, by copying those properties in PROPS for which
 
513
 * the filter FILTER returns FALSE.
 
514
 *
 
515
 * The number of properties not copied will be stored in FILTERED_COUNT.
 
516
 *
 
517
 * The returned set of properties is allocated from POOL.
 
518
 */
 
519
static apr_hash_t *
 
520
filter_props(int *filtered_count, apr_hash_t *props,
 
521
             filter_func_t filter,
 
522
             apr_pool_t *pool)
 
523
{
 
524
  apr_hash_index_t *hi;
 
525
  apr_hash_t *filtered = apr_hash_make(pool);
 
526
  *filtered_count = 0;
 
527
 
 
528
  for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
 
529
    {
 
530
      const char *propname = svn__apr_hash_index_key(hi);
 
531
      void *propval = svn__apr_hash_index_val(hi);
 
532
 
 
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))
 
537
        {
 
538
          svn_hash_sets(filtered, propname, propval);
 
539
        }
 
540
      else
 
541
        {
 
542
          *filtered_count += 1;
 
543
        }
 
544
    }
 
545
 
 
546
  return filtered;
 
547
}
 
548
 
 
549
 
 
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.
 
555
 *
 
556
 * All allocations will be done in a subpool of POOL.
 
557
 */
 
558
static svn_error_t *
 
559
write_revprops(int *filtered_count,
 
560
               svn_ra_session_t *session,
 
561
               svn_revnum_t rev,
 
562
               apr_hash_t *rev_props,
 
563
               apr_pool_t *pool)
 
564
{
 
565
  apr_pool_t *subpool = svn_pool_create(pool);
 
566
  apr_hash_index_t *hi;
 
567
 
 
568
  *filtered_count = 0;
 
569
 
 
570
  for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
 
571
    {
 
572
      const char *propname = svn__apr_hash_index_key(hi);
 
573
      const svn_string_t *propval = svn__apr_hash_index_val(hi);
 
574
 
 
575
      svn_pool_clear(subpool);
 
576
 
 
577
      if (strncmp(propname, SVNSYNC_PROP_PREFIX,
 
578
                  sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
 
579
        {
 
580
          SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
 
581
                                          propval, subpool));
 
582
        }
 
583
      else
 
584
        {
 
585
          *filtered_count += 1;
 
586
        }
 
587
    }
 
588
 
 
589
  svn_pool_destroy(subpool);
 
590
 
 
591
  return SVN_NO_ERROR;
 
592
}
 
593
 
 
594
 
 
595
static svn_error_t *
 
596
log_properties_copied(svn_boolean_t syncprops_found,
 
597
                      svn_revnum_t rev,
 
598
                      apr_pool_t *pool)
 
599
{
 
600
  if (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));
 
605
  else
 
606
    SVN_ERR(svn_cmdline_printf(pool,
 
607
                               _("Copied properties for revision %ld.\n"),
 
608
                               rev));
 
609
 
 
610
  return SVN_NO_ERROR;
 
611
}
 
612
 
 
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. */
 
616
static svn_error_t *
 
617
log_properties_normalized(int normalized_rev_props_count,
 
618
                          int normalized_node_props_count,
 
619
                          apr_pool_t *pool)
 
620
{
 
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"),
 
626
                               SVN_PROP_PREFIX,
 
627
                               normalized_rev_props_count,
 
628
                               normalized_node_props_count));
 
629
  return SVN_NO_ERROR;
 
630
}
 
631
 
 
632
 
 
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.
 
637
 *
 
638
 * If SYNC is TRUE, then properties on the destination revision that
 
639
 * do not exist on the source revision will be removed.
 
640
 *
 
641
 * If QUIET is FALSE, then log_properties_copied() is called to log that
 
642
 * properties were copied for revision REV.
 
643
 *
 
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.
 
647
 */
 
648
static svn_error_t *
 
649
copy_revprops(svn_ra_session_t *from_session,
 
650
              svn_ra_session_t *to_session,
 
651
              svn_revnum_t rev,
 
652
              svn_boolean_t sync,
 
653
              svn_boolean_t quiet,
 
654
              const char *source_prop_encoding,
 
655
              int *normalized_count,
 
656
              apr_pool_t *pool)
 
657
{
 
658
  apr_pool_t *subpool = svn_pool_create(pool);
 
659
  apr_hash_t *existing_props, *rev_props;
 
660
  int filtered_count = 0;
 
661
 
 
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'. */
 
664
  if (sync)
 
665
    SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
 
666
  else
 
667
    existing_props = NULL;
 
668
 
 
669
  /* Get the list of revision properties on REV of SOURCE. */
 
670
  SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
 
671
 
 
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));
 
676
 
 
677
  /* Copy all but the svn:svnsync properties. */
 
678
  SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
 
679
 
 
680
  /* Delete those properties that were in TARGET but not in SOURCE */
 
681
  if (sync)
 
682
    SVN_ERR(remove_props_not_in_source(to_session, rev,
 
683
                                       rev_props, existing_props, pool));
 
684
 
 
685
  if (! quiet)
 
686
    SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
 
687
 
 
688
  svn_pool_destroy(subpool);
 
689
 
 
690
  return SVN_NO_ERROR;
 
691
}
 
692
 
 
693
 
 
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,
 
701
                      const char *to_url,
 
702
                      const char *from_url,
 
703
                      svn_revnum_t start_rev,
 
704
                      svn_revnum_t end_rev,
 
705
                      apr_pool_t *pool)
 
706
{
 
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;
 
715
  b->to_url = to_url;
 
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;
 
720
  return b;
 
721
}
 
722
 
 
723
static svn_error_t *
 
724
open_target_session(svn_ra_session_t **to_session_p,
 
725
                    subcommand_baton_t *baton,
 
726
                    apr_pool_t *pool);
 
727
 
 
728
 
 
729
/*** `svnsync init' ***/
 
730
 
 
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.
 
734
 */
 
735
static svn_error_t *
 
736
do_initialize(svn_ra_session_t *to_session,
 
737
              subcommand_baton_t *baton,
 
738
              apr_pool_t *pool)
 
739
{
 
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;
 
745
 
 
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
 
752
      (APR_EINVAL, NULL,
 
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"));
 
756
 
 
757
  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
 
758
                          &from_url, pool));
 
759
  if (from_url && (! baton->allow_non_empty))
 
760
    return svn_error_createf
 
761
      (APR_EINVAL, NULL,
 
762
       _("Destination repository is already synchronizing from '%s'"),
 
763
       from_url->data);
 
764
 
 
765
  /* Now fill in our bookkeeping info in the dest repository. */
 
766
 
 
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));
 
771
 
 
772
  /* If we're doing a partial replay, we have to check first if the server
 
773
     supports this. */
 
774
  if (strcmp(root_url, baton->from_url) != 0)
 
775
    {
 
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,
 
780
                                               pool);
 
781
      if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
 
782
        return svn_error_trace(err);
 
783
 
 
784
      if (err || !server_supports_partial_replay)
 
785
        return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
 
786
                                NULL);
 
787
    }
 
788
 
 
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. */
 
791
  if (latest != 0)
 
792
    {
 
793
      SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
 
794
      if (from_latest < latest)
 
795
        return svn_error_create
 
796
          (APR_EINVAL, NULL,
 
797
           _("Destination repository has more revisions than source "
 
798
             "repository"));
 
799
    }
 
800
 
 
801
  SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
 
802
                                  svn_string_create(baton->from_url, pool),
 
803
                                  pool));
 
804
 
 
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));
 
808
 
 
809
  SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
 
810
                                  NULL, svn_string_createf(pool, "%ld", latest),
 
811
                                  pool));
 
812
 
 
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,
 
823
                        pool));
 
824
 
 
825
  SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
 
826
 
 
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. */
 
833
 
 
834
  return SVN_NO_ERROR;
 
835
}
 
836
 
 
837
 
 
838
/* SUBCOMMAND: init */
 
839
static svn_error_t *
 
840
initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
 
841
{
 
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;
 
847
 
 
848
  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
 
849
                                        apr_array_make(pool, 0,
 
850
                                                       sizeof(const char *)),
 
851
                                        pool));
 
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);
 
856
 
 
857
  to_url = APR_ARRAY_IDX(targets, 0, const char *);
 
858
  from_url = APR_ARRAY_IDX(targets, 1, const char *);
 
859
 
 
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);
 
866
 
 
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));
 
871
  else
 
872
    SVN_ERR(with_locked(to_session, do_initialize, baton,
 
873
                        opt_baton->steal_lock, pool));
 
874
 
 
875
  return SVN_NO_ERROR;
 
876
}
 
877
 
 
878
 
 
879
 
 
880
/*** `svnsync sync' ***/
 
881
 
 
882
/* Implements `svn_commit_callback2_t' interface. */
 
883
static svn_error_t *
 
884
commit_callback(const svn_commit_info_t *commit_info,
 
885
                void *baton,
 
886
                apr_pool_t *pool)
 
887
{
 
888
  subcommand_baton_t *sb = baton;
 
889
 
 
890
  if (! sb->quiet)
 
891
    {
 
892
      SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
 
893
                                 commit_info->revision));
 
894
    }
 
895
 
 
896
  sb->committed_rev = commit_info->revision;
 
897
 
 
898
  return SVN_NO_ERROR;
 
899
}
 
900
 
 
901
 
 
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.
 
909
 *
 
910
 * CALLBACKS is a vtable of RA callbacks to provide when creating
 
911
 * *FROM_SESSION.  CONFIG is a configuration hash.
 
912
 */
 
913
static svn_error_t *
 
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,
 
919
                    apr_hash_t *config,
 
920
                    void *baton,
 
921
                    apr_pool_t *pool)
 
922
{
 
923
  apr_hash_t *props;
 
924
  svn_string_t *from_url_str, *from_uuid_str;
 
925
 
 
926
  SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
 
927
 
 
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);
 
931
 
 
932
  if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
 
933
    return svn_error_create
 
934
      (APR_EINVAL, NULL,
 
935
       _("Destination repository has not been initialized"));
 
936
 
 
937
  /* ### TODO: Should we validate that FROM_URL_STR->data matches any
 
938
     provided FROM_URL here?  */
 
939
  if (! from_url)
 
940
    from_url = from_url_str->data;
 
941
 
 
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));
 
945
 
 
946
  return SVN_NO_ERROR;
 
947
}
 
948
 
 
949
/* Set *TARGET_SESSION_P to an RA session associated with the target
 
950
 * repository of the synchronization.
 
951
 */
 
952
static svn_error_t *
 
953
open_target_session(svn_ra_session_t **target_session_p,
 
954
                    subcommand_baton_t *baton,
 
955
                    apr_pool_t *pool)
 
956
{
 
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));
 
961
 
 
962
  *target_session_p = target_session;
 
963
  return SVN_NO_ERROR;
 
964
}
 
965
 
 
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;
 
977
  const char *to_root;
 
978
} replay_baton_t;
 
979
 
 
980
/* Return a replay baton allocated from POOL and populated with
 
981
   data from the provided parameters. */
 
982
static svn_error_t *
 
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)
 
987
{
 
988
  replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
 
989
  rb->from_session = from_session;
 
990
  rb->to_session = to_session;
 
991
  rb->sb = sb;
 
992
 
 
993
  SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
 
994
 
 
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));
 
998
#endif
 
999
 
 
1000
  *baton_p = rb;
 
1001
  return SVN_NO_ERROR;
 
1002
}
 
1003
 
 
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.
 
1007
 */
 
1008
static svn_boolean_t
 
1009
filter_exclude_date_author_sync(const char *key)
 
1010
{
 
1011
  if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
 
1012
    return TRUE;
 
1013
  else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
 
1014
    return TRUE;
 
1015
  else if (strncmp(key, SVNSYNC_PROP_PREFIX,
 
1016
                   sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
 
1017
    return TRUE;
 
1018
 
 
1019
  return FALSE;
 
1020
}
 
1021
 
 
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.
 
1025
 */
 
1026
static svn_boolean_t
 
1027
filter_include_date_author_sync(const char *key)
 
1028
{
 
1029
  return ! filter_exclude_date_author_sync(key);
 
1030
}
 
1031
 
 
1032
 
 
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.
 
1035
 */
 
1036
static svn_boolean_t
 
1037
filter_exclude_log(const char *key)
 
1038
{
 
1039
  if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
 
1040
    return TRUE;
 
1041
  else
 
1042
    return FALSE;
 
1043
}
 
1044
 
 
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.
 
1047
 */
 
1048
static svn_boolean_t
 
1049
filter_include_log(const char *key)
 
1050
{
 
1051
  return ! filter_exclude_log(key);
 
1052
}
 
1053
 
 
1054
 
 
1055
static svn_error_t *
 
1056
fetch_base_func(const char **filename,
 
1057
                void *baton,
 
1058
                const char *path,
 
1059
                svn_revnum_t base_revision,
 
1060
                apr_pool_t *result_pool,
 
1061
                apr_pool_t *scratch_pool)
 
1062
{
 
1063
  struct replay_baton_t *rb = baton;
 
1064
  svn_stream_t *fstream;
 
1065
  svn_error_t *err;
 
1066
 
 
1067
  if (svn_path_is_url(path))
 
1068
    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
 
1069
  else if (path[0] == '/')
 
1070
    path += 1;
 
1071
 
 
1072
  if (! SVN_IS_VALID_REVNUM(base_revision))
 
1073
    base_revision = rb->current_revision - 1;
 
1074
 
 
1075
  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
 
1076
                                 svn_io_file_del_on_pool_cleanup,
 
1077
                                 result_pool, scratch_pool));
 
1078
 
 
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)
 
1082
    {
 
1083
      svn_error_clear(err);
 
1084
      SVN_ERR(svn_stream_close(fstream));
 
1085
 
 
1086
      *filename = NULL;
 
1087
      return SVN_NO_ERROR;
 
1088
    }
 
1089
  else if (err)
 
1090
    return svn_error_trace(err);
 
1091
 
 
1092
  SVN_ERR(svn_stream_close(fstream));
 
1093
 
 
1094
  return SVN_NO_ERROR;
 
1095
}
 
1096
 
 
1097
static svn_error_t *
 
1098
fetch_props_func(apr_hash_t **props,
 
1099
                 void *baton,
 
1100
                 const char *path,
 
1101
                 svn_revnum_t base_revision,
 
1102
                 apr_pool_t *result_pool,
 
1103
                 apr_pool_t *scratch_pool)
 
1104
{
 
1105
  struct replay_baton_t *rb = baton;
 
1106
  svn_node_kind_t node_kind;
 
1107
 
 
1108
  if (svn_path_is_url(path))
 
1109
    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
 
1110
  else if (path[0] == '/')
 
1111
    path += 1;
 
1112
 
 
1113
  if (! SVN_IS_VALID_REVNUM(base_revision))
 
1114
    base_revision = rb->current_revision - 1;
 
1115
 
 
1116
  SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
 
1117
                            &node_kind, scratch_pool));
 
1118
 
 
1119
  if (node_kind == svn_node_file)
 
1120
    {
 
1121
      SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
 
1122
                              NULL, NULL, props, result_pool));
 
1123
    }
 
1124
  else if (node_kind == svn_node_dir)
 
1125
    {
 
1126
      apr_array_header_t *tmp_props;
 
1127
 
 
1128
      SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
 
1129
                              base_revision, 0 /* Dirent fields */,
 
1130
                              result_pool));
 
1131
      tmp_props = svn_prop_hash_to_array(*props, result_pool);
 
1132
      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
 
1133
                                   result_pool));
 
1134
      *props = svn_prop_array_to_hash(tmp_props, result_pool);
 
1135
    }
 
1136
  else
 
1137
    {
 
1138
      *props = apr_hash_make(result_pool);
 
1139
    }
 
1140
 
 
1141
  return SVN_NO_ERROR;
 
1142
}
 
1143
 
 
1144
static svn_error_t *
 
1145
fetch_kind_func(svn_node_kind_t *kind,
 
1146
                void *baton,
 
1147
                const char *path,
 
1148
                svn_revnum_t base_revision,
 
1149
                apr_pool_t *scratch_pool)
 
1150
{
 
1151
  struct replay_baton_t *rb = baton;
 
1152
 
 
1153
  if (svn_path_is_url(path))
 
1154
    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
 
1155
  else if (path[0] == '/')
 
1156
    path += 1;
 
1157
 
 
1158
  if (! SVN_IS_VALID_REVNUM(base_revision))
 
1159
    base_revision = rb->current_revision - 1;
 
1160
 
 
1161
  SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
 
1162
                            kind, scratch_pool));
 
1163
 
 
1164
  return SVN_NO_ERROR;
 
1165
}
 
1166
 
 
1167
 
 
1168
static svn_delta_shim_callbacks_t *
 
1169
get_shim_callbacks(replay_baton_t *rb,
 
1170
                   apr_pool_t *result_pool)
 
1171
{
 
1172
  svn_delta_shim_callbacks_t *callbacks =
 
1173
                            svn_delta_shim_callbacks_default(result_pool);
 
1174
 
 
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;
 
1179
 
 
1180
  return callbacks;
 
1181
}
 
1182
 
 
1183
 
 
1184
/* Callback function for svn_ra_replay_range, invoked when starting to parse
 
1185
 * a replay report.
 
1186
 */
 
1187
static svn_error_t *
 
1188
replay_rev_started(svn_revnum_t revision,
 
1189
                   void *replay_baton,
 
1190
                   const svn_delta_editor_t **editor,
 
1191
                   void **edit_baton,
 
1192
                   apr_hash_t *rev_props,
 
1193
                   apr_pool_t *pool)
 
1194
{
 
1195
  const svn_delta_editor_t *commit_editor;
 
1196
  const svn_delta_editor_t *cancel_editor;
 
1197
  const svn_delta_editor_t *sync_editor;
 
1198
  void *commit_baton;
 
1199
  void *cancel_baton;
 
1200
  void *sync_baton;
 
1201
  replay_baton_t *rb = replay_baton;
 
1202
  apr_hash_t *filtered;
 
1203
  int filtered_count;
 
1204
  int normalized_count;
 
1205
 
 
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.
 
1211
 
 
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
 
1214
     commit. */
 
1215
  SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
 
1216
                                  SVNSYNC_PROP_CURRENTLY_COPYING,
 
1217
                                  NULL,
 
1218
                                  svn_string_createf(pool, "%ld", revision),
 
1219
                                  pool));
 
1220
 
 
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),
 
1232
                          pool);
 
1233
 
 
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));
 
1242
 
 
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;
 
1249
 
 
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,
 
1253
                                    &commit_baton,
 
1254
                                    filtered,
 
1255
                                    commit_callback, rb->sb,
 
1256
                                    NULL, FALSE, pool));
 
1257
 
 
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));
 
1265
 
 
1266
  SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
 
1267
                                            sync_editor, sync_baton,
 
1268
                                            &cancel_editor,
 
1269
                                            &cancel_baton,
 
1270
                                            pool));
 
1271
  *editor = cancel_editor;
 
1272
  *edit_baton = cancel_baton;
 
1273
 
 
1274
  rb->current_revision = revision;
 
1275
  return SVN_NO_ERROR;
 
1276
}
 
1277
 
 
1278
/* Callback function for svn_ra_replay_range, invoked when finishing parsing
 
1279
 * a replay report.
 
1280
 */
 
1281
static svn_error_t *
 
1282
replay_rev_finished(svn_revnum_t revision,
 
1283
                    void *replay_baton,
 
1284
                    const svn_delta_editor_t *editor,
 
1285
                    void *edit_baton,
 
1286
                    apr_hash_t *rev_props,
 
1287
                    apr_pool_t *pool)
 
1288
{
 
1289
  apr_pool_t *subpool = svn_pool_create(pool);
 
1290
  replay_baton_t *rb = replay_baton;
 
1291
  apr_hash_t *filtered, *existing_props;
 
1292
  int filtered_count;
 
1293
  int normalized_count;
 
1294
 
 
1295
  SVN_ERR(editor->close_edit(edit_baton, pool));
 
1296
 
 
1297
  /* Sanity check that we actually committed the revision we meant to. */
 
1298
  if (rb->sb->committed_rev != revision)
 
1299
    return svn_error_createf
 
1300
             (APR_EINVAL, NULL,
 
1301
              _("Commit created r%ld but should have created r%ld"),
 
1302
              rb->sb->committed_rev, revision);
 
1303
 
 
1304
  SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
 
1305
                              subpool));
 
1306
 
 
1307
 
 
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),
 
1316
                          subpool);
 
1317
 
 
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;
 
1323
 
 
1324
  SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
 
1325
                         subpool));
 
1326
 
 
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));
 
1330
 
 
1331
  svn_pool_clear(subpool);
 
1332
 
 
1333
  /* Ok, we're done, bring the last-merged-rev property up to date. */
 
1334
  SVN_ERR(svn_ra_change_rev_prop2(
 
1335
           rb->to_session,
 
1336
           0,
 
1337
           SVNSYNC_PROP_LAST_MERGED_REV,
 
1338
           NULL,
 
1339
           svn_string_create(apr_psprintf(pool, "%ld", revision),
 
1340
                             subpool),
 
1341
           subpool));
 
1342
 
 
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));
 
1348
 
 
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));
 
1352
 
 
1353
  svn_pool_destroy(subpool);
 
1354
 
 
1355
  return SVN_NO_ERROR;
 
1356
}
 
1357
 
 
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.
 
1361
 */
 
1362
static svn_error_t *
 
1363
do_synchronize(svn_ra_session_t *to_session,
 
1364
               subcommand_baton_t *baton, apr_pool_t *pool)
 
1365
{
 
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;
 
1372
  replay_baton_t *rb;
 
1373
  int normalized_rev_props_count = 0;
 
1374
 
 
1375
  SVN_ERR(open_source_session(&from_session, &last_merged_rev,
 
1376
                              baton->from_url, to_session,
 
1377
                              &(baton->source_callbacks), baton->config,
 
1378
                              baton, pool));
 
1379
 
 
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.
 
1385
 
 
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.
 
1391
 
 
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.
 
1397
  */
 
1398
 
 
1399
  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
 
1400
                          &currently_copying, pool));
 
1401
 
 
1402
  SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
 
1403
 
 
1404
  last_merged = SVN_STR_TO_REV(last_merged_rev->data);
 
1405
 
 
1406
  if (currently_copying)
 
1407
    {
 
1408
      copying = SVN_STR_TO_REV(currently_copying->data);
 
1409
 
 
1410
      if ((copying < last_merged)
 
1411
          || (copying > (last_merged + 1))
 
1412
          || ((to_latest != last_merged) && (to_latest != copying)))
 
1413
        {
 
1414
          return svn_error_createf
 
1415
            (APR_EINVAL, NULL,
 
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);
 
1420
        }
 
1421
      else if (copying == to_latest)
 
1422
        {
 
1423
          if (copying > last_merged)
 
1424
            {
 
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);
 
1431
            }
 
1432
 
 
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. */
 
1438
 
 
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,
 
1444
                                          NULL, NULL, pool));
 
1445
        }
 
1446
      /* If copying > to_latest, then we just fall through to
 
1447
         attempting to copy the revision again. */
 
1448
    }
 
1449
  else
 
1450
    {
 
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 "
 
1456
                                   "using svnsync?"),
 
1457
                                 to_latest, last_merged);
 
1458
    }
 
1459
 
 
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));
 
1462
 
 
1463
  if (from_latest < last_merged)
 
1464
    return SVN_NO_ERROR;
 
1465
 
 
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));
 
1469
 
 
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,
 
1475
                                pool));
 
1476
 
 
1477
  start_revision = last_merged + 1;
 
1478
  end_revision = from_latest;
 
1479
 
 
1480
  SVN_ERR(check_cancel(NULL));
 
1481
 
 
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));
 
1485
 
 
1486
  SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
 
1487
                                      + normalized_rev_props_count,
 
1488
                                    rb->normalized_node_props_count,
 
1489
                                    pool));
 
1490
 
 
1491
 
 
1492
  return SVN_NO_ERROR;
 
1493
}
 
1494
 
 
1495
 
 
1496
/* SUBCOMMAND: sync */
 
1497
static svn_error_t *
 
1498
synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
 
1499
{
 
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;
 
1505
 
 
1506
  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
 
1507
                                        apr_array_make(pool, 0,
 
1508
                                                       sizeof(const char *)),
 
1509
                                        pool));
 
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);
 
1514
 
 
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);
 
1519
 
 
1520
  if (targets->nelts == 2)
 
1521
    {
 
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);
 
1526
    }
 
1527
  else
 
1528
    {
 
1529
      from_url = NULL; /* we'll read it from the destination repos */
 
1530
    }
 
1531
 
 
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));
 
1536
  else
 
1537
    SVN_ERR(with_locked(to_session, do_synchronize, baton,
 
1538
                        opt_baton->steal_lock, pool));
 
1539
 
 
1540
  return SVN_NO_ERROR;
 
1541
}
 
1542
 
 
1543
 
 
1544
 
 
1545
/*** `svnsync copy-revprops' ***/
 
1546
 
 
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.
 
1550
 */
 
1551
static svn_error_t *
 
1552
do_copy_revprops(svn_ra_session_t *to_session,
 
1553
                 subcommand_baton_t *baton, apr_pool_t *pool)
 
1554
{
 
1555
  svn_ra_session_t *from_session;
 
1556
  svn_string_t *last_merged_rev;
 
1557
  svn_revnum_t i;
 
1558
  svn_revnum_t step = 1;
 
1559
  int normalized_rev_props_count = 0;
 
1560
 
 
1561
  SVN_ERR(open_source_session(&from_session, &last_merged_rev,
 
1562
                              baton->from_url, to_session,
 
1563
                              &(baton->source_callbacks), baton->config,
 
1564
                              baton, pool));
 
1565
 
 
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);
 
1571
 
 
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
 
1575
      (APR_EINVAL, NULL,
 
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
 
1580
      (APR_EINVAL, NULL,
 
1581
       _("Cannot copy revprops for a revision (%ld) that has not "
 
1582
         "been synchronized yet"), baton->end_rev);
 
1583
 
 
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)
 
1587
    {
 
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,
 
1592
                            pool));
 
1593
      normalized_rev_props_count += normalized_count;
 
1594
    }
 
1595
 
 
1596
  /* Notify about normalized props, if any. */
 
1597
  SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
 
1598
 
 
1599
  return SVN_NO_ERROR;
 
1600
}
 
1601
 
 
1602
 
 
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.
 
1609
 
 
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
 
1612
   SVN_INVALID_REVNUM.
 
1613
 
 
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)
 
1621
{
 
1622
  svn_revnum_t start_rev, end_rev;
 
1623
 
 
1624
  /* Special case: neither revision is specified?  This is like
 
1625
     -r0:HEAD. */
 
1626
  if ((start_revision.kind == svn_opt_revision_unspecified) &&
 
1627
      (end_revision.kind == svn_opt_revision_unspecified))
 
1628
    {
 
1629
      *start_revnum = 0;
 
1630
      *end_revnum = SVN_INVALID_REVNUM;
 
1631
      return SVN_NO_ERROR;
 
1632
    }
 
1633
 
 
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)
 
1637
    {
 
1638
      start_rev = SVN_INVALID_REVNUM;
 
1639
    }
 
1640
  else
 
1641
    {
 
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)"),
 
1646
                                 start_rev);
 
1647
    }
 
1648
 
 
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)
 
1653
    {
 
1654
      end_rev = start_rev;
 
1655
    }
 
1656
  else if (end_revision.kind == svn_opt_revision_head)
 
1657
    {
 
1658
      end_rev = SVN_INVALID_REVNUM;
 
1659
    }
 
1660
  else
 
1661
    {
 
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)"),
 
1666
                                 end_rev);
 
1667
    }
 
1668
 
 
1669
  *start_revnum = start_rev;
 
1670
  *end_revnum = end_rev;
 
1671
  return SVN_NO_ERROR;
 
1672
}
 
1673
 
 
1674
 
 
1675
/* SUBCOMMAND: copy-revprops */
 
1676
static svn_error_t *
 
1677
copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
 
1678
{
 
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;
 
1687
 
 
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);
 
1693
 
 
1694
  /* If there are two args, the last one is either a revision range or
 
1695
     the source URL.  */
 
1696
  if (os->argc - os->ind == 2)
 
1697
    {
 
1698
      const char *arg_str = os->argv[os->argc - 1];
 
1699
      const char *utf_arg_str;
 
1700
 
 
1701
      SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
 
1702
 
 
1703
      if (! svn_path_is_url(utf_arg_str))
 
1704
        {
 
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.) */
 
1709
          os->argc--;
 
1710
 
 
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"));
 
1717
 
 
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"),
 
1724
                                     arg_str);
 
1725
 
 
1726
          SVN_ERR(resolve_revnums(&start_rev, &end_rev,
 
1727
                                  start_revision, end_revision));
 
1728
 
 
1729
          SVN_ERR(svn_opt__args_to_target_array(
 
1730
                      &targets, os,
 
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 *);
 
1735
          from_url = NULL;
 
1736
        }
 
1737
    }
 
1738
 
 
1739
  if (! to_url)
 
1740
    {
 
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));
 
1745
 
 
1746
      SVN_ERR(svn_opt__args_to_target_array(
 
1747
                  &targets, os,
 
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 *);
 
1756
      else
 
1757
        from_url = NULL;
 
1758
    }
 
1759
 
 
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);
 
1766
 
 
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));
 
1772
  else
 
1773
    SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
 
1774
                        opt_baton->steal_lock, pool));
 
1775
 
 
1776
  return SVN_NO_ERROR;
 
1777
}
 
1778
 
 
1779
 
 
1780
 
 
1781
/*** `svnsync info' ***/
 
1782
 
 
1783
 
 
1784
/* SUBCOMMAND: info */
 
1785
static svn_error_t *
 
1786
info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
 
1787
{
 
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;
 
1792
  const char *to_url;
 
1793
  apr_hash_t *props;
 
1794
  svn_string_t *from_url, *from_uuid, *last_merged_rev;
 
1795
 
 
1796
  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
 
1797
                                        apr_array_make(pool, 0,
 
1798
                                                       sizeof(const char *)),
 
1799
                                        pool));
 
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);
 
1804
 
 
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);
 
1810
 
 
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));
 
1814
 
 
1815
  SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
 
1816
 
 
1817
  from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
 
1818
 
 
1819
  if (! from_url)
 
1820
    return svn_error_createf
 
1821
      (SVN_ERR_BAD_URL, NULL,
 
1822
       _("Repository '%s' is not initialized for synchronization"), to_url);
 
1823
 
 
1824
  from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
 
1825
  last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
 
1826
 
 
1827
  /* Print the info. */
 
1828
  SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
 
1829
  if (from_uuid)
 
1830
    SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
 
1831
                               from_uuid->data));
 
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;
 
1836
}
 
1837
 
 
1838
 
 
1839
 
 
1840
/*** `svnsync help' ***/
 
1841
 
 
1842
 
 
1843
/* SUBCOMMAND: help */
 
1844
static svn_error_t *
 
1845
help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
 
1846
{
 
1847
  opt_baton_t *opt_baton = baton;
 
1848
 
 
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"
 
1853
      "\n"
 
1854
      "Available subcommands:\n");
 
1855
 
 
1856
  const char *ra_desc_start
 
1857
    = _("The following repository access (RA) modules are available:\n\n");
 
1858
 
 
1859
  svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
 
1860
                                                         pool);
 
1861
 
 
1862
  SVN_ERR(svn_ra_print_modules(version_footer, pool));
 
1863
 
 
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,
 
1870
                              NULL, pool));
 
1871
 
 
1872
  return SVN_NO_ERROR;
 
1873
}
 
1874
 
 
1875
 
 
1876
 
 
1877
/*** Main ***/
 
1878
 
 
1879
int
 
1880
main(int argc, const char *argv[])
 
1881
{
 
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;
 
1887
  apr_getopt_t *os;
 
1888
  apr_pool_t *pool;
 
1889
  svn_error_t *err;
 
1890
  int opt_id, i;
 
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;
 
1896
 
 
1897
  if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
 
1898
    {
 
1899
      return EXIT_FAILURE;
 
1900
    }
 
1901
 
 
1902
  err = check_lib_versions();
 
1903
  if (err)
 
1904
    return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
 
1905
 
 
1906
  /* Create our top-level pool.  Use a separate mutexless allocator,
 
1907
   * given this application is single threaded.
 
1908
   */
 
1909
  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
 
1910
 
 
1911
  err = svn_ra_initialize(pool);
 
1912
  if (err)
 
1913
    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
1914
 
 
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;
 
1919
 
 
1920
  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
 
1921
 
 
1922
  if (argc <= 1)
 
1923
    {
 
1924
      SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
1925
      svn_pool_destroy(pool);
 
1926
      return EXIT_FAILURE;
 
1927
    }
 
1928
 
 
1929
  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
 
1930
  if (err)
 
1931
    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
1932
 
 
1933
  os->interleave = 1;
 
1934
 
 
1935
  for (;;)
 
1936
    {
 
1937
      const char *opt_arg;
 
1938
      svn_error_t* opt_err = NULL;
 
1939
 
 
1940
      apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
 
1941
      if (APR_STATUS_IS_EOF(apr_err))
 
1942
        break;
 
1943
      else if (apr_err)
 
1944
        {
 
1945
          SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
1946
          svn_pool_destroy(pool);
 
1947
          return EXIT_FAILURE;
 
1948
        }
 
1949
 
 
1950
      APR_ARRAY_PUSH(received_opts, int) = opt_id;
 
1951
 
 
1952
      switch (opt_id)
 
1953
        {
 
1954
          case svnsync_opt_non_interactive:
 
1955
            opt_baton.non_interactive = TRUE;
 
1956
            break;
 
1957
 
 
1958
          case svnsync_opt_force_interactive:
 
1959
            force_interactive = TRUE;
 
1960
            break;
 
1961
 
 
1962
          case svnsync_opt_trust_server_cert:
 
1963
            opt_baton.trust_server_cert = TRUE;
 
1964
            break;
 
1965
 
 
1966
          case svnsync_opt_no_auth_cache:
 
1967
            opt_baton.no_auth_cache = TRUE;
 
1968
            break;
 
1969
 
 
1970
          case svnsync_opt_auth_username:
 
1971
            opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
 
1972
            break;
 
1973
 
 
1974
          case svnsync_opt_auth_password:
 
1975
            opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
 
1976
            break;
 
1977
 
 
1978
          case svnsync_opt_source_username:
 
1979
            opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
 
1980
            break;
 
1981
 
 
1982
          case svnsync_opt_source_password:
 
1983
            opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
 
1984
            break;
 
1985
 
 
1986
          case svnsync_opt_sync_username:
 
1987
            opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
 
1988
            break;
 
1989
 
 
1990
          case svnsync_opt_sync_password:
 
1991
            opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
 
1992
            break;
 
1993
 
 
1994
          case svnsync_opt_config_dir:
 
1995
            {
 
1996
              const char *path_utf8;
 
1997
              opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
 
1998
 
 
1999
              if (!opt_err)
 
2000
                opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
 
2001
            }
 
2002
            break;
 
2003
          case svnsync_opt_config_options:
 
2004
            if (!config_options)
 
2005
              config_options =
 
2006
                    apr_array_make(pool, 1,
 
2007
                                   sizeof(svn_cmdline__config_argument_t*));
 
2008
 
 
2009
            err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
 
2010
            if (!err)
 
2011
              err = svn_cmdline__parse_config_option(config_options,
 
2012
                                                     opt_arg, pool);
 
2013
            if (err)
 
2014
              return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
2015
            break;
 
2016
 
 
2017
          case svnsync_opt_source_prop_encoding:
 
2018
            opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
 
2019
                                              pool);
 
2020
            break;
 
2021
 
 
2022
          case svnsync_opt_disable_locking:
 
2023
            opt_baton.disable_locking = TRUE;
 
2024
            break;
 
2025
 
 
2026
          case svnsync_opt_steal_lock:
 
2027
            opt_baton.steal_lock = TRUE;
 
2028
            break;
 
2029
 
 
2030
          case svnsync_opt_version:
 
2031
            opt_baton.version = TRUE;
 
2032
            break;
 
2033
 
 
2034
          case svnsync_opt_allow_non_empty:
 
2035
            opt_baton.allow_non_empty = TRUE;
 
2036
            break;
 
2037
 
 
2038
          case 'q':
 
2039
            opt_baton.quiet = TRUE;
 
2040
            break;
 
2041
 
 
2042
          case 'r':
 
2043
            if (svn_opt_parse_revision(&opt_baton.start_rev,
 
2044
                                       &opt_baton.end_rev,
 
2045
                                       opt_arg, pool) != 0)
 
2046
              {
 
2047
                const char *utf8_opt_arg;
 
2048
                err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
 
2049
                if (! err)
 
2050
                  err = svn_error_createf(
 
2051
                            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
 
2052
                            _("Syntax error in revision argument '%s'"),
 
2053
                            utf8_opt_arg);
 
2054
                return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
2055
              }
 
2056
 
 
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)))
 
2063
              {
 
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: ");
 
2068
              }
 
2069
            break;
 
2070
 
 
2071
          case '?':
 
2072
          case 'h':
 
2073
            opt_baton.help = TRUE;
 
2074
            break;
 
2075
 
 
2076
          default:
 
2077
            {
 
2078
              SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
2079
              svn_pool_destroy(pool);
 
2080
              return EXIT_FAILURE;
 
2081
            }
 
2082
        }
 
2083
 
 
2084
      if(opt_err)
 
2085
        return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: ");
 
2086
    }
 
2087
 
 
2088
  if (opt_baton.help)
 
2089
    subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
 
2090
 
 
2091
  /* The --non-interactive and --force-interactive options are mutually
 
2092
   * exclusive. */
 
2093
  if (opt_baton.non_interactive && force_interactive)
 
2094
    {
 
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: ");
 
2099
    }
 
2100
  else
 
2101
    opt_baton.non_interactive = !svn_cmdline__be_interactive(
 
2102
                                  opt_baton.non_interactive,
 
2103
                                  force_interactive);
 
2104
 
 
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))
 
2111
    {
 
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: ");
 
2118
    }
 
2119
  if (username)
 
2120
    {
 
2121
      source_username = username;
 
2122
      sync_username = username;
 
2123
    }
 
2124
  if (password)
 
2125
    {
 
2126
      source_password = password;
 
2127
      sync_password = password;
 
2128
    }
 
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;
 
2133
 
 
2134
  /* Disallow mixing of --steal-lock and --disable-locking. */
 
2135
  if (opt_baton.steal_lock && opt_baton.disable_locking)
 
2136
    {
 
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: ");
 
2141
    }
 
2142
 
 
2143
  /* --trust-server-cert can only be used with --non-interactive */
 
2144
  if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
 
2145
    {
 
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: ");
 
2150
    }
 
2151
 
 
2152
  err = svn_config_ensure(opt_baton.config_dir, pool);
 
2153
  if (err)
 
2154
    return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
 
2155
 
 
2156
  if (subcommand == NULL)
 
2157
    {
 
2158
      if (os->ind >= os->argc)
 
2159
        {
 
2160
          if (opt_baton.version)
 
2161
            {
 
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 */
 
2166
                   'q',  /* --quiet */
 
2167
                  } };
 
2168
 
 
2169
              subcommand = &pseudo_cmd;
 
2170
            }
 
2171
          else
 
2172
            {
 
2173
              SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
2174
              svn_pool_destroy(pool);
 
2175
              return EXIT_FAILURE;
 
2176
            }
 
2177
        }
 
2178
      else
 
2179
        {
 
2180
          const char *first_arg = os->argv[os->ind++];
 
2181
          subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
 
2182
                                                         first_arg);
 
2183
          if (subcommand == NULL)
 
2184
            {
 
2185
              SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
2186
              svn_pool_destroy(pool);
 
2187
              return EXIT_FAILURE;
 
2188
            }
 
2189
        }
 
2190
    }
 
2191
 
 
2192
  for (i = 0; i < received_opts->nelts; ++i)
 
2193
    {
 
2194
      opt_id = APR_ARRAY_IDX(received_opts, i, int);
 
2195
 
 
2196
      if (opt_id == 'h' || opt_id == '?')
 
2197
        continue;
 
2198
 
 
2199
      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
 
2200
        {
 
2201
          const char *optstr;
 
2202
          const apr_getopt_option_t *badopt =
 
2203
            svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
 
2204
                                          pool);
 
2205
          svn_opt_format_option(&optstr, badopt, FALSE, pool);
 
2206
          if (subcommand->name[0] == '-')
 
2207
            {
 
2208
              SVN_INT_ERR(help_cmd(NULL, NULL, pool));
 
2209
            }
 
2210
          else
 
2211
            {
 
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: ");
 
2218
            }
 
2219
        }
 
2220
    }
 
2221
 
 
2222
  err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
 
2223
  if (err)
 
2224
    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
2225
 
 
2226
  /* Update the options in the config */
 
2227
  if (config_options)
 
2228
    {
 
2229
      svn_error_clear(
 
2230
          svn_cmdline__apply_config_options(opt_baton.config, config_options,
 
2231
                                            "svnsync: ", "--config-option"));
 
2232
    }
 
2233
 
 
2234
  config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
 
2235
 
 
2236
  opt_baton.source_prop_encoding = source_prop_encoding;
 
2237
 
 
2238
  apr_signal(SIGINT, signal_handler);
 
2239
 
 
2240
#ifdef SIGBREAK
 
2241
  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
 
2242
  apr_signal(SIGBREAK, signal_handler);
 
2243
#endif
 
2244
 
 
2245
#ifdef SIGHUP
 
2246
  apr_signal(SIGHUP, signal_handler);
 
2247
#endif
 
2248
 
 
2249
#ifdef SIGTERM
 
2250
  apr_signal(SIGTERM, signal_handler);
 
2251
#endif
 
2252
 
 
2253
#ifdef SIGPIPE
 
2254
  /* Disable SIGPIPE generation for the platforms that have it. */
 
2255
  apr_signal(SIGPIPE, SIG_IGN);
 
2256
#endif
 
2257
 
 
2258
#ifdef SIGXFSZ
 
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,
 
2262
     which is uncool. */
 
2263
  apr_signal(SIGXFSZ, SIG_IGN);
 
2264
#endif
 
2265
 
 
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,
 
2273
                                      config,
 
2274
                                      check_cancel, NULL,
 
2275
                                      pool);
 
2276
  if (! err)
 
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,
 
2284
                                        config,
 
2285
                                        check_cancel, NULL,
 
2286
                                        pool);
 
2287
  if (! err)
 
2288
    err = (*subcommand->cmd_func)(os, &opt_baton, pool);
 
2289
  if (err)
 
2290
    {
 
2291
      /* For argument-related problems, suggest using the 'help'
 
2292
         subcommand. */
 
2293
      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
 
2294
          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
 
2295
        {
 
2296
          err = svn_error_quick_wrap(err,
 
2297
                                     _("Try 'svnsync help' for more info"));
 
2298
        }
 
2299
 
 
2300
      return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
 
2301
    }
 
2302
 
 
2303
  svn_pool_destroy(pool);
 
2304
 
 
2305
  return EXIT_SUCCESS;
 
2306
}