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