1251881Speter/*
2251881Speter * ====================================================================
3251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
4251881Speter *    or more contributor license agreements.  See the NOTICE file
5251881Speter *    distributed with this work for additional information
6251881Speter *    regarding copyright ownership.  The ASF licenses this file
7251881Speter *    to you under the Apache License, Version 2.0 (the
8251881Speter *    "License"); you may not use this file except in compliance
9251881Speter *    with the License.  You may obtain a copy of the License at
10251881Speter *
11251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
12251881Speter *
13251881Speter *    Unless required by applicable law or agreed to in writing,
14251881Speter *    software distributed under the License is distributed on an
15251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16251881Speter *    KIND, either express or implied.  See the License for the
17251881Speter *    specific language governing permissions and limitations
18251881Speter *    under the License.
19251881Speter * ====================================================================
20251881Speter */
21251881Speter
22251881Speter#include "svn_hash.h"
23251881Speter#include "svn_cmdline.h"
24251881Speter#include "svn_config.h"
25251881Speter#include "svn_pools.h"
26251881Speter#include "svn_delta.h"
27251881Speter#include "svn_dirent_uri.h"
28251881Speter#include "svn_path.h"
29251881Speter#include "svn_props.h"
30251881Speter#include "svn_auth.h"
31251881Speter#include "svn_opt.h"
32251881Speter#include "svn_ra.h"
33251881Speter#include "svn_utf.h"
34251881Speter#include "svn_subst.h"
35251881Speter#include "svn_string.h"
36251881Speter#include "svn_version.h"
37251881Speter
38251881Speter#include "private/svn_opt_private.h"
39251881Speter#include "private/svn_ra_private.h"
40251881Speter#include "private/svn_cmdline_private.h"
41251881Speter
42251881Speter#include "sync.h"
43251881Speter
44251881Speter#include "svn_private_config.h"
45251881Speter
46251881Speter#include <apr_signal.h>
47251881Speter#include <apr_uuid.h>
48251881Speter
49251881Speterstatic svn_opt_subcommand_t initialize_cmd,
50251881Speter                            synchronize_cmd,
51251881Speter                            copy_revprops_cmd,
52251881Speter                            info_cmd,
53251881Speter                            help_cmd;
54251881Speter
55251881Speterenum svnsync__opt {
56251881Speter  svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
57251881Speter  svnsync_opt_force_interactive,
58251881Speter  svnsync_opt_no_auth_cache,
59251881Speter  svnsync_opt_auth_username,
60251881Speter  svnsync_opt_auth_password,
61251881Speter  svnsync_opt_source_username,
62251881Speter  svnsync_opt_source_password,
63251881Speter  svnsync_opt_sync_username,
64251881Speter  svnsync_opt_sync_password,
65251881Speter  svnsync_opt_config_dir,
66251881Speter  svnsync_opt_config_options,
67251881Speter  svnsync_opt_source_prop_encoding,
68251881Speter  svnsync_opt_disable_locking,
69251881Speter  svnsync_opt_version,
70251881Speter  svnsync_opt_trust_server_cert,
71299742Sdim  svnsync_opt_trust_server_cert_failures_src,
72299742Sdim  svnsync_opt_trust_server_cert_failures_dst,
73251881Speter  svnsync_opt_allow_non_empty,
74251881Speter  svnsync_opt_steal_lock
75251881Speter};
76251881Speter
77251881Speter#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
78251881Speter                             svnsync_opt_force_interactive, \
79251881Speter                             svnsync_opt_no_auth_cache, \
80251881Speter                             svnsync_opt_auth_username, \
81251881Speter                             svnsync_opt_auth_password, \
82251881Speter                             svnsync_opt_trust_server_cert, \
83299742Sdim                             svnsync_opt_trust_server_cert_failures_src, \
84299742Sdim                             svnsync_opt_trust_server_cert_failures_dst, \
85251881Speter                             svnsync_opt_source_username, \
86251881Speter                             svnsync_opt_source_password, \
87251881Speter                             svnsync_opt_sync_username, \
88251881Speter                             svnsync_opt_sync_password, \
89251881Speter                             svnsync_opt_config_dir, \
90251881Speter                             svnsync_opt_config_options
91251881Speter
92251881Speterstatic const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
93251881Speter  {
94251881Speter    { "initialize", initialize_cmd, { "init" },
95251881Speter      N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
96251881Speter         "\n"
97251881Speter         "Initialize a destination repository for synchronization from\n"
98251881Speter         "another repository.\n"
99251881Speter         "\n"
100251881Speter         "If the source URL is not the root of a repository, only the\n"
101251881Speter         "specified part of the repository will be synchronized.\n"
102251881Speter         "\n"
103251881Speter         "The destination URL must point to the root of a repository which\n"
104251881Speter         "has been configured to allow revision property changes.  In\n"
105251881Speter         "the general case, the destination repository must contain no\n"
106251881Speter         "committed revisions.  Use --allow-non-empty to override this\n"
107251881Speter         "restriction, which will cause svnsync to assume that any revisions\n"
108251881Speter         "already present in the destination repository perfectly mirror\n"
109251881Speter         "their counterparts in the source repository.  (This is useful\n"
110251881Speter         "when initializing a copy of a repository as a mirror of that same\n"
111251881Speter         "repository, for example.)\n"
112251881Speter         "\n"
113251881Speter         "You should not commit to, or make revision property changes in,\n"
114251881Speter         "the destination repository by any method other than 'svnsync'.\n"
115251881Speter         "In other words, the destination repository should be a read-only\n"
116251881Speter         "mirror of the source repository.\n"),
117251881Speter      { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
118251881Speter        svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
119299742Sdim        svnsync_opt_steal_lock, 'M' } },
120251881Speter    { "synchronize", synchronize_cmd, { "sync" },
121251881Speter      N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
122251881Speter         "\n"
123251881Speter         "Transfer all pending revisions to the destination from the source\n"
124251881Speter         "with which it was initialized.\n"
125251881Speter         "\n"
126251881Speter         "If SOURCE_URL is provided, use that as the source repository URL,\n"
127251881Speter         "ignoring what is recorded in the destination repository as the\n"
128251881Speter         "source URL.  Specifying SOURCE_URL is recommended in particular\n"
129251881Speter         "if untrusted users/administrators may have write access to the\n"
130251881Speter         "DEST_URL repository.\n"),
131251881Speter      { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
132299742Sdim        svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } },
133251881Speter    { "copy-revprops", copy_revprops_cmd, { 0 },
134251881Speter      N_("usage:\n"
135251881Speter         "\n"
136251881Speter         "    1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
137251881Speter         "    2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
138251881Speter         "\n"
139251881Speter         "Copy the revision properties in a given range of revisions to the\n"
140251881Speter         "destination from the source with which it was initialized.  If the\n"
141251881Speter         "revision range is not specified, it defaults to all revisions in\n"
142251881Speter         "the DEST_URL repository.  Note also that the 'HEAD' revision is the\n"
143251881Speter         "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
144251881Speter         "\n"
145251881Speter         "If SOURCE_URL is provided, use that as the source repository URL,\n"
146251881Speter         "ignoring what is recorded in the destination repository as the\n"
147251881Speter         "source URL.  Specifying SOURCE_URL is recommended in particular\n"
148251881Speter         "if untrusted users/administrators may have write access to the\n"
149251881Speter         "DEST_URL repository.\n"
150251881Speter         "\n"
151251881Speter         "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
152251881Speter      { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
153299742Sdim        svnsync_opt_disable_locking, svnsync_opt_steal_lock, 'M' } },
154251881Speter    { "info", info_cmd, { 0 },
155251881Speter      N_("usage: svnsync info DEST_URL\n"
156251881Speter         "\n"
157251881Speter         "Print information about the synchronization destination repository\n"
158251881Speter         "located at DEST_URL.\n"),
159251881Speter      { SVNSYNC_OPTS_DEFAULT } },
160251881Speter    { "help", help_cmd, { "?", "h" },
161251881Speter      N_("usage: svnsync help [SUBCOMMAND...]\n"
162251881Speter         "\n"
163251881Speter         "Describe the usage of this program or its subcommands.\n"),
164251881Speter      { 0 } },
165251881Speter    { NULL, NULL, { 0 }, NULL, { 0 } }
166251881Speter  };
167251881Speter
168251881Speterstatic const apr_getopt_option_t svnsync_options[] =
169251881Speter  {
170251881Speter    {"quiet",          'q', 0,
171251881Speter                       N_("print as little as possible") },
172251881Speter    {"revision",       'r', 1,
173251881Speter                       N_("operate on revision ARG (or range ARG1:ARG2)\n"
174251881Speter                          "                             "
175251881Speter                          "A revision argument can be one of:\n"
176251881Speter                          "                             "
177251881Speter                          "    NUMBER       revision number\n"
178251881Speter                          "                             "
179251881Speter                          "    'HEAD'       latest in repository") },
180251881Speter    {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
181251881Speter                       N_("allow a non-empty destination repository") },
182251881Speter    {"non-interactive", svnsync_opt_non_interactive, 0,
183251881Speter                       N_("do no interactive prompting (default is to prompt\n"
184251881Speter                          "                             "
185251881Speter                          "only if standard input is a terminal device)")},
186251881Speter    {"force-interactive", svnsync_opt_force_interactive, 0,
187251881Speter                      N_("do interactive prompting even if standard input\n"
188251881Speter                         "                             "
189251881Speter                         "is not a terminal device")},
190251881Speter    {"no-auth-cache",  svnsync_opt_no_auth_cache, 0,
191251881Speter                       N_("do not cache authentication tokens") },
192251881Speter    {"username",       svnsync_opt_auth_username, 1,
193251881Speter                       N_("specify a username ARG (deprecated;\n"
194251881Speter                          "                             "
195251881Speter                          "see --source-username and --sync-username)") },
196251881Speter    {"password",       svnsync_opt_auth_password, 1,
197251881Speter                       N_("specify a password ARG (deprecated;\n"
198251881Speter                          "                             "
199251881Speter                          "see --source-password and --sync-password)") },
200251881Speter    {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
201299742Sdim                      N_("deprecated; same as\n"
202299742Sdim                         "                             "
203299742Sdim                         "--source-trust-server-cert-failures=unknown-ca\n"
204299742Sdim                         "                             "
205299742Sdim                         "--sync-trust-server-cert-failures=unknown-ca")},
206299742Sdim    {"source-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_src, 1,
207299742Sdim                      N_("with --non-interactive, accept SSL\n"
208299742Sdim                         "                             "
209299742Sdim                         "server certificates with failures.\n"
210299742Sdim                         "                             "
211299742Sdim                         "ARG is a comma-separated list of:\n"
212299742Sdim                         "                             "
213299742Sdim                         "- 'unknown-ca' (Unknown Authority)\n"
214299742Sdim                         "                             "
215299742Sdim                         "- 'cn-mismatch' (Hostname mismatch)\n"
216299742Sdim                         "                             "
217299742Sdim                         "- 'expired' (Expired certificate)\n"
218299742Sdim                         "                             "
219299742Sdim                         "- 'not-yet-valid' (Not yet valid certificate)\n"
220299742Sdim                         "                             "
221299742Sdim                         "- 'other' (all other not separately classified\n"
222299742Sdim                         "                             "
223299742Sdim                         "  certificate errors).\n"
224299742Sdim                         "                             "
225299742Sdim                         "Applied to the source URL.")},
226299742Sdim    {"sync-trust-server-cert-failures", svnsync_opt_trust_server_cert_failures_dst, 1,
227299742Sdim                       N_("Like\n"
228251881Speter                          "                             "
229299742Sdim                          "--source-trust-server-cert-failures,\n"
230251881Speter                          "                             "
231299742Sdim                          "but applied to the destination URL.")},
232251881Speter    {"source-username", svnsync_opt_source_username, 1,
233251881Speter                       N_("connect to source repository with username ARG") },
234251881Speter    {"source-password", svnsync_opt_source_password, 1,
235251881Speter                       N_("connect to source repository with password ARG") },
236251881Speter    {"sync-username",  svnsync_opt_sync_username, 1,
237251881Speter                       N_("connect to sync repository with username ARG") },
238251881Speter    {"sync-password",  svnsync_opt_sync_password, 1,
239251881Speter                       N_("connect to sync repository with password ARG") },
240251881Speter    {"config-dir",     svnsync_opt_config_dir, 1,
241251881Speter                       N_("read user configuration files from directory ARG")},
242251881Speter    {"config-option",  svnsync_opt_config_options, 1,
243251881Speter                       N_("set user configuration option in the format:\n"
244251881Speter                          "                             "
245251881Speter                          "    FILE:SECTION:OPTION=[VALUE]\n"
246251881Speter                          "                             "
247251881Speter                          "For example:\n"
248251881Speter                          "                             "
249251881Speter                          "    servers:global:http-library=serf")},
250251881Speter    {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
251251881Speter                       N_("convert translatable properties from encoding ARG\n"
252251881Speter                          "                             "
253251881Speter                          "to UTF-8. If not specified, then properties are\n"
254251881Speter                          "                             "
255251881Speter                          "presumed to be encoded in UTF-8.")},
256251881Speter    {"disable-locking",  svnsync_opt_disable_locking, 0,
257251881Speter                       N_("Disable built-in locking.  Use of this option can\n"
258251881Speter                          "                             "
259251881Speter                          "corrupt the mirror unless you ensure that no other\n"
260251881Speter                          "                             "
261251881Speter                          "instance of svnsync is running concurrently.")},
262251881Speter    {"steal-lock",     svnsync_opt_steal_lock, 0,
263251881Speter                       N_("Steal locks as necessary.  Use, with caution,\n"
264251881Speter                          "                             "
265251881Speter                          "if your mirror repository contains stale locks\n"
266251881Speter                          "                             "
267251881Speter                          "and is not being concurrently accessed by another\n"
268251881Speter                          "                             "
269251881Speter                          "svnsync instance.")},
270299742Sdim    {"memory-cache-size", 'M', 1,
271299742Sdim                       N_("size of the extra in-memory cache in MB used to\n"
272299742Sdim                          "                             "
273299742Sdim                          "minimize operations for local 'file' scheme.\n")},
274251881Speter    {"version",        svnsync_opt_version, 0,
275251881Speter                       N_("show program version information")},
276251881Speter    {"help",           'h', 0,
277251881Speter                       N_("show help on a subcommand")},
278251881Speter    {NULL,             '?', 0,
279251881Speter                       N_("show help on a subcommand")},
280251881Speter    { 0, 0, 0, 0 }
281251881Speter  };
282251881Speter
283251881Spetertypedef struct opt_baton_t {
284251881Speter  svn_boolean_t non_interactive;
285299742Sdim  struct {
286299742Sdim    svn_boolean_t trust_server_cert_unknown_ca;
287299742Sdim    svn_boolean_t trust_server_cert_cn_mismatch;
288299742Sdim    svn_boolean_t trust_server_cert_expired;
289299742Sdim    svn_boolean_t trust_server_cert_not_yet_valid;
290299742Sdim    svn_boolean_t trust_server_cert_other_failure;
291299742Sdim  } src_trust, dst_trust;
292251881Speter  svn_boolean_t no_auth_cache;
293251881Speter  svn_auth_baton_t *source_auth_baton;
294251881Speter  svn_auth_baton_t *sync_auth_baton;
295251881Speter  const char *source_username;
296251881Speter  const char *source_password;
297251881Speter  const char *sync_username;
298251881Speter  const char *sync_password;
299251881Speter  const char *config_dir;
300251881Speter  apr_hash_t *config;
301251881Speter  const char *source_prop_encoding;
302251881Speter  svn_boolean_t disable_locking;
303251881Speter  svn_boolean_t steal_lock;
304251881Speter  svn_boolean_t quiet;
305251881Speter  svn_boolean_t allow_non_empty;
306251881Speter  svn_boolean_t version;
307251881Speter  svn_boolean_t help;
308251881Speter  svn_opt_revision_t start_rev;
309251881Speter  svn_opt_revision_t end_rev;
310251881Speter} opt_baton_t;
311251881Speter
312251881Speter
313251881Speter
314251881Speter
315251881Speter/*** Helper functions ***/
316251881Speter
317251881Speter
318251881Speter/* Global record of whether the user has requested cancellation. */
319251881Speterstatic volatile sig_atomic_t cancelled = FALSE;
320251881Speter
321251881Speter
322251881Speter/* Callback function for apr_signal(). */
323251881Speterstatic void
324251881Spetersignal_handler(int signum)
325251881Speter{
326251881Speter  apr_signal(signum, SIG_IGN);
327251881Speter  cancelled = TRUE;
328251881Speter}
329251881Speter
330251881Speter
331251881Speter/* Cancellation callback function. */
332251881Speterstatic svn_error_t *
333251881Spetercheck_cancel(void *baton)
334251881Speter{
335251881Speter  if (cancelled)
336251881Speter    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
337251881Speter  else
338251881Speter    return SVN_NO_ERROR;
339251881Speter}
340251881Speter
341251881Speter
342251881Speter/* Check that the version of libraries in use match what we expect. */
343251881Speterstatic svn_error_t *
344251881Spetercheck_lib_versions(void)
345251881Speter{
346251881Speter  static const svn_version_checklist_t checklist[] =
347251881Speter    {
348251881Speter      { "svn_subr",  svn_subr_version },
349251881Speter      { "svn_delta", svn_delta_version },
350251881Speter      { "svn_ra",    svn_ra_version },
351251881Speter      { NULL, NULL }
352251881Speter    };
353251881Speter  SVN_VERSION_DEFINE(my_version);
354251881Speter
355262253Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
356251881Speter}
357251881Speter
358251881Speter
359251881Speter/* Implements `svn_ra__lock_retry_func_t'. */
360251881Speterstatic svn_error_t *
361251881Speterlock_retry_func(void *baton,
362251881Speter                const svn_string_t *reposlocktoken,
363251881Speter                apr_pool_t *pool)
364251881Speter{
365251881Speter  return svn_cmdline_printf(pool,
366251881Speter                            _("Failed to get lock on destination "
367251881Speter                              "repos, currently held by '%s'\n"),
368251881Speter                            reposlocktoken->data);
369251881Speter}
370251881Speter
371251881Speter/* Acquire a lock (of sorts) on the repository associated with the
372251881Speter * given RA SESSION. This lock is just a revprop change attempt in a
373251881Speter * time-delay loop. This function is duplicated by svnrdump in
374251881Speter * svnrdump/load_editor.c
375251881Speter */
376251881Speterstatic svn_error_t *
377251881Speterget_lock(const svn_string_t **lock_string_p,
378251881Speter         svn_ra_session_t *session,
379251881Speter         svn_boolean_t steal_lock,
380251881Speter         apr_pool_t *pool)
381251881Speter{
382251881Speter  svn_error_t *err;
383251881Speter  svn_boolean_t be_atomic;
384251881Speter  const svn_string_t *stolen_lock;
385251881Speter
386251881Speter  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
387251881Speter                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
388251881Speter                                pool));
389251881Speter  if (! be_atomic)
390251881Speter    {
391251881Speter      /* Pre-1.7 server.  Can't lock without a race condition.
392251881Speter         See issue #3546.
393251881Speter       */
394251881Speter      err = svn_error_create(
395251881Speter              SVN_ERR_UNSUPPORTED_FEATURE, NULL,
396251881Speter              _("Target server does not support atomic revision property "
397251881Speter                "edits; consider upgrading it to 1.7 or using an external "
398251881Speter                "locking program"));
399251881Speter      svn_handle_warning2(stderr, err, "svnsync: ");
400251881Speter      svn_error_clear(err);
401251881Speter    }
402251881Speter
403251881Speter  err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
404251881Speter                                     SVNSYNC_PROP_LOCK, steal_lock,
405251881Speter                                     10 /* retries */, lock_retry_func, NULL,
406251881Speter                                     check_cancel, NULL, pool);
407251881Speter  if (!err && stolen_lock)
408251881Speter    {
409251881Speter      return svn_cmdline_printf(pool,
410251881Speter                                _("Stole lock previously held by '%s'\n"),
411251881Speter                                stolen_lock->data);
412251881Speter    }
413251881Speter  return err;
414251881Speter}
415251881Speter
416251881Speter
417251881Speter/* Baton for the various subcommands to share. */
418251881Spetertypedef struct subcommand_baton_t {
419251881Speter  /* common to all subcommands */
420251881Speter  apr_hash_t *config;
421251881Speter  svn_ra_callbacks2_t source_callbacks;
422251881Speter  svn_ra_callbacks2_t sync_callbacks;
423251881Speter  svn_boolean_t quiet;
424251881Speter  svn_boolean_t allow_non_empty;
425251881Speter  const char *to_url;
426251881Speter
427251881Speter  /* initialize, synchronize, and copy-revprops only */
428251881Speter  const char *source_prop_encoding;
429251881Speter
430251881Speter  /* initialize only */
431251881Speter  const char *from_url;
432251881Speter
433251881Speter  /* synchronize only */
434251881Speter  svn_revnum_t committed_rev;
435251881Speter
436251881Speter  /* copy-revprops only */
437251881Speter  svn_revnum_t start_rev;
438251881Speter  svn_revnum_t end_rev;
439251881Speter
440251881Speter} subcommand_baton_t;
441251881Speter
442251881Spetertypedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
443251881Speter                                           subcommand_baton_t *baton,
444251881Speter                                           apr_pool_t *pool);
445251881Speter
446251881Speter
447251881Speter/* Lock the repository associated with RA SESSION, then execute the
448251881Speter * given FUNC/BATON pair while holding the lock.  Finally, drop the
449251881Speter * lock once it finishes.
450251881Speter */
451251881Speterstatic svn_error_t *
452251881Speterwith_locked(svn_ra_session_t *session,
453251881Speter            with_locked_func_t func,
454251881Speter            subcommand_baton_t *baton,
455251881Speter            svn_boolean_t steal_lock,
456251881Speter            apr_pool_t *pool)
457251881Speter{
458251881Speter  const svn_string_t *lock_string;
459251881Speter  svn_error_t *err;
460251881Speter
461251881Speter  SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
462251881Speter
463251881Speter  err = func(session, baton, pool);
464251881Speter  return svn_error_compose_create(err,
465251881Speter             svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
466251881Speter                                              lock_string, pool));
467251881Speter}
468251881Speter
469251881Speter
470251881Speter/* Callback function for the RA session's open_tmp_file()
471251881Speter * requirements.
472251881Speter */
473251881Speterstatic svn_error_t *
474251881Speteropen_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
475251881Speter{
476251881Speter  return svn_io_open_unique_file3(fp, NULL, NULL,
477251881Speter                                  svn_io_file_del_on_pool_cleanup,
478251881Speter                                  pool, pool);
479251881Speter}
480251881Speter
481251881Speter
482251881Speter/* Return SVN_NO_ERROR iff URL identifies the root directory of the
483251881Speter * repository associated with RA session SESS.
484251881Speter */
485251881Speterstatic svn_error_t *
486251881Spetercheck_if_session_is_at_repos_root(svn_ra_session_t *sess,
487251881Speter                                  const char *url,
488251881Speter                                  apr_pool_t *pool)
489251881Speter{
490251881Speter  const char *sess_root;
491251881Speter
492251881Speter  SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
493251881Speter
494251881Speter  if (strcmp(url, sess_root) == 0)
495251881Speter    return SVN_NO_ERROR;
496251881Speter  else
497251881Speter    return svn_error_createf
498251881Speter      (APR_EINVAL, NULL,
499251881Speter       _("Session is rooted at '%s' but the repos root is '%s'"),
500251881Speter       url, sess_root);
501251881Speter}
502251881Speter
503251881Speter
504251881Speter/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
505251881Speter * revision REV of the repository associated with RA session SESSION.
506251881Speter *
507251881Speter * For REV zero, don't remove properties with the "svn:sync-" prefix.
508251881Speter *
509251881Speter * All allocations will be done in a subpool of POOL.
510251881Speter */
511251881Speterstatic svn_error_t *
512251881Speterremove_props_not_in_source(svn_ra_session_t *session,
513251881Speter                           svn_revnum_t rev,
514251881Speter                           apr_hash_t *source_props,
515251881Speter                           apr_hash_t *target_props,
516251881Speter                           apr_pool_t *pool)
517251881Speter{
518251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
519251881Speter  apr_hash_index_t *hi;
520251881Speter
521251881Speter  for (hi = apr_hash_first(pool, target_props);
522251881Speter       hi;
523251881Speter       hi = apr_hash_next(hi))
524251881Speter    {
525299742Sdim      const char *propname = apr_hash_this_key(hi);
526251881Speter
527251881Speter      svn_pool_clear(subpool);
528251881Speter
529251881Speter      if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
530251881Speter                               sizeof(SVNSYNC_PROP_PREFIX) - 1))
531251881Speter        continue;
532251881Speter
533251881Speter      /* Delete property if the name can't be found in SOURCE_PROPS. */
534251881Speter      if (! svn_hash_gets(source_props, propname))
535251881Speter        SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
536251881Speter                                        NULL, subpool));
537251881Speter    }
538251881Speter
539251881Speter  svn_pool_destroy(subpool);
540251881Speter
541251881Speter  return SVN_NO_ERROR;
542251881Speter}
543251881Speter
544251881Speter/* Filter callback function.
545251881Speter * Takes a property name KEY, and is expected to return TRUE if the property
546251881Speter * should be filtered out (ie. not be copied to the target list), or FALSE if
547251881Speter * not.
548251881Speter */
549251881Spetertypedef svn_boolean_t (*filter_func_t)(const char *key);
550251881Speter
551251881Speter/* Make a new set of properties, by copying those properties in PROPS for which
552251881Speter * the filter FILTER returns FALSE.
553251881Speter *
554251881Speter * The number of properties not copied will be stored in FILTERED_COUNT.
555251881Speter *
556251881Speter * The returned set of properties is allocated from POOL.
557251881Speter */
558251881Speterstatic apr_hash_t *
559251881Speterfilter_props(int *filtered_count, apr_hash_t *props,
560251881Speter             filter_func_t filter,
561251881Speter             apr_pool_t *pool)
562251881Speter{
563251881Speter  apr_hash_index_t *hi;
564251881Speter  apr_hash_t *filtered = apr_hash_make(pool);
565251881Speter  *filtered_count = 0;
566251881Speter
567251881Speter  for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
568251881Speter    {
569299742Sdim      const char *propname = apr_hash_this_key(hi);
570299742Sdim      void *propval = apr_hash_this_val(hi);
571251881Speter
572251881Speter      /* Copy all properties:
573251881Speter          - not matching the exclude pattern if provided OR
574251881Speter          - matching the include pattern if provided */
575251881Speter      if (!filter || !filter(propname))
576251881Speter        {
577251881Speter          svn_hash_sets(filtered, propname, propval);
578251881Speter        }
579251881Speter      else
580251881Speter        {
581251881Speter          *filtered_count += 1;
582251881Speter        }
583251881Speter    }
584251881Speter
585251881Speter  return filtered;
586251881Speter}
587251881Speter
588251881Speter
589251881Speter/* Write the set of revision properties REV_PROPS to revision REV to the
590251881Speter * repository associated with RA session SESSION.
591251881Speter * Omit any properties whose names are in the svnsync property name space,
592251881Speter * and set *FILTERED_COUNT to the number of properties thus omitted.
593251881Speter * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
594251881Speter *
595251881Speter * All allocations will be done in a subpool of POOL.
596251881Speter */
597251881Speterstatic svn_error_t *
598251881Speterwrite_revprops(int *filtered_count,
599251881Speter               svn_ra_session_t *session,
600251881Speter               svn_revnum_t rev,
601251881Speter               apr_hash_t *rev_props,
602251881Speter               apr_pool_t *pool)
603251881Speter{
604251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
605251881Speter  apr_hash_index_t *hi;
606251881Speter
607251881Speter  *filtered_count = 0;
608251881Speter
609251881Speter  for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
610251881Speter    {
611299742Sdim      const char *propname = apr_hash_this_key(hi);
612299742Sdim      const svn_string_t *propval = apr_hash_this_val(hi);
613251881Speter
614251881Speter      svn_pool_clear(subpool);
615251881Speter
616251881Speter      if (strncmp(propname, SVNSYNC_PROP_PREFIX,
617251881Speter                  sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
618251881Speter        {
619251881Speter          SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
620251881Speter                                          propval, subpool));
621251881Speter        }
622251881Speter      else
623251881Speter        {
624251881Speter          *filtered_count += 1;
625251881Speter        }
626251881Speter    }
627251881Speter
628251881Speter  svn_pool_destroy(subpool);
629251881Speter
630251881Speter  return SVN_NO_ERROR;
631251881Speter}
632251881Speter
633251881Speter
634251881Speterstatic svn_error_t *
635251881Speterlog_properties_copied(svn_boolean_t syncprops_found,
636251881Speter                      svn_revnum_t rev,
637251881Speter                      apr_pool_t *pool)
638251881Speter{
639251881Speter  if (syncprops_found)
640251881Speter    SVN_ERR(svn_cmdline_printf(pool,
641251881Speter                               _("Copied properties for revision %ld "
642251881Speter                                 "(%s* properties skipped).\n"),
643251881Speter                               rev, SVNSYNC_PROP_PREFIX));
644251881Speter  else
645251881Speter    SVN_ERR(svn_cmdline_printf(pool,
646251881Speter                               _("Copied properties for revision %ld.\n"),
647251881Speter                               rev));
648251881Speter
649251881Speter  return SVN_NO_ERROR;
650251881Speter}
651251881Speter
652251881Speter/* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
653251881Speter * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
654251881Speter * endings, if either of those numbers is non-zero. */
655251881Speterstatic svn_error_t *
656251881Speterlog_properties_normalized(int normalized_rev_props_count,
657251881Speter                          int normalized_node_props_count,
658251881Speter                          apr_pool_t *pool)
659251881Speter{
660251881Speter  if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
661251881Speter    SVN_ERR(svn_cmdline_printf(pool,
662251881Speter                               _("NOTE: Normalized %s* properties "
663251881Speter                                 "to LF line endings (%d rev-props, "
664251881Speter                                 "%d node-props).\n"),
665251881Speter                               SVN_PROP_PREFIX,
666251881Speter                               normalized_rev_props_count,
667251881Speter                               normalized_node_props_count));
668251881Speter  return SVN_NO_ERROR;
669251881Speter}
670251881Speter
671251881Speter
672251881Speter/* Copy all the revision properties, except for those that have the
673251881Speter * "svn:sync-" prefix, from revision REV of the repository associated
674251881Speter * with RA session FROM_SESSION, to the repository associated with RA
675251881Speter * session TO_SESSION.
676251881Speter *
677251881Speter * If SYNC is TRUE, then properties on the destination revision that
678251881Speter * do not exist on the source revision will be removed.
679251881Speter *
680251881Speter * If QUIET is FALSE, then log_properties_copied() is called to log that
681251881Speter * properties were copied for revision REV.
682251881Speter *
683251881Speter * Make sure the values of svn:* revision properties use only LF (\n)
684251881Speter * line ending style, correcting their values as necessary. The number
685251881Speter * of properties that were normalized is returned in *NORMALIZED_COUNT.
686251881Speter */
687251881Speterstatic svn_error_t *
688251881Spetercopy_revprops(svn_ra_session_t *from_session,
689251881Speter              svn_ra_session_t *to_session,
690251881Speter              svn_revnum_t rev,
691251881Speter              svn_boolean_t sync,
692251881Speter              svn_boolean_t quiet,
693251881Speter              const char *source_prop_encoding,
694251881Speter              int *normalized_count,
695251881Speter              apr_pool_t *pool)
696251881Speter{
697251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
698251881Speter  apr_hash_t *existing_props, *rev_props;
699251881Speter  int filtered_count = 0;
700251881Speter
701251881Speter  /* Get the list of revision properties on REV of TARGET. We're only interested
702251881Speter     in the property names, but we'll get the values 'for free'. */
703251881Speter  if (sync)
704251881Speter    SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
705251881Speter  else
706251881Speter    existing_props = NULL;
707251881Speter
708251881Speter  /* Get the list of revision properties on REV of SOURCE. */
709251881Speter  SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
710251881Speter
711251881Speter  /* If necessary, normalize encoding and line ending style and return the count
712251881Speter     of EOL-normalized properties in int *NORMALIZED_COUNT. */
713251881Speter  SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
714251881Speter                                     source_prop_encoding, pool));
715251881Speter
716251881Speter  /* Copy all but the svn:svnsync properties. */
717251881Speter  SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
718251881Speter
719251881Speter  /* Delete those properties that were in TARGET but not in SOURCE */
720251881Speter  if (sync)
721251881Speter    SVN_ERR(remove_props_not_in_source(to_session, rev,
722251881Speter                                       rev_props, existing_props, pool));
723251881Speter
724251881Speter  if (! quiet)
725251881Speter    SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
726251881Speter
727251881Speter  svn_pool_destroy(subpool);
728251881Speter
729251881Speter  return SVN_NO_ERROR;
730251881Speter}
731251881Speter
732251881Speter
733251881Speter/* Return a subcommand baton allocated from POOL and populated with
734251881Speter   data from the provided parameters, which include the global
735251881Speter   OPT_BATON options structure and a handful of other options.  Not
736251881Speter   all parameters are used in all subcommands -- see
737251881Speter   subcommand_baton_t's definition for details. */
738251881Speterstatic subcommand_baton_t *
739251881Spetermake_subcommand_baton(opt_baton_t *opt_baton,
740251881Speter                      const char *to_url,
741251881Speter                      const char *from_url,
742251881Speter                      svn_revnum_t start_rev,
743251881Speter                      svn_revnum_t end_rev,
744251881Speter                      apr_pool_t *pool)
745251881Speter{
746251881Speter  subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
747251881Speter  b->config = opt_baton->config;
748251881Speter  b->source_callbacks.open_tmp_file = open_tmp_file;
749251881Speter  b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
750251881Speter  b->sync_callbacks.open_tmp_file = open_tmp_file;
751251881Speter  b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
752251881Speter  b->quiet = opt_baton->quiet;
753251881Speter  b->allow_non_empty = opt_baton->allow_non_empty;
754251881Speter  b->to_url = to_url;
755251881Speter  b->source_prop_encoding = opt_baton->source_prop_encoding;
756251881Speter  b->from_url = from_url;
757251881Speter  b->start_rev = start_rev;
758251881Speter  b->end_rev = end_rev;
759251881Speter  return b;
760251881Speter}
761251881Speter
762251881Speterstatic svn_error_t *
763251881Speteropen_target_session(svn_ra_session_t **to_session_p,
764251881Speter                    subcommand_baton_t *baton,
765251881Speter                    apr_pool_t *pool);
766251881Speter
767251881Speter
768251881Speter/*** `svnsync init' ***/
769251881Speter
770251881Speter/* Initialize the repository associated with RA session TO_SESSION,
771299742Sdim * using information found in BATON.
772299742Sdim *
773299742Sdim * Implements `with_locked_func_t' interface.  The caller has
774299742Sdim * acquired a lock on the repository if locking is needed.
775251881Speter */
776251881Speterstatic svn_error_t *
777251881Speterdo_initialize(svn_ra_session_t *to_session,
778251881Speter              subcommand_baton_t *baton,
779251881Speter              apr_pool_t *pool)
780251881Speter{
781251881Speter  svn_ra_session_t *from_session;
782251881Speter  svn_string_t *from_url;
783251881Speter  svn_revnum_t latest, from_latest;
784251881Speter  const char *uuid, *root_url;
785251881Speter  int normalized_rev_props_count;
786251881Speter
787251881Speter  /* First, sanity check to see that we're copying into a brand new
788251881Speter     repos.  If we aren't, and we aren't being asked to forcibly
789251881Speter     complete this initialization, that's a bad news.  */
790251881Speter  SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
791251881Speter  if ((latest != 0) && (! baton->allow_non_empty))
792251881Speter    return svn_error_create
793251881Speter      (APR_EINVAL, NULL,
794251881Speter       _("Destination repository already contains revision history; consider "
795251881Speter         "using --allow-non-empty if the repository's revisions are known "
796251881Speter         "to mirror their respective revisions in the source repository"));
797251881Speter
798251881Speter  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
799251881Speter                          &from_url, pool));
800251881Speter  if (from_url && (! baton->allow_non_empty))
801251881Speter    return svn_error_createf
802251881Speter      (APR_EINVAL, NULL,
803251881Speter       _("Destination repository is already synchronizing from '%s'"),
804251881Speter       from_url->data);
805251881Speter
806251881Speter  /* Now fill in our bookkeeping info in the dest repository. */
807251881Speter
808251881Speter  SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
809251881Speter                       &(baton->source_callbacks), baton,
810251881Speter                       baton->config, pool));
811251881Speter  SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
812251881Speter
813251881Speter  /* If we're doing a partial replay, we have to check first if the server
814251881Speter     supports this. */
815251881Speter  if (strcmp(root_url, baton->from_url) != 0)
816251881Speter    {
817251881Speter      svn_boolean_t server_supports_partial_replay;
818251881Speter      svn_error_t *err = svn_ra_has_capability(from_session,
819251881Speter                                               &server_supports_partial_replay,
820251881Speter                                               SVN_RA_CAPABILITY_PARTIAL_REPLAY,
821251881Speter                                               pool);
822251881Speter      if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
823251881Speter        return svn_error_trace(err);
824251881Speter
825251881Speter      if (err || !server_supports_partial_replay)
826251881Speter        return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
827251881Speter                                NULL);
828251881Speter    }
829251881Speter
830251881Speter  /* If we're initializing a non-empty destination, we'll make sure
831251881Speter     that it at least doesn't have more revisions than the source. */
832251881Speter  if (latest != 0)
833251881Speter    {
834251881Speter      SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
835251881Speter      if (from_latest < latest)
836251881Speter        return svn_error_create
837251881Speter          (APR_EINVAL, NULL,
838251881Speter           _("Destination repository has more revisions than source "
839251881Speter             "repository"));
840251881Speter    }
841251881Speter
842251881Speter  SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
843251881Speter                                  svn_string_create(baton->from_url, pool),
844251881Speter                                  pool));
845251881Speter
846251881Speter  SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
847251881Speter  SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
848251881Speter                                  svn_string_create(uuid, pool), pool));
849251881Speter
850251881Speter  SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
851251881Speter                                  NULL, svn_string_createf(pool, "%ld", latest),
852251881Speter                                  pool));
853251881Speter
854251881Speter  /* Copy all non-svnsync revprops from the LATEST rev in the source
855251881Speter     repository into the destination, notifying about normalized
856251881Speter     props, if any.  When LATEST is 0, this serves the practical
857251881Speter     purpose of initializing data that would otherwise be overlooked
858251881Speter     by the sync process (which is going to begin with r1).  When
859251881Speter     LATEST is not 0, this really serves merely aesthetic and
860251881Speter     informational purposes, keeping the output of this command
861251881Speter     consistent while allowing folks to see what the latest revision is.  */
862251881Speter  SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
863251881Speter                        baton->source_prop_encoding, &normalized_rev_props_count,
864251881Speter                        pool));
865251881Speter
866251881Speter  SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
867251881Speter
868251881Speter  /* TODO: It would be nice if we could set the dest repos UUID to be
869251881Speter     equal to the UUID of the source repos, at least optionally.  That
870251881Speter     way people could check out/log/diff using a local fast mirror,
871251881Speter     but switch --relocate to the actual final repository in order to
872251881Speter     make changes...  But at this time, the RA layer doesn't have a
873251881Speter     way to set a UUID. */
874251881Speter
875251881Speter  return SVN_NO_ERROR;
876251881Speter}
877251881Speter
878251881Speter
879251881Speter/* SUBCOMMAND: init */
880251881Speterstatic svn_error_t *
881251881Speterinitialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
882251881Speter{
883251881Speter  const char *to_url, *from_url;
884251881Speter  svn_ra_session_t *to_session;
885251881Speter  opt_baton_t *opt_baton = b;
886251881Speter  apr_array_header_t *targets;
887251881Speter  subcommand_baton_t *baton;
888251881Speter
889251881Speter  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
890251881Speter                                        apr_array_make(pool, 0,
891251881Speter                                                       sizeof(const char *)),
892251881Speter                                        pool));
893251881Speter  if (targets->nelts < 2)
894251881Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
895251881Speter  if (targets->nelts > 2)
896251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
897251881Speter
898251881Speter  to_url = APR_ARRAY_IDX(targets, 0, const char *);
899251881Speter  from_url = APR_ARRAY_IDX(targets, 1, const char *);
900251881Speter
901251881Speter  if (! svn_path_is_url(to_url))
902251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
903251881Speter                             _("Path '%s' is not a URL"), to_url);
904251881Speter  if (! svn_path_is_url(from_url))
905251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
906251881Speter                             _("Path '%s' is not a URL"), from_url);
907251881Speter
908251881Speter  baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
909251881Speter  SVN_ERR(open_target_session(&to_session, baton, pool));
910251881Speter  if (opt_baton->disable_locking)
911251881Speter    SVN_ERR(do_initialize(to_session, baton, pool));
912251881Speter  else
913251881Speter    SVN_ERR(with_locked(to_session, do_initialize, baton,
914251881Speter                        opt_baton->steal_lock, pool));
915251881Speter
916251881Speter  return SVN_NO_ERROR;
917251881Speter}
918251881Speter
919251881Speter
920251881Speter
921251881Speter/*** `svnsync sync' ***/
922251881Speter
923251881Speter/* Implements `svn_commit_callback2_t' interface. */
924251881Speterstatic svn_error_t *
925251881Spetercommit_callback(const svn_commit_info_t *commit_info,
926251881Speter                void *baton,
927251881Speter                apr_pool_t *pool)
928251881Speter{
929251881Speter  subcommand_baton_t *sb = baton;
930251881Speter
931251881Speter  if (! sb->quiet)
932251881Speter    {
933251881Speter      SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
934251881Speter                                 commit_info->revision));
935251881Speter    }
936251881Speter
937251881Speter  sb->committed_rev = commit_info->revision;
938251881Speter
939251881Speter  return SVN_NO_ERROR;
940251881Speter}
941251881Speter
942251881Speter
943251881Speter/* Set *FROM_SESSION to an RA session associated with the source
944251881Speter * repository of the synchronization.  If FROM_URL is non-NULL, use it
945251881Speter * as the source repository URL; otherwise, determine the source
946251881Speter * repository URL by reading svn:sync- properties from the destination
947251881Speter * repository (associated with TO_SESSION).  Set LAST_MERGED_REV to
948251881Speter * the value of the property which records the most recently
949251881Speter * synchronized revision.
950251881Speter *
951251881Speter * CALLBACKS is a vtable of RA callbacks to provide when creating
952251881Speter * *FROM_SESSION.  CONFIG is a configuration hash.
953251881Speter */
954251881Speterstatic svn_error_t *
955251881Speteropen_source_session(svn_ra_session_t **from_session,
956251881Speter                    svn_string_t **last_merged_rev,
957251881Speter                    const char *from_url,
958251881Speter                    svn_ra_session_t *to_session,
959251881Speter                    svn_ra_callbacks2_t *callbacks,
960251881Speter                    apr_hash_t *config,
961251881Speter                    void *baton,
962251881Speter                    apr_pool_t *pool)
963251881Speter{
964251881Speter  apr_hash_t *props;
965251881Speter  svn_string_t *from_url_str, *from_uuid_str;
966251881Speter
967251881Speter  SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
968251881Speter
969251881Speter  from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
970251881Speter  from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
971251881Speter  *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
972251881Speter
973251881Speter  if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
974251881Speter    return svn_error_create
975251881Speter      (APR_EINVAL, NULL,
976251881Speter       _("Destination repository has not been initialized"));
977251881Speter
978251881Speter  /* ### TODO: Should we validate that FROM_URL_STR->data matches any
979251881Speter     provided FROM_URL here?  */
980251881Speter  if (! from_url)
981299742Sdim    SVN_ERR(svn_opt__arg_canonicalize_url(&from_url, from_url_str->data,
982299742Sdim                                          pool));
983251881Speter
984251881Speter  /* Open the session to copy the revision data. */
985251881Speter  SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
986251881Speter                       callbacks, baton, config, pool));
987251881Speter
988251881Speter  return SVN_NO_ERROR;
989251881Speter}
990251881Speter
991251881Speter/* Set *TARGET_SESSION_P to an RA session associated with the target
992251881Speter * repository of the synchronization.
993251881Speter */
994251881Speterstatic svn_error_t *
995251881Speteropen_target_session(svn_ra_session_t **target_session_p,
996251881Speter                    subcommand_baton_t *baton,
997251881Speter                    apr_pool_t *pool)
998251881Speter{
999251881Speter  svn_ra_session_t *target_session;
1000251881Speter  SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
1001251881Speter                       &(baton->sync_callbacks), baton, baton->config, pool));
1002251881Speter  SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
1003251881Speter
1004251881Speter  *target_session_p = target_session;
1005251881Speter  return SVN_NO_ERROR;
1006251881Speter}
1007251881Speter
1008251881Speter/* Replay baton, used during synchronization. */
1009251881Spetertypedef struct replay_baton_t {
1010251881Speter  svn_ra_session_t *from_session;
1011251881Speter  svn_ra_session_t *to_session;
1012251881Speter  svn_revnum_t current_revision;
1013251881Speter  subcommand_baton_t *sb;
1014251881Speter  svn_boolean_t has_commit_revprops_capability;
1015299742Sdim  svn_boolean_t has_atomic_revprops_capability;
1016251881Speter  int normalized_rev_props_count;
1017251881Speter  int normalized_node_props_count;
1018251881Speter  const char *to_root;
1019299742Sdim
1020299742Sdim#ifdef ENABLE_EV2_SHIMS
1021299742Sdim  /* Extra 'backdoor' session for fetching data *from* the target repo. */
1022299742Sdim  svn_ra_session_t *extra_to_session;
1023299742Sdim#endif
1024251881Speter} replay_baton_t;
1025251881Speter
1026251881Speter/* Return a replay baton allocated from POOL and populated with
1027251881Speter   data from the provided parameters. */
1028251881Speterstatic svn_error_t *
1029251881Spetermake_replay_baton(replay_baton_t **baton_p,
1030251881Speter                  svn_ra_session_t *from_session,
1031251881Speter                  svn_ra_session_t *to_session,
1032251881Speter                  subcommand_baton_t *sb, apr_pool_t *pool)
1033251881Speter{
1034251881Speter  replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
1035251881Speter  rb->from_session = from_session;
1036251881Speter  rb->to_session = to_session;
1037251881Speter  rb->sb = sb;
1038251881Speter
1039251881Speter  SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
1040251881Speter
1041251881Speter#ifdef ENABLE_EV2_SHIMS
1042251881Speter  /* Open up the extra baton.  Only needed for Ev2 shims. */
1043251881Speter  SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
1044251881Speter#endif
1045251881Speter
1046251881Speter  *baton_p = rb;
1047251881Speter  return SVN_NO_ERROR;
1048251881Speter}
1049251881Speter
1050251881Speter/* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
1051251881Speter * property. Implements filter_func_t. Use with filter_props() to filter out
1052251881Speter * svn:date and svn:author and svnsync properties.
1053251881Speter */
1054251881Speterstatic svn_boolean_t
1055251881Speterfilter_exclude_date_author_sync(const char *key)
1056251881Speter{
1057251881Speter  if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1058251881Speter    return TRUE;
1059251881Speter  else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1060251881Speter    return TRUE;
1061251881Speter  else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1062251881Speter                   sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1063251881Speter    return TRUE;
1064251881Speter
1065251881Speter  return FALSE;
1066251881Speter}
1067251881Speter
1068251881Speter/* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
1069251881Speter * property. Implements filter_func_t. Use with filter_props() to filter out
1070251881Speter * all properties except svn:date and svn:author and svnsync properties.
1071251881Speter */
1072251881Speterstatic svn_boolean_t
1073251881Speterfilter_include_date_author_sync(const char *key)
1074251881Speter{
1075251881Speter  return ! filter_exclude_date_author_sync(key);
1076251881Speter}
1077251881Speter
1078251881Speter
1079251881Speter/* Return TRUE iff KEY is the name of the svn:log property.
1080251881Speter * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
1081251881Speter */
1082251881Speterstatic svn_boolean_t
1083251881Speterfilter_exclude_log(const char *key)
1084251881Speter{
1085251881Speter  if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1086251881Speter    return TRUE;
1087251881Speter  else
1088251881Speter    return FALSE;
1089251881Speter}
1090251881Speter
1091251881Speter/* Return FALSE iff KEY is the name of the svn:log property.
1092251881Speter * Implements filter_func_t. Use with filter_props() to only include svn:log.
1093251881Speter */
1094251881Speterstatic svn_boolean_t
1095251881Speterfilter_include_log(const char *key)
1096251881Speter{
1097251881Speter  return ! filter_exclude_log(key);
1098251881Speter}
1099251881Speter
1100299742Sdim#ifdef ENABLE_EV2_SHIMS
1101251881Speterstatic svn_error_t *
1102251881Speterfetch_base_func(const char **filename,
1103251881Speter                void *baton,
1104251881Speter                const char *path,
1105251881Speter                svn_revnum_t base_revision,
1106251881Speter                apr_pool_t *result_pool,
1107251881Speter                apr_pool_t *scratch_pool)
1108251881Speter{
1109251881Speter  struct replay_baton_t *rb = baton;
1110251881Speter  svn_stream_t *fstream;
1111251881Speter  svn_error_t *err;
1112251881Speter
1113251881Speter  if (svn_path_is_url(path))
1114251881Speter    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1115251881Speter  else if (path[0] == '/')
1116251881Speter    path += 1;
1117251881Speter
1118251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1119251881Speter    base_revision = rb->current_revision - 1;
1120251881Speter
1121251881Speter  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1122251881Speter                                 svn_io_file_del_on_pool_cleanup,
1123251881Speter                                 result_pool, scratch_pool));
1124251881Speter
1125251881Speter  err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
1126251881Speter                        fstream, NULL, NULL, scratch_pool);
1127251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1128251881Speter    {
1129251881Speter      svn_error_clear(err);
1130251881Speter      SVN_ERR(svn_stream_close(fstream));
1131251881Speter
1132251881Speter      *filename = NULL;
1133251881Speter      return SVN_NO_ERROR;
1134251881Speter    }
1135251881Speter  else if (err)
1136251881Speter    return svn_error_trace(err);
1137251881Speter
1138251881Speter  SVN_ERR(svn_stream_close(fstream));
1139251881Speter
1140251881Speter  return SVN_NO_ERROR;
1141251881Speter}
1142251881Speter
1143251881Speterstatic svn_error_t *
1144251881Speterfetch_props_func(apr_hash_t **props,
1145251881Speter                 void *baton,
1146251881Speter                 const char *path,
1147251881Speter                 svn_revnum_t base_revision,
1148251881Speter                 apr_pool_t *result_pool,
1149251881Speter                 apr_pool_t *scratch_pool)
1150251881Speter{
1151251881Speter  struct replay_baton_t *rb = baton;
1152251881Speter  svn_node_kind_t node_kind;
1153251881Speter
1154251881Speter  if (svn_path_is_url(path))
1155251881Speter    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1156251881Speter  else if (path[0] == '/')
1157251881Speter    path += 1;
1158251881Speter
1159251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1160251881Speter    base_revision = rb->current_revision - 1;
1161251881Speter
1162251881Speter  SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1163251881Speter                            &node_kind, scratch_pool));
1164251881Speter
1165251881Speter  if (node_kind == svn_node_file)
1166251881Speter    {
1167251881Speter      SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
1168251881Speter                              NULL, NULL, props, result_pool));
1169251881Speter    }
1170251881Speter  else if (node_kind == svn_node_dir)
1171251881Speter    {
1172251881Speter      apr_array_header_t *tmp_props;
1173251881Speter
1174251881Speter      SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
1175251881Speter                              base_revision, 0 /* Dirent fields */,
1176251881Speter                              result_pool));
1177251881Speter      tmp_props = svn_prop_hash_to_array(*props, result_pool);
1178251881Speter      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1179251881Speter                                   result_pool));
1180251881Speter      *props = svn_prop_array_to_hash(tmp_props, result_pool);
1181251881Speter    }
1182251881Speter  else
1183251881Speter    {
1184251881Speter      *props = apr_hash_make(result_pool);
1185251881Speter    }
1186251881Speter
1187251881Speter  return SVN_NO_ERROR;
1188251881Speter}
1189251881Speter
1190251881Speterstatic svn_error_t *
1191251881Speterfetch_kind_func(svn_node_kind_t *kind,
1192251881Speter                void *baton,
1193251881Speter                const char *path,
1194251881Speter                svn_revnum_t base_revision,
1195251881Speter                apr_pool_t *scratch_pool)
1196251881Speter{
1197251881Speter  struct replay_baton_t *rb = baton;
1198251881Speter
1199251881Speter  if (svn_path_is_url(path))
1200251881Speter    path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
1201251881Speter  else if (path[0] == '/')
1202251881Speter    path += 1;
1203251881Speter
1204251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1205251881Speter    base_revision = rb->current_revision - 1;
1206251881Speter
1207251881Speter  SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
1208251881Speter                            kind, scratch_pool));
1209251881Speter
1210251881Speter  return SVN_NO_ERROR;
1211251881Speter}
1212251881Speter
1213251881Speter
1214251881Speterstatic svn_delta_shim_callbacks_t *
1215251881Speterget_shim_callbacks(replay_baton_t *rb,
1216251881Speter                   apr_pool_t *result_pool)
1217251881Speter{
1218251881Speter  svn_delta_shim_callbacks_t *callbacks =
1219251881Speter                            svn_delta_shim_callbacks_default(result_pool);
1220251881Speter
1221251881Speter  callbacks->fetch_props_func = fetch_props_func;
1222251881Speter  callbacks->fetch_kind_func = fetch_kind_func;
1223251881Speter  callbacks->fetch_base_func = fetch_base_func;
1224251881Speter  callbacks->fetch_baton = rb;
1225251881Speter
1226251881Speter  return callbacks;
1227251881Speter}
1228299742Sdim#endif
1229251881Speter
1230251881Speter
1231251881Speter/* Callback function for svn_ra_replay_range, invoked when starting to parse
1232251881Speter * a replay report.
1233251881Speter */
1234251881Speterstatic svn_error_t *
1235251881Speterreplay_rev_started(svn_revnum_t revision,
1236251881Speter                   void *replay_baton,
1237251881Speter                   const svn_delta_editor_t **editor,
1238251881Speter                   void **edit_baton,
1239251881Speter                   apr_hash_t *rev_props,
1240251881Speter                   apr_pool_t *pool)
1241251881Speter{
1242251881Speter  const svn_delta_editor_t *commit_editor;
1243251881Speter  const svn_delta_editor_t *cancel_editor;
1244251881Speter  const svn_delta_editor_t *sync_editor;
1245251881Speter  void *commit_baton;
1246251881Speter  void *cancel_baton;
1247251881Speter  void *sync_baton;
1248251881Speter  replay_baton_t *rb = replay_baton;
1249251881Speter  apr_hash_t *filtered;
1250251881Speter  int filtered_count;
1251251881Speter  int normalized_count;
1252251881Speter
1253251881Speter  /* We set this property so that if we error out for some reason
1254251881Speter     we can later determine where we were in the process of
1255251881Speter     merging a revision.  If we had committed the change, but we
1256251881Speter     hadn't finished copying the revprops we need to know that, so
1257251881Speter     we can go back and finish the job before we move on.
1258251881Speter
1259251881Speter     NOTE: We have to set this before we start the commit editor,
1260251881Speter     because ra_svn doesn't let you change rev props during a
1261251881Speter     commit. */
1262251881Speter  SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1263251881Speter                                  SVNSYNC_PROP_CURRENTLY_COPYING,
1264251881Speter                                  NULL,
1265251881Speter                                  svn_string_createf(pool, "%ld", revision),
1266251881Speter                                  pool));
1267251881Speter
1268251881Speter  /* The actual copy is just a replay hooked up to a commit.  Include
1269251881Speter     all the revision properties from the source repositories, except
1270251881Speter     'svn:author' and 'svn:date', those are not guaranteed to get
1271251881Speter     through the editor anyway.
1272251881Speter     If we're syncing to an non-commit-revprops capable server, filter
1273251881Speter     out all revprops except svn:log and add them later in
1274251881Speter     revplay_rev_finished. */
1275251881Speter  filtered = filter_props(&filtered_count, rev_props,
1276251881Speter                          (rb->has_commit_revprops_capability
1277251881Speter                            ? filter_exclude_date_author_sync
1278251881Speter                            : filter_include_log),
1279251881Speter                          pool);
1280251881Speter
1281251881Speter  /* svn_ra_get_commit_editor3 requires the log message to be
1282251881Speter     set. It's possible that we didn't receive 'svn:log' here, so we
1283251881Speter     have to set it to at least the empty string. If there's a svn:log
1284251881Speter     property on this revision, we will write the actual value in the
1285251881Speter     replay_rev_finished callback. */
1286251881Speter  if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
1287251881Speter    svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
1288251881Speter                  svn_string_create_empty(pool));
1289251881Speter
1290251881Speter  /* If necessary, normalize encoding and line ending style. Add the number
1291251881Speter     of properties that required EOL normalization to the overall count
1292251881Speter     in the replay baton. */
1293251881Speter  SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1294251881Speter                                     rb->sb->source_prop_encoding, pool));
1295251881Speter  rb->normalized_rev_props_count += normalized_count;
1296251881Speter
1297299742Sdim#ifdef ENABLE_EV2_SHIMS
1298251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
1299251881Speter                                get_shim_callbacks(rb, pool)));
1300299742Sdim#endif
1301251881Speter  SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1302251881Speter                                    &commit_baton,
1303251881Speter                                    filtered,
1304251881Speter                                    commit_callback, rb->sb,
1305251881Speter                                    NULL, FALSE, pool));
1306251881Speter
1307251881Speter  /* There's one catch though, the diff shows us props we can't send
1308251881Speter     over the RA interface, so we need an editor that's smart enough
1309251881Speter     to filter those out for us.  */
1310251881Speter  SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
1311251881Speter                                  rb->sb->to_url, rb->sb->source_prop_encoding,
1312251881Speter                                  rb->sb->quiet, &sync_editor, &sync_baton,
1313251881Speter                                  &(rb->normalized_node_props_count), pool));
1314251881Speter
1315251881Speter  SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1316251881Speter                                            sync_editor, sync_baton,
1317251881Speter                                            &cancel_editor,
1318251881Speter                                            &cancel_baton,
1319251881Speter                                            pool));
1320251881Speter  *editor = cancel_editor;
1321251881Speter  *edit_baton = cancel_baton;
1322251881Speter
1323251881Speter  rb->current_revision = revision;
1324251881Speter  return SVN_NO_ERROR;
1325251881Speter}
1326251881Speter
1327251881Speter/* Callback function for svn_ra_replay_range, invoked when finishing parsing
1328251881Speter * a replay report.
1329251881Speter */
1330251881Speterstatic svn_error_t *
1331251881Speterreplay_rev_finished(svn_revnum_t revision,
1332251881Speter                    void *replay_baton,
1333251881Speter                    const svn_delta_editor_t *editor,
1334251881Speter                    void *edit_baton,
1335251881Speter                    apr_hash_t *rev_props,
1336251881Speter                    apr_pool_t *pool)
1337251881Speter{
1338251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1339251881Speter  replay_baton_t *rb = replay_baton;
1340251881Speter  apr_hash_t *filtered, *existing_props;
1341251881Speter  int filtered_count;
1342251881Speter  int normalized_count;
1343299742Sdim  const svn_string_t *rev_str;
1344251881Speter
1345251881Speter  SVN_ERR(editor->close_edit(edit_baton, pool));
1346251881Speter
1347251881Speter  /* Sanity check that we actually committed the revision we meant to. */
1348251881Speter  if (rb->sb->committed_rev != revision)
1349251881Speter    return svn_error_createf
1350251881Speter             (APR_EINVAL, NULL,
1351262253Speter              _("Commit created r%ld but should have created r%ld"),
1352251881Speter              rb->sb->committed_rev, revision);
1353251881Speter
1354251881Speter  SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1355251881Speter                              subpool));
1356251881Speter
1357251881Speter
1358251881Speter  /* Ok, we're done with the data, now we just need to copy the remaining
1359251881Speter     'svn:date' and 'svn:author' revprops and we're all set.
1360251881Speter     If the server doesn't support revprops-in-a-commit, we still have to
1361251881Speter     set all revision properties except svn:log. */
1362251881Speter  filtered = filter_props(&filtered_count, rev_props,
1363251881Speter                          (rb->has_commit_revprops_capability
1364251881Speter                            ? filter_include_date_author_sync
1365251881Speter                            : filter_exclude_log),
1366251881Speter                          subpool);
1367251881Speter
1368251881Speter  /* If necessary, normalize encoding and line ending style, and add the number
1369251881Speter     of EOL-normalized properties to the overall count in the replay baton. */
1370251881Speter  SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
1371251881Speter                                     rb->sb->source_prop_encoding, pool));
1372251881Speter  rb->normalized_rev_props_count += normalized_count;
1373251881Speter
1374251881Speter  SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1375251881Speter                         subpool));
1376251881Speter
1377251881Speter  /* Remove all extra properties in TARGET. */
1378251881Speter  SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1379251881Speter                                     rev_props, existing_props, subpool));
1380251881Speter
1381251881Speter  svn_pool_clear(subpool);
1382251881Speter
1383299742Sdim  rev_str = svn_string_createf(subpool, "%ld", revision);
1384299742Sdim
1385251881Speter  /* Ok, we're done, bring the last-merged-rev property up to date. */
1386251881Speter  SVN_ERR(svn_ra_change_rev_prop2(
1387251881Speter           rb->to_session,
1388251881Speter           0,
1389251881Speter           SVNSYNC_PROP_LAST_MERGED_REV,
1390251881Speter           NULL,
1391299742Sdim           rev_str,
1392251881Speter           subpool));
1393251881Speter
1394251881Speter  /* And finally drop the currently copying prop, since we're done
1395251881Speter     with this revision. */
1396251881Speter  SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
1397251881Speter                                  SVNSYNC_PROP_CURRENTLY_COPYING,
1398299742Sdim                                  rb->has_atomic_revprops_capability
1399299742Sdim                                    ? &rev_str : NULL,
1400299742Sdim                                  NULL, subpool));
1401251881Speter
1402251881Speter  /* Notify the user that we copied revision properties. */
1403251881Speter  if (! rb->sb->quiet)
1404251881Speter    SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1405251881Speter
1406251881Speter  svn_pool_destroy(subpool);
1407251881Speter
1408251881Speter  return SVN_NO_ERROR;
1409251881Speter}
1410251881Speter
1411251881Speter/* Synchronize the repository associated with RA session TO_SESSION,
1412299742Sdim * using information found in BATON.
1413299742Sdim *
1414299742Sdim * Implements `with_locked_func_t' interface.  The caller has
1415299742Sdim * acquired a lock on the repository if locking is needed.
1416251881Speter */
1417251881Speterstatic svn_error_t *
1418251881Speterdo_synchronize(svn_ra_session_t *to_session,
1419251881Speter               subcommand_baton_t *baton, apr_pool_t *pool)
1420251881Speter{
1421251881Speter  svn_string_t *last_merged_rev;
1422251881Speter  svn_revnum_t from_latest;
1423251881Speter  svn_ra_session_t *from_session;
1424251881Speter  svn_string_t *currently_copying;
1425251881Speter  svn_revnum_t to_latest, copying, last_merged;
1426251881Speter  svn_revnum_t start_revision, end_revision;
1427251881Speter  replay_baton_t *rb;
1428251881Speter  int normalized_rev_props_count = 0;
1429251881Speter
1430251881Speter  SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1431251881Speter                              baton->from_url, to_session,
1432251881Speter                              &(baton->source_callbacks), baton->config,
1433251881Speter                              baton, pool));
1434251881Speter
1435251881Speter  /* Check to see if we have revprops that still need to be copied for
1436251881Speter     a prior revision we didn't finish copying.  But first, check for
1437251881Speter     state sanity.  Remember, mirroring is not an atomic action,
1438251881Speter     because revision properties are copied separately from the
1439251881Speter     revision's contents.
1440251881Speter
1441251881Speter     So, any time that currently-copying is not set, then
1442251881Speter     last-merged-rev should be the HEAD revision of the destination
1443251881Speter     repository.  That is, if we didn't fall over in the middle of a
1444251881Speter     previous synchronization, then our destination repository should
1445251881Speter     have exactly as many revisions in it as we've synchronized.
1446251881Speter
1447251881Speter     Alternately, if currently-copying *is* set, it must
1448251881Speter     be either last-merged-rev or last-merged-rev + 1, and the HEAD
1449251881Speter     revision must be equal to either last-merged-rev or
1450251881Speter     currently-copying. If this is not the case, somebody has meddled
1451251881Speter     with the destination without using svnsync.
1452251881Speter  */
1453251881Speter
1454251881Speter  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1455251881Speter                          &currently_copying, pool));
1456251881Speter
1457251881Speter  SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1458251881Speter
1459251881Speter  last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1460251881Speter
1461251881Speter  if (currently_copying)
1462251881Speter    {
1463251881Speter      copying = SVN_STR_TO_REV(currently_copying->data);
1464251881Speter
1465251881Speter      if ((copying < last_merged)
1466251881Speter          || (copying > (last_merged + 1))
1467251881Speter          || ((to_latest != last_merged) && (to_latest != copying)))
1468251881Speter        {
1469251881Speter          return svn_error_createf
1470251881Speter            (APR_EINVAL, NULL,
1471251881Speter             _("Revision being currently copied (%ld), last merged revision "
1472251881Speter               "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1473251881Speter               "committed to the destination without using svnsync?"),
1474251881Speter             copying, last_merged, to_latest);
1475251881Speter        }
1476251881Speter      else if (copying == to_latest)
1477251881Speter        {
1478251881Speter          if (copying > last_merged)
1479251881Speter            {
1480251881Speter              SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
1481251881Speter                                    baton->quiet, baton->source_prop_encoding,
1482251881Speter                                    &normalized_rev_props_count, pool));
1483251881Speter              last_merged = copying;
1484251881Speter              last_merged_rev = svn_string_create
1485251881Speter                (apr_psprintf(pool, "%ld", last_merged), pool);
1486251881Speter            }
1487251881Speter
1488251881Speter          /* Now update last merged rev and drop currently changing.
1489251881Speter             Note that the order here is significant, if we do them
1490251881Speter             in the wrong order there are race conditions where we
1491251881Speter             end up not being able to tell if there have been bogus
1492251881Speter             (i.e. non-svnsync) commits to the dest repository. */
1493251881Speter
1494251881Speter          SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1495251881Speter                                          SVNSYNC_PROP_LAST_MERGED_REV,
1496251881Speter                                          NULL, last_merged_rev, pool));
1497251881Speter          SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1498251881Speter                                          SVNSYNC_PROP_CURRENTLY_COPYING,
1499251881Speter                                          NULL, NULL, pool));
1500251881Speter        }
1501251881Speter      /* If copying > to_latest, then we just fall through to
1502251881Speter         attempting to copy the revision again. */
1503251881Speter    }
1504251881Speter  else
1505251881Speter    {
1506251881Speter      if (to_latest != last_merged)
1507251881Speter        return svn_error_createf(APR_EINVAL, NULL,
1508251881Speter                                 _("Destination HEAD (%ld) is not the last "
1509251881Speter                                   "merged revision (%ld); have you "
1510251881Speter                                   "committed to the destination without "
1511251881Speter                                   "using svnsync?"),
1512251881Speter                                 to_latest, last_merged);
1513251881Speter    }
1514251881Speter
1515251881Speter  /* Now check to see if there are any revisions to copy. */
1516251881Speter  SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1517251881Speter
1518251881Speter  if (from_latest < last_merged)
1519251881Speter    return SVN_NO_ERROR;
1520251881Speter
1521251881Speter  /* Ok, so there are new revisions, iterate over them copying them
1522251881Speter     into the destination repository. */
1523251881Speter  SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
1524251881Speter
1525251881Speter  /* For compatibility with older svnserve versions, check first if we
1526251881Speter     support adding revprops to the commit. */
1527251881Speter  SVN_ERR(svn_ra_has_capability(rb->to_session,
1528251881Speter                                &rb->has_commit_revprops_capability,
1529251881Speter                                SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1530251881Speter                                pool));
1531251881Speter
1532299742Sdim  SVN_ERR(svn_ra_has_capability(rb->to_session,
1533299742Sdim                                &rb->has_atomic_revprops_capability,
1534299742Sdim                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1535299742Sdim                                pool));
1536299742Sdim
1537251881Speter  start_revision = last_merged + 1;
1538251881Speter  end_revision = from_latest;
1539251881Speter
1540251881Speter  SVN_ERR(check_cancel(NULL));
1541251881Speter
1542251881Speter  SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1543251881Speter                              0, TRUE, replay_rev_started,
1544251881Speter                              replay_rev_finished, rb, pool));
1545251881Speter
1546251881Speter  SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
1547251881Speter                                      + normalized_rev_props_count,
1548251881Speter                                    rb->normalized_node_props_count,
1549251881Speter                                    pool));
1550251881Speter
1551251881Speter
1552251881Speter  return SVN_NO_ERROR;
1553251881Speter}
1554251881Speter
1555251881Speter
1556251881Speter/* SUBCOMMAND: sync */
1557251881Speterstatic svn_error_t *
1558251881Spetersynchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1559251881Speter{
1560251881Speter  svn_ra_session_t *to_session;
1561251881Speter  opt_baton_t *opt_baton = b;
1562251881Speter  apr_array_header_t *targets;
1563251881Speter  subcommand_baton_t *baton;
1564251881Speter  const char *to_url, *from_url;
1565251881Speter
1566251881Speter  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1567251881Speter                                        apr_array_make(pool, 0,
1568251881Speter                                                       sizeof(const char *)),
1569251881Speter                                        pool));
1570251881Speter  if (targets->nelts < 1)
1571251881Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1572251881Speter  if (targets->nelts > 2)
1573251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1574251881Speter
1575251881Speter  to_url = APR_ARRAY_IDX(targets, 0, const char *);
1576251881Speter  if (! svn_path_is_url(to_url))
1577251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1578251881Speter                             _("Path '%s' is not a URL"), to_url);
1579251881Speter
1580251881Speter  if (targets->nelts == 2)
1581251881Speter    {
1582251881Speter      from_url = APR_ARRAY_IDX(targets, 1, const char *);
1583251881Speter      if (! svn_path_is_url(from_url))
1584251881Speter        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1585251881Speter                                 _("Path '%s' is not a URL"), from_url);
1586251881Speter    }
1587251881Speter  else
1588251881Speter    {
1589251881Speter      from_url = NULL; /* we'll read it from the destination repos */
1590251881Speter    }
1591251881Speter
1592251881Speter  baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
1593251881Speter  SVN_ERR(open_target_session(&to_session, baton, pool));
1594251881Speter  if (opt_baton->disable_locking)
1595251881Speter    SVN_ERR(do_synchronize(to_session, baton, pool));
1596251881Speter  else
1597251881Speter    SVN_ERR(with_locked(to_session, do_synchronize, baton,
1598251881Speter                        opt_baton->steal_lock, pool));
1599251881Speter
1600251881Speter  return SVN_NO_ERROR;
1601251881Speter}
1602251881Speter
1603251881Speter
1604251881Speter
1605251881Speter/*** `svnsync copy-revprops' ***/
1606251881Speter
1607251881Speter/* Copy revision properties to the repository associated with RA
1608299742Sdim * session TO_SESSION, using information found in BATON.
1609299742Sdim *
1610299742Sdim * Implements `with_locked_func_t' interface.  The caller has
1611299742Sdim * acquired a lock on the repository if locking is needed.
1612251881Speter */
1613251881Speterstatic svn_error_t *
1614251881Speterdo_copy_revprops(svn_ra_session_t *to_session,
1615251881Speter                 subcommand_baton_t *baton, apr_pool_t *pool)
1616251881Speter{
1617251881Speter  svn_ra_session_t *from_session;
1618251881Speter  svn_string_t *last_merged_rev;
1619251881Speter  svn_revnum_t i;
1620251881Speter  svn_revnum_t step = 1;
1621251881Speter  int normalized_rev_props_count = 0;
1622251881Speter
1623251881Speter  SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1624251881Speter                              baton->from_url, to_session,
1625251881Speter                              &(baton->source_callbacks), baton->config,
1626251881Speter                              baton, pool));
1627251881Speter
1628251881Speter  /* An invalid revision means "last-synced" */
1629251881Speter  if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1630251881Speter    baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1631251881Speter  if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1632251881Speter    baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1633251881Speter
1634251881Speter  /* Make sure we have revisions within the valid range. */
1635251881Speter  if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1636251881Speter    return svn_error_createf
1637251881Speter      (APR_EINVAL, NULL,
1638251881Speter       _("Cannot copy revprops for a revision (%ld) that has not "
1639251881Speter         "been synchronized yet"), baton->start_rev);
1640251881Speter  if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1641251881Speter    return svn_error_createf
1642251881Speter      (APR_EINVAL, NULL,
1643251881Speter       _("Cannot copy revprops for a revision (%ld) that has not "
1644251881Speter         "been synchronized yet"), baton->end_rev);
1645251881Speter
1646251881Speter  /* Now, copy all the requested revisions, in the requested order. */
1647251881Speter  step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1648251881Speter  for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1649251881Speter    {
1650251881Speter      int normalized_count;
1651251881Speter      SVN_ERR(check_cancel(NULL));
1652251881Speter      SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
1653251881Speter                            baton->source_prop_encoding, &normalized_count,
1654251881Speter                            pool));
1655251881Speter      normalized_rev_props_count += normalized_count;
1656251881Speter    }
1657251881Speter
1658251881Speter  /* Notify about normalized props, if any. */
1659251881Speter  SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
1660251881Speter
1661251881Speter  return SVN_NO_ERROR;
1662251881Speter}
1663251881Speter
1664251881Speter
1665251881Speter/* Set *START_REVNUM to the revision number associated with
1666251881Speter   START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
1667251881Speter   represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
1668251881Speter   the revision number associated with END_REVISION or to
1669251881Speter   SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
1670251881Speter   END_REVNUM to the same value as START_REVNUM.
1671251881Speter
1672251881Speter   As a special case, if neither START_REVISION nor END_REVISION is
1673251881Speter   specified, set *START_REVNUM to 0 and set *END_REVNUM to
1674251881Speter   SVN_INVALID_REVNUM.
1675251881Speter
1676251881Speter   Freak out if either START_REVISION or END_REVISION represents an
1677251881Speter   explicit but invalid revision number. */
1678251881Speterstatic svn_error_t *
1679251881Speterresolve_revnums(svn_revnum_t *start_revnum,
1680251881Speter                svn_revnum_t *end_revnum,
1681251881Speter                svn_opt_revision_t start_revision,
1682251881Speter                svn_opt_revision_t end_revision)
1683251881Speter{
1684251881Speter  svn_revnum_t start_rev, end_rev;
1685251881Speter
1686251881Speter  /* Special case: neither revision is specified?  This is like
1687251881Speter     -r0:HEAD. */
1688251881Speter  if ((start_revision.kind == svn_opt_revision_unspecified) &&
1689251881Speter      (end_revision.kind == svn_opt_revision_unspecified))
1690251881Speter    {
1691251881Speter      *start_revnum = 0;
1692251881Speter      *end_revnum = SVN_INVALID_REVNUM;
1693251881Speter      return SVN_NO_ERROR;
1694251881Speter    }
1695251881Speter
1696251881Speter  /* Get the start revision, which must be either HEAD or a number
1697251881Speter     (which is required to be a valid one). */
1698251881Speter  if (start_revision.kind == svn_opt_revision_head)
1699251881Speter    {
1700251881Speter      start_rev = SVN_INVALID_REVNUM;
1701251881Speter    }
1702251881Speter  else
1703251881Speter    {
1704251881Speter      start_rev = start_revision.value.number;
1705251881Speter      if (! SVN_IS_VALID_REVNUM(start_rev))
1706251881Speter        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1707251881Speter                                 _("Invalid revision number (%ld)"),
1708251881Speter                                 start_rev);
1709251881Speter    }
1710251881Speter
1711251881Speter  /* Get the end revision, which must be unspecified (meaning,
1712251881Speter     "same as the start_rev"), HEAD, or a number (which is
1713251881Speter     required to be a valid one). */
1714251881Speter  if (end_revision.kind == svn_opt_revision_unspecified)
1715251881Speter    {
1716251881Speter      end_rev = start_rev;
1717251881Speter    }
1718251881Speter  else if (end_revision.kind == svn_opt_revision_head)
1719251881Speter    {
1720251881Speter      end_rev = SVN_INVALID_REVNUM;
1721251881Speter    }
1722251881Speter  else
1723251881Speter    {
1724251881Speter      end_rev = end_revision.value.number;
1725251881Speter      if (! SVN_IS_VALID_REVNUM(end_rev))
1726251881Speter        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1727251881Speter                                 _("Invalid revision number (%ld)"),
1728251881Speter                                 end_rev);
1729251881Speter    }
1730251881Speter
1731251881Speter  *start_revnum = start_rev;
1732251881Speter  *end_revnum = end_rev;
1733251881Speter  return SVN_NO_ERROR;
1734251881Speter}
1735251881Speter
1736251881Speter
1737251881Speter/* SUBCOMMAND: copy-revprops */
1738251881Speterstatic svn_error_t *
1739251881Spetercopy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1740251881Speter{
1741251881Speter  svn_ra_session_t *to_session;
1742251881Speter  opt_baton_t *opt_baton = b;
1743251881Speter  apr_array_header_t *targets;
1744251881Speter  subcommand_baton_t *baton;
1745251881Speter  const char *to_url = NULL;
1746251881Speter  const char *from_url = NULL;
1747251881Speter  svn_opt_revision_t start_revision, end_revision;
1748251881Speter  svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1749251881Speter
1750251881Speter  /* There should be either one or two arguments left to parse. */
1751251881Speter  if (os->argc - os->ind > 2)
1752251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1753251881Speter  if (os->argc - os->ind < 1)
1754251881Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1755251881Speter
1756251881Speter  /* If there are two args, the last one is either a revision range or
1757251881Speter     the source URL.  */
1758251881Speter  if (os->argc - os->ind == 2)
1759251881Speter    {
1760251881Speter      const char *arg_str = os->argv[os->argc - 1];
1761251881Speter      const char *utf_arg_str;
1762251881Speter
1763251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
1764251881Speter
1765251881Speter      if (! svn_path_is_url(utf_arg_str))
1766251881Speter        {
1767251881Speter          /* This is the old "... TO_URL REV[:REV2]" syntax.
1768251881Speter             Revisions come only from this argument.  (We effectively
1769251881Speter             pop that last argument from the end of the argument list
1770251881Speter             so svn_opt__args_to_target_array() can do its thang.) */
1771251881Speter          os->argc--;
1772251881Speter
1773251881Speter          if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
1774251881Speter              || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
1775251881Speter            return svn_error_create(
1776251881Speter                SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1777251881Speter                _("Cannot specify revisions via both command-line arguments "
1778251881Speter                  "and the --revision (-r) option"));
1779251881Speter
1780251881Speter          start_revision.kind = svn_opt_revision_unspecified;
1781251881Speter          end_revision.kind = svn_opt_revision_unspecified;
1782251881Speter          if (svn_opt_parse_revision(&start_revision, &end_revision,
1783251881Speter                                     arg_str, pool) != 0)
1784251881Speter            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1785251881Speter                                     _("Invalid revision range '%s' provided"),
1786251881Speter                                     arg_str);
1787251881Speter
1788251881Speter          SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1789251881Speter                                  start_revision, end_revision));
1790251881Speter
1791251881Speter          SVN_ERR(svn_opt__args_to_target_array(
1792251881Speter                      &targets, os,
1793251881Speter                      apr_array_make(pool, 1, sizeof(const char *)), pool));
1794251881Speter          if (targets->nelts != 1)
1795251881Speter            return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1796251881Speter          to_url = APR_ARRAY_IDX(targets, 0, const char *);
1797251881Speter          from_url = NULL;
1798251881Speter        }
1799251881Speter    }
1800251881Speter
1801251881Speter  if (! to_url)
1802251881Speter    {
1803251881Speter      /* This is the "... TO_URL SOURCE_URL" syntax.  Revisions
1804251881Speter         come only from the --revision parameter.  */
1805251881Speter      SVN_ERR(resolve_revnums(&start_rev, &end_rev,
1806251881Speter                              opt_baton->start_rev, opt_baton->end_rev));
1807251881Speter
1808251881Speter      SVN_ERR(svn_opt__args_to_target_array(
1809251881Speter                  &targets, os,
1810251881Speter                  apr_array_make(pool, 2, sizeof(const char *)), pool));
1811251881Speter      if (targets->nelts < 1)
1812251881Speter        return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1813251881Speter      if (targets->nelts > 2)
1814251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1815251881Speter      to_url = APR_ARRAY_IDX(targets, 0, const char *);
1816251881Speter      if (targets->nelts == 2)
1817251881Speter        from_url = APR_ARRAY_IDX(targets, 1, const char *);
1818251881Speter      else
1819251881Speter        from_url = NULL;
1820251881Speter    }
1821251881Speter
1822251881Speter  if (! svn_path_is_url(to_url))
1823251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1824251881Speter                             _("Path '%s' is not a URL"), to_url);
1825251881Speter  if (from_url && (! svn_path_is_url(from_url)))
1826251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1827251881Speter                             _("Path '%s' is not a URL"), from_url);
1828251881Speter
1829251881Speter  baton = make_subcommand_baton(opt_baton, to_url, from_url,
1830251881Speter                                start_rev, end_rev, pool);
1831251881Speter  SVN_ERR(open_target_session(&to_session, baton, pool));
1832251881Speter  if (opt_baton->disable_locking)
1833251881Speter    SVN_ERR(do_copy_revprops(to_session, baton, pool));
1834251881Speter  else
1835251881Speter    SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
1836251881Speter                        opt_baton->steal_lock, pool));
1837251881Speter
1838251881Speter  return SVN_NO_ERROR;
1839251881Speter}
1840251881Speter
1841251881Speter
1842251881Speter
1843251881Speter/*** `svnsync info' ***/
1844251881Speter
1845251881Speter
1846251881Speter/* SUBCOMMAND: info */
1847251881Speterstatic svn_error_t *
1848251881Speterinfo_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1849251881Speter{
1850251881Speter  svn_ra_session_t *to_session;
1851251881Speter  opt_baton_t *opt_baton = b;
1852251881Speter  apr_array_header_t *targets;
1853251881Speter  subcommand_baton_t *baton;
1854251881Speter  const char *to_url;
1855251881Speter  apr_hash_t *props;
1856251881Speter  svn_string_t *from_url, *from_uuid, *last_merged_rev;
1857251881Speter
1858251881Speter  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1859251881Speter                                        apr_array_make(pool, 0,
1860251881Speter                                                       sizeof(const char *)),
1861251881Speter                                        pool));
1862251881Speter  if (targets->nelts < 1)
1863251881Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1864251881Speter  if (targets->nelts > 1)
1865251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1866251881Speter
1867251881Speter  /* Get the mirror repository URL, and verify that it is URL-ish. */
1868251881Speter  to_url = APR_ARRAY_IDX(targets, 0, const char *);
1869251881Speter  if (! svn_path_is_url(to_url))
1870251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1871251881Speter                             _("Path '%s' is not a URL"), to_url);
1872251881Speter
1873251881Speter  /* Open an RA session to the mirror repository URL. */
1874251881Speter  baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1875251881Speter  SVN_ERR(open_target_session(&to_session, baton, pool));
1876251881Speter
1877251881Speter  SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
1878251881Speter
1879251881Speter  from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
1880251881Speter
1881251881Speter  if (! from_url)
1882251881Speter    return svn_error_createf
1883251881Speter      (SVN_ERR_BAD_URL, NULL,
1884251881Speter       _("Repository '%s' is not initialized for synchronization"), to_url);
1885251881Speter
1886251881Speter  from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
1887251881Speter  last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
1888251881Speter
1889251881Speter  /* Print the info. */
1890251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1891251881Speter  if (from_uuid)
1892251881Speter    SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1893251881Speter                               from_uuid->data));
1894251881Speter  if (last_merged_rev)
1895251881Speter    SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1896251881Speter                               last_merged_rev->data));
1897251881Speter  return SVN_NO_ERROR;
1898251881Speter}
1899251881Speter
1900251881Speter
1901251881Speter
1902251881Speter/*** `svnsync help' ***/
1903251881Speter
1904251881Speter
1905251881Speter/* SUBCOMMAND: help */
1906251881Speterstatic svn_error_t *
1907251881Speterhelp_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1908251881Speter{
1909251881Speter  opt_baton_t *opt_baton = baton;
1910251881Speter
1911251881Speter  const char *header =
1912251881Speter    _("general usage: svnsync SUBCOMMAND DEST_URL  [ARGS & OPTIONS ...]\n"
1913299742Sdim      "Subversion repository replication tool.\n"
1914251881Speter      "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1915251881Speter      "Type 'svnsync --version' to see the program version and RA modules.\n"
1916251881Speter      "\n"
1917251881Speter      "Available subcommands:\n");
1918251881Speter
1919251881Speter  const char *ra_desc_start
1920251881Speter    = _("The following repository access (RA) modules are available:\n\n");
1921251881Speter
1922251881Speter  svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1923251881Speter                                                         pool);
1924251881Speter
1925251881Speter  SVN_ERR(svn_ra_print_modules(version_footer, pool));
1926251881Speter
1927251881Speter  SVN_ERR(svn_opt_print_help4(os, "svnsync",
1928251881Speter                              opt_baton ? opt_baton->version : FALSE,
1929251881Speter                              opt_baton ? opt_baton->quiet : FALSE,
1930251881Speter                              /*###opt_state ? opt_state->verbose :*/ FALSE,
1931251881Speter                              version_footer->data, header,
1932251881Speter                              svnsync_cmd_table, svnsync_options, NULL,
1933251881Speter                              NULL, pool));
1934251881Speter
1935251881Speter  return SVN_NO_ERROR;
1936251881Speter}
1937251881Speter
1938251881Speter
1939251881Speter
1940251881Speter/*** Main ***/
1941251881Speter
1942299742Sdim/*
1943299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1944299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1945299742Sdim * return SVN_NO_ERROR.
1946299742Sdim */
1947299742Sdimstatic svn_error_t *
1948299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1949251881Speter{
1950251881Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
1951251881Speter  apr_array_header_t *received_opts;
1952251881Speter  opt_baton_t opt_baton;
1953251881Speter  svn_config_t *config;
1954251881Speter  apr_status_t apr_err;
1955251881Speter  apr_getopt_t *os;
1956251881Speter  svn_error_t *err;
1957251881Speter  int opt_id, i;
1958251881Speter  const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1959251881Speter  const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1960251881Speter  apr_array_header_t *config_options = NULL;
1961251881Speter  const char *source_prop_encoding = NULL;
1962251881Speter  svn_boolean_t force_interactive = FALSE;
1963251881Speter
1964299742Sdim  /* Check library versions */
1965299742Sdim  SVN_ERR(check_lib_versions());
1966251881Speter
1967299742Sdim  SVN_ERR(svn_ra_initialize(pool));
1968251881Speter
1969251881Speter  /* Initialize the option baton. */
1970251881Speter  memset(&opt_baton, 0, sizeof(opt_baton));
1971251881Speter  opt_baton.start_rev.kind = svn_opt_revision_unspecified;
1972251881Speter  opt_baton.end_rev.kind = svn_opt_revision_unspecified;
1973251881Speter
1974251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1975251881Speter
1976251881Speter  if (argc <= 1)
1977251881Speter    {
1978299742Sdim      SVN_ERR(help_cmd(NULL, NULL, pool));
1979299742Sdim      *exit_code = EXIT_FAILURE;
1980299742Sdim      return SVN_NO_ERROR;
1981251881Speter    }
1982251881Speter
1983299742Sdim  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1984251881Speter
1985251881Speter  os->interleave = 1;
1986251881Speter
1987251881Speter  for (;;)
1988251881Speter    {
1989251881Speter      const char *opt_arg;
1990251881Speter      svn_error_t* opt_err = NULL;
1991251881Speter
1992251881Speter      apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
1993251881Speter      if (APR_STATUS_IS_EOF(apr_err))
1994251881Speter        break;
1995251881Speter      else if (apr_err)
1996251881Speter        {
1997299742Sdim          SVN_ERR(help_cmd(NULL, NULL, pool));
1998299742Sdim          *exit_code = EXIT_FAILURE;
1999299742Sdim          return SVN_NO_ERROR;
2000251881Speter        }
2001251881Speter
2002251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
2003251881Speter
2004251881Speter      switch (opt_id)
2005251881Speter        {
2006251881Speter          case svnsync_opt_non_interactive:
2007251881Speter            opt_baton.non_interactive = TRUE;
2008251881Speter            break;
2009251881Speter
2010251881Speter          case svnsync_opt_force_interactive:
2011251881Speter            force_interactive = TRUE;
2012251881Speter            break;
2013251881Speter
2014299742Sdim          case svnsync_opt_trust_server_cert: /* backwards compat */
2015299742Sdim            opt_baton.src_trust.trust_server_cert_unknown_ca = TRUE;
2016299742Sdim            opt_baton.dst_trust.trust_server_cert_unknown_ca = TRUE;
2017251881Speter            break;
2018251881Speter
2019299742Sdim          case svnsync_opt_trust_server_cert_failures_src:
2020299742Sdim            SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2021299742Sdim            SVN_ERR(svn_cmdline__parse_trust_options(
2022299742Sdim                      &opt_baton.src_trust.trust_server_cert_unknown_ca,
2023299742Sdim                      &opt_baton.src_trust.trust_server_cert_cn_mismatch,
2024299742Sdim                      &opt_baton.src_trust.trust_server_cert_expired,
2025299742Sdim                      &opt_baton.src_trust.trust_server_cert_not_yet_valid,
2026299742Sdim                      &opt_baton.src_trust.trust_server_cert_other_failure,
2027299742Sdim                      opt_arg, pool));
2028299742Sdim            break;
2029299742Sdim
2030299742Sdim          case svnsync_opt_trust_server_cert_failures_dst:
2031299742Sdim            SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2032299742Sdim            SVN_ERR(svn_cmdline__parse_trust_options(
2033299742Sdim                      &opt_baton.dst_trust.trust_server_cert_unknown_ca,
2034299742Sdim                      &opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2035299742Sdim                      &opt_baton.dst_trust.trust_server_cert_expired,
2036299742Sdim                      &opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2037299742Sdim                      &opt_baton.dst_trust.trust_server_cert_other_failure,
2038299742Sdim                      opt_arg, pool));
2039299742Sdim            break;
2040299742Sdim
2041251881Speter          case svnsync_opt_no_auth_cache:
2042251881Speter            opt_baton.no_auth_cache = TRUE;
2043251881Speter            break;
2044251881Speter
2045251881Speter          case svnsync_opt_auth_username:
2046251881Speter            opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
2047251881Speter            break;
2048251881Speter
2049251881Speter          case svnsync_opt_auth_password:
2050251881Speter            opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
2051251881Speter            break;
2052251881Speter
2053251881Speter          case svnsync_opt_source_username:
2054251881Speter            opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
2055251881Speter            break;
2056251881Speter
2057251881Speter          case svnsync_opt_source_password:
2058251881Speter            opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
2059251881Speter            break;
2060251881Speter
2061251881Speter          case svnsync_opt_sync_username:
2062251881Speter            opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
2063251881Speter            break;
2064251881Speter
2065251881Speter          case svnsync_opt_sync_password:
2066251881Speter            opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
2067251881Speter            break;
2068251881Speter
2069251881Speter          case svnsync_opt_config_dir:
2070251881Speter            {
2071251881Speter              const char *path_utf8;
2072251881Speter              opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
2073251881Speter
2074251881Speter              if (!opt_err)
2075251881Speter                opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
2076251881Speter            }
2077251881Speter            break;
2078251881Speter          case svnsync_opt_config_options:
2079251881Speter            if (!config_options)
2080251881Speter              config_options =
2081251881Speter                    apr_array_make(pool, 1,
2082251881Speter                                   sizeof(svn_cmdline__config_argument_t*));
2083251881Speter
2084299742Sdim            SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2085299742Sdim            SVN_ERR(svn_cmdline__parse_config_option(config_options,
2086299742Sdim                                                     opt_arg, "svnsync: ",
2087299742Sdim                                                     pool));
2088251881Speter            break;
2089251881Speter
2090251881Speter          case svnsync_opt_source_prop_encoding:
2091251881Speter            opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
2092251881Speter                                              pool);
2093251881Speter            break;
2094251881Speter
2095251881Speter          case svnsync_opt_disable_locking:
2096251881Speter            opt_baton.disable_locking = TRUE;
2097251881Speter            break;
2098251881Speter
2099251881Speter          case svnsync_opt_steal_lock:
2100251881Speter            opt_baton.steal_lock = TRUE;
2101251881Speter            break;
2102251881Speter
2103251881Speter          case svnsync_opt_version:
2104251881Speter            opt_baton.version = TRUE;
2105251881Speter            break;
2106251881Speter
2107251881Speter          case svnsync_opt_allow_non_empty:
2108251881Speter            opt_baton.allow_non_empty = TRUE;
2109251881Speter            break;
2110251881Speter
2111251881Speter          case 'q':
2112251881Speter            opt_baton.quiet = TRUE;
2113251881Speter            break;
2114251881Speter
2115251881Speter          case 'r':
2116251881Speter            if (svn_opt_parse_revision(&opt_baton.start_rev,
2117251881Speter                                       &opt_baton.end_rev,
2118251881Speter                                       opt_arg, pool) != 0)
2119251881Speter              {
2120251881Speter                const char *utf8_opt_arg;
2121299742Sdim                SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2122299742Sdim                return svn_error_createf(
2123251881Speter                            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2124251881Speter                            _("Syntax error in revision argument '%s'"),
2125251881Speter                            utf8_opt_arg);
2126251881Speter              }
2127251881Speter
2128251881Speter            /* We only allow numbers and 'HEAD'. */
2129251881Speter            if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
2130251881Speter                 (opt_baton.start_rev.kind != svn_opt_revision_head))
2131251881Speter                || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
2132251881Speter                    (opt_baton.end_rev.kind != svn_opt_revision_head) &&
2133251881Speter                    (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
2134251881Speter              {
2135299742Sdim                return svn_error_createf(
2136251881Speter                          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2137251881Speter                          _("Invalid revision range '%s' provided"), opt_arg);
2138251881Speter              }
2139251881Speter            break;
2140251881Speter
2141299742Sdim          case 'M':
2142299742Sdim            if (!config_options)
2143299742Sdim              config_options =
2144299742Sdim                    apr_array_make(pool, 1,
2145299742Sdim                                   sizeof(svn_cmdline__config_argument_t*));
2146299742Sdim
2147299742Sdim            SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
2148299742Sdim            SVN_ERR(svn_cmdline__parse_config_option(
2149299742Sdim                      config_options,
2150299742Sdim                      apr_psprintf(pool,
2151299742Sdim                                   "config:miscellany:memory-cache-size=%s",
2152299742Sdim                                   opt_arg),
2153299742Sdim                      NULL /* won't be used */,
2154299742Sdim                      pool));
2155299742Sdim            break;
2156299742Sdim
2157251881Speter          case '?':
2158251881Speter          case 'h':
2159251881Speter            opt_baton.help = TRUE;
2160251881Speter            break;
2161251881Speter
2162251881Speter          default:
2163251881Speter            {
2164299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
2165299742Sdim              *exit_code = EXIT_FAILURE;
2166299742Sdim              return SVN_NO_ERROR;
2167251881Speter            }
2168251881Speter        }
2169251881Speter
2170299742Sdim      if (opt_err)
2171299742Sdim        return opt_err;
2172251881Speter    }
2173251881Speter
2174251881Speter  if (opt_baton.help)
2175251881Speter    subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2176251881Speter
2177251881Speter  /* The --non-interactive and --force-interactive options are mutually
2178251881Speter   * exclusive. */
2179251881Speter  if (opt_baton.non_interactive && force_interactive)
2180251881Speter    {
2181299742Sdim      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2182299742Sdim                              _("--non-interactive and --force-interactive "
2183299742Sdim                                "are mutually exclusive"));
2184251881Speter    }
2185251881Speter  else
2186251881Speter    opt_baton.non_interactive = !svn_cmdline__be_interactive(
2187251881Speter                                  opt_baton.non_interactive,
2188251881Speter                                  force_interactive);
2189251881Speter
2190251881Speter  /* Disallow the mixing --username/password with their --source- and
2191251881Speter     --sync- variants.  Treat "--username FOO" as "--source-username
2192251881Speter     FOO --sync-username FOO"; ditto for "--password FOO". */
2193251881Speter  if ((username || password)
2194251881Speter      && (source_username || sync_username
2195251881Speter          || source_password || sync_password))
2196251881Speter    {
2197299742Sdim      return svn_error_create
2198251881Speter        (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2199251881Speter         _("Cannot use --username or --password with any of "
2200251881Speter           "--source-username, --source-password, --sync-username, "
2201251881Speter           "or --sync-password.\n"));
2202251881Speter    }
2203251881Speter  if (username)
2204251881Speter    {
2205251881Speter      source_username = username;
2206251881Speter      sync_username = username;
2207251881Speter    }
2208251881Speter  if (password)
2209251881Speter    {
2210251881Speter      source_password = password;
2211251881Speter      sync_password = password;
2212251881Speter    }
2213251881Speter  opt_baton.source_username = source_username;
2214251881Speter  opt_baton.source_password = source_password;
2215251881Speter  opt_baton.sync_username = sync_username;
2216251881Speter  opt_baton.sync_password = sync_password;
2217251881Speter
2218251881Speter  /* Disallow mixing of --steal-lock and --disable-locking. */
2219251881Speter  if (opt_baton.steal_lock && opt_baton.disable_locking)
2220251881Speter    {
2221299742Sdim      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2222299742Sdim                              _("--disable-locking and --steal-lock are "
2223299742Sdim                                "mutually exclusive"));
2224251881Speter    }
2225251881Speter
2226299742Sdim  /* --trust-* can only be used with --non-interactive */
2227299742Sdim  if (!opt_baton.non_interactive)
2228251881Speter    {
2229299742Sdim      if (opt_baton.src_trust.trust_server_cert_unknown_ca
2230299742Sdim          || opt_baton.src_trust.trust_server_cert_cn_mismatch
2231299742Sdim          || opt_baton.src_trust.trust_server_cert_expired
2232299742Sdim          || opt_baton.src_trust.trust_server_cert_not_yet_valid
2233299742Sdim          || opt_baton.src_trust.trust_server_cert_other_failure
2234299742Sdim          || opt_baton.dst_trust.trust_server_cert_unknown_ca
2235299742Sdim          || opt_baton.dst_trust.trust_server_cert_cn_mismatch
2236299742Sdim          || opt_baton.dst_trust.trust_server_cert_expired
2237299742Sdim          || opt_baton.dst_trust.trust_server_cert_not_yet_valid
2238299742Sdim          || opt_baton.dst_trust.trust_server_cert_other_failure)
2239299742Sdim        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2240299742Sdim                                _("--source-trust-server-cert-failures "
2241299742Sdim                                  "and "
2242299742Sdim                                  "--sync-trust-server-cert-failures require "
2243299742Sdim                                  "--non-interactive"));
2244251881Speter    }
2245251881Speter
2246299742Sdim  SVN_ERR(svn_config_ensure(opt_baton.config_dir, pool));
2247251881Speter
2248251881Speter  if (subcommand == NULL)
2249251881Speter    {
2250251881Speter      if (os->ind >= os->argc)
2251251881Speter        {
2252251881Speter          if (opt_baton.version)
2253251881Speter            {
2254251881Speter              /* Use the "help" subcommand to handle "--version". */
2255251881Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
2256251881Speter                { "--version", help_cmd, {0}, "",
2257251881Speter                  {svnsync_opt_version,  /* must accept its own option */
2258251881Speter                   'q',  /* --quiet */
2259251881Speter                  } };
2260251881Speter
2261251881Speter              subcommand = &pseudo_cmd;
2262251881Speter            }
2263251881Speter          else
2264251881Speter            {
2265299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
2266299742Sdim              *exit_code = EXIT_FAILURE;
2267299742Sdim              return SVN_NO_ERROR;
2268251881Speter            }
2269251881Speter        }
2270251881Speter      else
2271251881Speter        {
2272251881Speter          const char *first_arg = os->argv[os->ind++];
2273251881Speter          subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2274251881Speter                                                         first_arg);
2275251881Speter          if (subcommand == NULL)
2276251881Speter            {
2277299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
2278299742Sdim              *exit_code = EXIT_FAILURE;
2279299742Sdim              return SVN_NO_ERROR;
2280251881Speter            }
2281251881Speter        }
2282251881Speter    }
2283251881Speter
2284251881Speter  for (i = 0; i < received_opts->nelts; ++i)
2285251881Speter    {
2286251881Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
2287251881Speter
2288251881Speter      if (opt_id == 'h' || opt_id == '?')
2289251881Speter        continue;
2290251881Speter
2291251881Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2292251881Speter        {
2293251881Speter          const char *optstr;
2294251881Speter          const apr_getopt_option_t *badopt =
2295251881Speter            svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2296251881Speter                                          pool);
2297251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
2298251881Speter          if (subcommand->name[0] == '-')
2299251881Speter            {
2300299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
2301251881Speter            }
2302251881Speter          else
2303251881Speter            {
2304299742Sdim              return svn_error_createf
2305251881Speter                (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2306251881Speter                 _("Subcommand '%s' doesn't accept option '%s'\n"
2307251881Speter                   "Type 'svnsync help %s' for usage.\n"),
2308251881Speter                 subcommand->name, optstr, subcommand->name);
2309251881Speter            }
2310251881Speter        }
2311251881Speter    }
2312251881Speter
2313299742Sdim  SVN_ERR(svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool));
2314251881Speter
2315251881Speter  /* Update the options in the config */
2316251881Speter  if (config_options)
2317251881Speter    {
2318251881Speter      svn_error_clear(
2319251881Speter          svn_cmdline__apply_config_options(opt_baton.config, config_options,
2320251881Speter                                            "svnsync: ", "--config-option"));
2321251881Speter    }
2322251881Speter
2323251881Speter  config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
2324251881Speter
2325251881Speter  opt_baton.source_prop_encoding = source_prop_encoding;
2326251881Speter
2327251881Speter  apr_signal(SIGINT, signal_handler);
2328251881Speter
2329251881Speter#ifdef SIGBREAK
2330251881Speter  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2331251881Speter  apr_signal(SIGBREAK, signal_handler);
2332251881Speter#endif
2333251881Speter
2334251881Speter#ifdef SIGHUP
2335251881Speter  apr_signal(SIGHUP, signal_handler);
2336251881Speter#endif
2337251881Speter
2338251881Speter#ifdef SIGTERM
2339251881Speter  apr_signal(SIGTERM, signal_handler);
2340251881Speter#endif
2341251881Speter
2342251881Speter#ifdef SIGPIPE
2343251881Speter  /* Disable SIGPIPE generation for the platforms that have it. */
2344251881Speter  apr_signal(SIGPIPE, SIG_IGN);
2345251881Speter#endif
2346251881Speter
2347251881Speter#ifdef SIGXFSZ
2348251881Speter  /* Disable SIGXFSZ generation for the platforms that have it,
2349251881Speter     otherwise working with large files when compiled against an APR
2350251881Speter     that doesn't have large file support will crash the program,
2351251881Speter     which is uncool. */
2352251881Speter  apr_signal(SIGXFSZ, SIG_IGN);
2353251881Speter#endif
2354251881Speter
2355299742Sdim  err = svn_cmdline_create_auth_baton2(
2356299742Sdim          &opt_baton.source_auth_baton,
2357299742Sdim          opt_baton.non_interactive,
2358299742Sdim          opt_baton.source_username,
2359299742Sdim          opt_baton.source_password,
2360299742Sdim          opt_baton.config_dir,
2361299742Sdim          opt_baton.no_auth_cache,
2362299742Sdim          opt_baton.src_trust.trust_server_cert_unknown_ca,
2363299742Sdim          opt_baton.src_trust.trust_server_cert_cn_mismatch,
2364299742Sdim          opt_baton.src_trust.trust_server_cert_expired,
2365299742Sdim          opt_baton.src_trust.trust_server_cert_not_yet_valid,
2366299742Sdim          opt_baton.src_trust.trust_server_cert_other_failure,
2367299742Sdim          config,
2368299742Sdim          check_cancel, NULL,
2369299742Sdim          pool);
2370251881Speter  if (! err)
2371299742Sdim    err = svn_cmdline_create_auth_baton2(
2372299742Sdim            &opt_baton.sync_auth_baton,
2373299742Sdim            opt_baton.non_interactive,
2374299742Sdim            opt_baton.sync_username,
2375299742Sdim            opt_baton.sync_password,
2376299742Sdim            opt_baton.config_dir,
2377299742Sdim            opt_baton.no_auth_cache,
2378299742Sdim            opt_baton.dst_trust.trust_server_cert_unknown_ca,
2379299742Sdim            opt_baton.dst_trust.trust_server_cert_cn_mismatch,
2380299742Sdim            opt_baton.dst_trust.trust_server_cert_expired,
2381299742Sdim            opt_baton.dst_trust.trust_server_cert_not_yet_valid,
2382299742Sdim            opt_baton.dst_trust.trust_server_cert_other_failure,
2383299742Sdim            config,
2384299742Sdim            check_cancel, NULL,
2385299742Sdim            pool);
2386251881Speter  if (! err)
2387251881Speter    err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2388251881Speter  if (err)
2389251881Speter    {
2390251881Speter      /* For argument-related problems, suggest using the 'help'
2391251881Speter         subcommand. */
2392251881Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2393251881Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2394251881Speter        {
2395251881Speter          err = svn_error_quick_wrap(err,
2396251881Speter                                     _("Try 'svnsync help' for more info"));
2397251881Speter        }
2398251881Speter
2399299742Sdim      return err;
2400251881Speter    }
2401251881Speter
2402299742Sdim  return SVN_NO_ERROR;
2403299742Sdim}
2404299742Sdim
2405299742Sdimint
2406299742Sdimmain(int argc, const char *argv[])
2407299742Sdim{
2408299742Sdim  apr_pool_t *pool;
2409299742Sdim  int exit_code = EXIT_SUCCESS;
2410299742Sdim  svn_error_t *err;
2411299742Sdim
2412299742Sdim  /* Initialize the app. */
2413299742Sdim  if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
2414299742Sdim    return EXIT_FAILURE;
2415299742Sdim
2416299742Sdim  /* Create our top-level pool.  Use a separate mutexless allocator,
2417299742Sdim   * given this application is single threaded.
2418299742Sdim   */
2419299742Sdim  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2420299742Sdim
2421299742Sdim  err = sub_main(&exit_code, argc, argv, pool);
2422299742Sdim
2423299742Sdim  /* Flush stdout and report if it fails. It would be flushed on exit anyway
2424299742Sdim     but this makes sure that output is not silently lost if it fails. */
2425299742Sdim  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2426299742Sdim
2427299742Sdim  if (err)
2428299742Sdim    {
2429299742Sdim      exit_code = EXIT_FAILURE;
2430299742Sdim      svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
2431299742Sdim    }
2432299742Sdim
2433251881Speter  svn_pool_destroy(pool);
2434299742Sdim  return exit_code;
2435251881Speter}
2436