source: valtobtest/subversion-1.6.2/subversion/svnsync/main.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 73.8 KB
Line 
1/*
2 * ====================================================================
3 * Copyright (c) 2005-2008 CollabNet.  All rights reserved.
4 *
5 * This software is licensed as described in the file COPYING, which
6 * you should have received as part of this distribution.  The terms
7 * are also available at http://subversion.tigris.org/license-1.html.
8 * If newer versions of this license are posted there, you may use a
9 * newer version instead, at your option.
10 *
11 * This software consists of voluntary contributions made by many
12 * individuals.  For exact contribution history, see the revision
13 * history and logs, available at http://subversion.tigris.org/.
14 * ====================================================================
15 */
16
17#include "svn_cmdline.h"
18#include "svn_config.h"
19#include "svn_pools.h"
20#include "svn_delta.h"
21#include "svn_path.h"
22#include "svn_props.h"
23#include "svn_auth.h"
24#include "svn_opt.h"
25#include "svn_ra.h"
26
27#include "private/svn_opt_private.h"
28
29#include "svn_private_config.h"
30
31#include <apr_network_io.h>
32#include <apr_signal.h>
33#include <apr_uuid.h>
34
35static svn_opt_subcommand_t initialize_cmd,
36                            synchronize_cmd,
37                            copy_revprops_cmd,
38                            info_cmd,
39                            help_cmd;
40
41enum {
42  svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
43  svnsync_opt_no_auth_cache,
44  svnsync_opt_auth_username,
45  svnsync_opt_auth_password,
46  svnsync_opt_source_username,
47  svnsync_opt_source_password,
48  svnsync_opt_sync_username,
49  svnsync_opt_sync_password,
50  svnsync_opt_config_dir,
51  svnsync_opt_version,
52  svnsync_opt_trust_server_cert
53};
54
55#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
56                             svnsync_opt_no_auth_cache, \
57                             svnsync_opt_auth_username, \
58                             svnsync_opt_auth_password, \
59                             svnsync_opt_trust_server_cert, \
60                             svnsync_opt_source_username, \
61                             svnsync_opt_source_password, \
62                             svnsync_opt_sync_username, \
63                             svnsync_opt_sync_password, \
64                             svnsync_opt_config_dir
65
66static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
67  {
68    { "initialize", initialize_cmd, { "init" },
69      N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
70         "\n"
71         "Initialize a destination repository for synchronization from\n"
72         "another repository.\n"
73         "\n"
74         "The destination URL must point to the root of a repository with\n"
75         "no committed revisions.  The destination repository must allow\n"
76         "revision property changes.\n"
77         "\n"
78         "If the source URL is not the root of a repository, only the\n"
79         "specified part of the repository will be synchronized.\n"
80         "\n"
81         "You should not commit to, or make revision property changes in,\n"
82         "the destination repository by any method other than 'svnsync'.\n"
83         "In other words, the destination repository should be a read-only\n"
84         "mirror of the source repository.\n"),
85      { SVNSYNC_OPTS_DEFAULT, 'q' } },
86    { "synchronize", synchronize_cmd, { "sync" },
87      N_("usage: svnsync synchronize DEST_URL\n"
88         "\n"
89         "Transfer all pending revisions to the destination from the source\n"
90         "with which it was initialized.\n"),
91      { SVNSYNC_OPTS_DEFAULT, 'q' } },
92    { "copy-revprops", copy_revprops_cmd, { 0 },
93      N_("usage: svnsync copy-revprops DEST_URL [REV[:REV2]]\n"
94         "\n"
95         "Copy the revision properties in a given range of revisions to the\n"
96         "destination from the source with which it was initialized.\n"
97         "\n"
98         "If REV and REV2 are provided, copy properties for the revisions\n"
99         "specified by that range, inclusively.  If only REV is provided,\n"
100         "copy properties for that revision alone.  If REV is not provided,\n"
101         "copy properties for all revisions previously transferred to the\n"
102         "destination.\n"
103         "\n"
104         "REV and REV2 must be revisions which were previously transferred\n"
105         "to the destination.  You may use \"HEAD\" for either revision to\n"
106         "mean \"the last revision transferred\".\n"),
107      { SVNSYNC_OPTS_DEFAULT, 'q' } },
108    { "info", info_cmd, { 0 },
109      N_("usage: svnsync info DEST_URL\n"
110         "\n"
111         "Print information about the synchronization destination repository\n"
112         "located at DEST_URL.\n"),
113      { SVNSYNC_OPTS_DEFAULT } },
114    { "help", help_cmd, { "?", "h" },
115      N_("usage: svnsync help [SUBCOMMAND...]\n"
116         "\n"
117         "Describe the usage of this program or its subcommands.\n"),
118      { 0 } },
119    { NULL, NULL, { 0 }, NULL, { 0 } }
120  };
121
122static const apr_getopt_option_t svnsync_options[] =
123  {
124    {"quiet",          'q', 0,
125                       N_("print as little as possible") },
126    {"non-interactive", svnsync_opt_non_interactive, 0,
127                       N_("do no interactive prompting") },
128    {"no-auth-cache",  svnsync_opt_no_auth_cache, 0,
129                       N_("do not cache authentication tokens") },
130    {"username",       svnsync_opt_auth_username, 1,
131                       N_("specify a username ARG (deprecated;\n"
132                          "                             "
133                          "see --source-username and --sync-username)") },
134    {"password",       svnsync_opt_auth_password, 1,
135                       N_("specify a password ARG (deprecated;\n"
136                          "                             "
137                          "see --source-password and --sync-password)") },
138    {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
139                       N_("accept unknown SSL server certificates without\n"
140                          "                             "
141                          "prompting (but only with '--non-interactive')") },
142    {"source-username", svnsync_opt_source_username, 1,
143                       N_("connect to source repository with username ARG") },
144    {"source-password", svnsync_opt_source_password, 1,
145                       N_("connect to source repository with password ARG") },
146    {"sync-username",  svnsync_opt_sync_username, 1,
147                       N_("connect to sync repository with username ARG") },
148    {"sync-password",  svnsync_opt_sync_password, 1,
149                       N_("connect to sync repository with password ARG") },
150    {"config-dir",     svnsync_opt_config_dir, 1,
151                       N_("read user configuration files from directory ARG")},
152    {"version",        svnsync_opt_version, 0,
153                       N_("show program version information")},
154    {"help",           'h', 0,
155                       N_("show help on a subcommand")},
156    {NULL,             '?', 0,
157                       N_("show help on a subcommand")},
158    { 0, 0, 0, 0 }
159  };
160
161typedef struct {
162  svn_boolean_t non_interactive;
163  svn_boolean_t trust_server_cert;
164  svn_boolean_t no_auth_cache;
165  svn_auth_baton_t *source_auth_baton;
166  svn_auth_baton_t *sync_auth_baton;
167  const char *source_username;
168  const char *source_password;
169  const char *sync_username;
170  const char *sync_password;
171  const char *config_dir;
172  apr_hash_t *config;
173  svn_boolean_t quiet;
174  svn_boolean_t version;
175  svn_boolean_t help;
176} opt_baton_t;
177
178
179
180
181/*** Helper functions ***/
182
183
184/* Global record of whether the user has requested cancellation. */
185static volatile sig_atomic_t cancelled = FALSE;
186
187
188/* Callback function for apr_signal(). */
189static void
190signal_handler(int signum)
191{
192  apr_signal(signum, SIG_IGN);
193  cancelled = TRUE;
194}
195
196
197/* Cancellation callback function. */
198static svn_error_t *
199check_cancel(void *baton)
200{
201  if (cancelled)
202    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
203  else
204    return SVN_NO_ERROR;
205}
206
207
208/* Check that the version of libraries in use match what we expect. */
209static svn_error_t *
210check_lib_versions(void)
211{
212  static const svn_version_checklist_t checklist[] =
213    {
214      { "svn_subr",  svn_subr_version },
215      { "svn_delta", svn_delta_version },
216      { "svn_ra",    svn_ra_version },
217      { NULL, NULL }
218    };
219
220  SVN_VERSION_DEFINE(my_version);
221
222  return svn_ver_check_list(&my_version, checklist);
223}
224
225
226/* Acquire a lock (of sorts) on the repository associated with the
227 * given RA SESSION.
228 */
229static svn_error_t *
230get_lock(svn_ra_session_t *session, apr_pool_t *pool)
231{
232  char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
233  svn_string_t *mylocktoken, *reposlocktoken;
234  apr_status_t apr_err;
235  apr_pool_t *subpool;
236  int i;
237
238  apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
239  if (apr_err)
240    return svn_error_wrap_apr(apr_err, _("Can't get local hostname"));
241
242  mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
243                                   svn_uuid_generate(pool));
244
245  subpool = svn_pool_create(pool);
246
247  for (i = 0; i < 10; ++i)
248    {
249      svn_pool_clear(subpool);
250      SVN_ERR(check_cancel(NULL));
251
252      SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken,
253                              subpool));
254
255      if (reposlocktoken)
256        {
257          /* Did we get it?   If so, we're done, otherwise we sleep. */
258          if (strcmp(reposlocktoken->data, mylocktoken->data) == 0)
259            return SVN_NO_ERROR;
260          else
261            {
262              SVN_ERR(svn_cmdline_printf
263                      (pool, _("Failed to get lock on destination "
264                               "repos, currently held by '%s'\n"),
265                       reposlocktoken->data));
266
267              apr_sleep(apr_time_from_sec(1));
268            }
269        }
270      else
271        {
272          SVN_ERR(svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK,
273                                         mylocktoken, subpool));
274        }
275    }
276
277  return svn_error_createf(APR_EINVAL, NULL,
278                           "Couldn't get lock on destination repos "
279                           "after %d attempts\n", i);
280}
281
282
283/* Baton for the various subcommands to share. */
284typedef struct {
285  /* common to all subcommands */
286  apr_hash_t *config;
287  svn_ra_callbacks2_t source_callbacks;
288  svn_ra_callbacks2_t sync_callbacks;
289  svn_boolean_t quiet;
290  const char *to_url;
291
292  /* initialize only */
293  const char *from_url;
294
295  /* synchronize only */
296  svn_revnum_t committed_rev;
297
298  /* copy-revprops only */
299  svn_revnum_t start_rev;
300  svn_revnum_t end_rev;
301
302} subcommand_baton_t;
303
304typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
305                                           subcommand_baton_t *baton,
306                                           apr_pool_t *pool);
307
308
309/* Lock the repository associated with RA SESSION, then execute the
310 * given FUNC/BATON pair while holding the lock.  Finally, drop the
311 * lock once it finishes.
312 */
313static svn_error_t *
314with_locked(svn_ra_session_t *session,
315            with_locked_func_t func,
316            subcommand_baton_t *baton,
317            apr_pool_t *pool)
318{
319  svn_error_t *err, *err2;
320
321  SVN_ERR(get_lock(session, pool));
322
323  err = func(session, baton, pool);
324
325  err2 = svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, NULL, pool);
326  if (err2 && err)
327    {
328      svn_error_clear(err2); /* XXX what to do here? */
329
330      return err;
331    }
332  else if (err2)
333    {
334      return err2;
335    }
336  else
337    {
338      return err;
339    }
340}
341
342
343/* Callback function for the RA session's open_tmp_file()
344 * requirements.
345 */
346static svn_error_t *
347open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
348{
349  return svn_io_open_unique_file3(fp, NULL, NULL,
350                                  svn_io_file_del_on_pool_cleanup,
351                                  pool, pool);
352}
353
354
355/* Return SVN_NO_ERROR iff URL identifies the root directory of the
356 * repository associated with RA session SESS.
357 */
358static svn_error_t *
359check_if_session_is_at_repos_root(svn_ra_session_t *sess,
360                                  const char *url,
361                                  apr_pool_t *pool)
362{
363  const char *sess_root;
364
365  SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
366
367  if (strcmp(url, sess_root) == 0)
368    return SVN_NO_ERROR;
369  else
370    return svn_error_createf
371      (APR_EINVAL, NULL,
372       _("Session is rooted at '%s' but the repos root is '%s'"),
373       url, sess_root);
374}
375
376
377/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
378 * revision REV of the repository associated with RA session SESSION.
379 *
380 * All allocations will be done in a subpool of POOL.
381 */
382static svn_error_t *
383remove_props_not_in_source(svn_ra_session_t *session,
384                           svn_revnum_t rev,
385                           apr_hash_t *source_props,
386                           apr_hash_t *target_props,
387                           apr_pool_t *pool)
388{
389  apr_pool_t *subpool = svn_pool_create(pool);
390  apr_hash_index_t *hi;
391
392  for (hi = apr_hash_first(pool, target_props);
393       hi;
394       hi = apr_hash_next(hi))
395    {
396      const void *key;
397
398      svn_pool_clear(subpool);
399
400      apr_hash_this(hi, &key, NULL, NULL);
401
402      /* Delete property if the key can't be found in SOURCE_PROPS. */
403      if (! apr_hash_get(source_props, key, APR_HASH_KEY_STRING))
404        SVN_ERR(svn_ra_change_rev_prop(session, rev, key, NULL,
405                                       subpool));
406    }
407
408  svn_pool_destroy(subpool);
409
410  return SVN_NO_ERROR;
411}
412
413/* Filter callback function.
414 * Takes a property name KEY, and is expected to return TRUE if the property
415 * should be filtered out (ie. not be copied to the target list), or FALSE if
416 * not.
417 */
418typedef svn_boolean_t (*filter_func_t)(const char *key);
419
420/* Make a new set of properties, by copying those properties in PROPS for which
421 * the filter FILTER returns FALSE.
422 *
423 * The number of filtered properties will be stored in FILTERED_COUNT.
424 *
425 * The returned set of properties is allocated from POOL.
426 */
427static apr_hash_t *
428filter_props(int *filtered_count, apr_hash_t *props,
429             filter_func_t filter,
430             apr_pool_t *pool)
431{
432  apr_hash_index_t *hi;
433  apr_hash_t *filtered = apr_hash_make(pool);
434  *filtered_count = 0;
435
436  for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
437    {
438      void *val;
439      const void *key;
440      apr_ssize_t len;
441
442      apr_hash_this(hi, &key, &len, &val);
443
444      /* Copy all properties:
445          - not matching the exclude pattern if provided OR
446          - matching the include pattern if provided */
447      if (!filter || filter(key) == FALSE)
448        {
449          apr_hash_set(filtered, key, APR_HASH_KEY_STRING, val);
450        }
451      else
452        {
453          *filtered_count += 1;
454        }
455    }
456
457  return filtered;
458}
459
460
461/* Write the set of revision properties REV_PROPS to revision REV to the
462 * repository associated with RA session SESSION.
463 *
464 * All allocations will be done in a subpool of POOL.
465 */
466static svn_error_t *
467write_revprops(int *filtered_count,
468               svn_ra_session_t *session,
469               svn_revnum_t rev,
470               apr_hash_t *rev_props,
471               apr_pool_t *pool)
472{
473  apr_pool_t *subpool = svn_pool_create(pool);
474  apr_hash_index_t *hi;
475
476  *filtered_count = 0;
477
478  for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
479    {
480      const void *key;
481      void *val;
482
483      svn_pool_clear(subpool);
484      apr_hash_this(hi, &key, NULL, &val);
485
486      if (strncmp(key, SVNSYNC_PROP_PREFIX,
487                  sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
488        {
489          SVN_ERR(svn_ra_change_rev_prop(session, rev, key, val, subpool));
490        }
491      else
492        {
493          *filtered_count += 1;
494        }
495    }
496
497  svn_pool_destroy(subpool);
498
499  return SVN_NO_ERROR;
500}
501
502
503static svn_error_t *
504log_properties_copied(svn_boolean_t syncprops_found,
505                      svn_revnum_t rev,
506                      apr_pool_t *pool)
507{
508  if (syncprops_found)
509    SVN_ERR(svn_cmdline_printf(pool,
510                               _("Copied properties for revision %ld "
511                                 "(%s* properties skipped).\n"),
512                               rev, SVNSYNC_PROP_PREFIX));
513  else
514    SVN_ERR(svn_cmdline_printf(pool,
515                               _("Copied properties for revision %ld.\n"),
516                               rev));
517
518  return SVN_NO_ERROR;
519}
520
521/* Copy all the revision properties, except for those that have the
522 * "svn:sync-" prefix, from revision REV of the repository associated
523 * with RA session FROM_SESSION, to the repository associated with RA
524 * session TO_SESSION.
525 *
526 * If SYNC is TRUE, then properties on the destination revision that
527 * do not exist on the source revision will be removed.
528 */
529static svn_error_t *
530copy_revprops(svn_ra_session_t *from_session,
531              svn_ra_session_t *to_session,
532              svn_revnum_t rev,
533              svn_boolean_t sync,
534              svn_boolean_t quiet,
535              apr_pool_t *pool)
536{
537  apr_pool_t *subpool = svn_pool_create(pool);
538  apr_hash_t *existing_props, *rev_props;
539  int filtered_count = 0;
540
541  /* Get the list of revision properties on REV of TARGET. We're only interested
542     in the property names, but we'll get the values 'for free'. */
543  if (sync)
544    SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
545
546  /* Get the list of revision properties on REV of SOURCE. */
547  SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
548
549  /* Copy all but the svn:svnsync properties. */
550  SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
551
552  /* Delete those properties that were in TARGET but not in SOURCE */
553  if (sync)
554    SVN_ERR(remove_props_not_in_source(to_session, rev,
555                                       rev_props, existing_props, pool));
556
557  if (! quiet)
558    SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
559
560  svn_pool_destroy(subpool);
561
562  return SVN_NO_ERROR;
563}
564
565
566/* Return a subcommand baton allocated from POOL and populated with
567   data from the provided parameters, which include the global
568   OPT_BATON options structure and a handful of other options.  Not
569   all parameters are used in all subcommands -- see
570   subcommand_baton_t's definition for details. */
571static subcommand_baton_t *
572make_subcommand_baton(opt_baton_t *opt_baton,
573                      const char *to_url,
574                      const char *from_url,
575                      svn_revnum_t start_rev,
576                      svn_revnum_t end_rev,
577                      apr_pool_t *pool)
578{
579  subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
580  b->config = opt_baton->config;
581  b->source_callbacks.open_tmp_file = open_tmp_file;
582  b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
583  b->sync_callbacks.open_tmp_file = open_tmp_file;
584  b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
585  b->quiet = opt_baton->quiet;
586  b->to_url = to_url;
587  b->from_url = from_url;
588  b->start_rev = start_rev;
589  b->end_rev = end_rev;
590  return b;
591}
592
593
594/*** `svnsync init' ***/
595
596/* Initialize the repository associated with RA session TO_SESSION,
597 * using information found in baton B, while the repository is
598 * locked.  Implements `with_locked_func_t' interface.
599 */
600static svn_error_t *
601do_initialize(svn_ra_session_t *to_session,
602              subcommand_baton_t *baton,
603              apr_pool_t *pool)
604{
605  svn_ra_session_t *from_session;
606  svn_string_t *from_url;
607  svn_revnum_t latest;
608  const char *uuid, *root_url;
609
610  /* First, sanity check to see that we're copying into a brand new repos. */
611
612  SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
613
614  if (latest != 0)
615    return svn_error_create
616      (APR_EINVAL, NULL,
617       _("Cannot initialize a repository with content in it"));
618
619  /* And check to see if anyone's run initialize on it before...  We
620     may want a --force option to override this check. */
621
622  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
623                          &from_url, pool));
624
625  if (from_url)
626    return svn_error_createf
627      (APR_EINVAL, NULL,
628       _("Destination repository is already synchronizing from '%s'"),
629       from_url->data);
630
631  /* Now fill in our bookkeeping info in the dest repository. */
632
633  SVN_ERR(svn_ra_open3(&from_session, baton->from_url, NULL,
634                       &(baton->source_callbacks), baton,
635                       baton->config, pool));
636  SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
637
638  /* If we're doing a partial replay, we have to check first if the server
639     supports this. */
640  if (svn_path_is_child(root_url, baton->from_url, pool))
641    {
642      svn_boolean_t server_supports_partial_replay;
643      svn_error_t *err = svn_ra_has_capability(from_session,
644                                               &server_supports_partial_replay,
645                                               SVN_RA_CAPABILITY_PARTIAL_REPLAY,
646                                               pool);
647      if (err && err->apr_err == SVN_ERR_UNKNOWN_CAPABILITY)
648        {
649          svn_error_clear(err);
650          server_supports_partial_replay = FALSE;
651        }
652
653      if (!server_supports_partial_replay)
654        return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, NULL,
655                                NULL);
656    }
657
658  SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
659                                 svn_string_create(baton->from_url, pool),
660                                 pool));
661
662  SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
663
664  SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
665                                 svn_string_create(uuid, pool), pool));
666
667  SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
668                                 svn_string_create("0", pool), pool));
669
670  /* Finally, copy all non-svnsync revprops from rev 0 of the source
671     repos into the dest repos. */
672
673  SVN_ERR(copy_revprops(from_session, to_session, 0, FALSE,
674                        baton->quiet, pool));
675
676  /* TODO: It would be nice if we could set the dest repos UUID to be
677     equal to the UUID of the source repos, at least optionally.  That
678     way people could check out/log/diff using a local fast mirror,
679     but switch --relocate to the actual final repository in order to
680     make changes...  But at this time, the RA layer doesn't have a
681     way to set a UUID. */
682
683  return SVN_NO_ERROR;
684}
685
686
687/* SUBCOMMAND: init */
688static svn_error_t *
689initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
690{
691  const char *to_url, *from_url;
692  svn_ra_session_t *to_session;
693  opt_baton_t *opt_baton = b;
694  apr_array_header_t *targets;
695  subcommand_baton_t *baton;
696
697  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
698                                        apr_array_make(pool, 0,
699                                                       sizeof(const char *)),
700                                        pool));
701  if (targets->nelts < 2)
702    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
703  if (targets->nelts > 2)
704    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
705
706  to_url = APR_ARRAY_IDX(targets, 0, const char *);
707  from_url = APR_ARRAY_IDX(targets, 1, const char *);
708
709  if (! svn_path_is_url(to_url))
710    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
711                             _("Path '%s' is not a URL"), to_url);
712  if (! svn_path_is_url(from_url))
713    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
714                             _("Path '%s' is not a URL"), from_url);
715
716  baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
717  SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL,
718                       &(baton->sync_callbacks), baton, baton->config, pool));
719  SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
720  SVN_ERR(with_locked(to_session, do_initialize, baton, pool));
721
722  return SVN_NO_ERROR;
723}
724
725
726
727/*** Synchronization Editor ***/
728
729/* This editor has a couple of jobs.
730 *
731 * First, it needs to filter out the propchanges that can't be passed over
732 * libsvn_ra.
733 *
734 * Second, it needs to adjust for the fact that we might not actually have
735 * permission to see all of the data from the remote repository, which means
736 * we could get revisions that are totally empty from our point of view.
737 *
738 * Third, it needs to adjust copyfrom paths, adding the root url for the
739 * destination repository to the beginning of them.
740 */
741
742
743/* Edit baton */
744typedef struct {
745  const svn_delta_editor_t *wrapped_editor;
746  void *wrapped_edit_baton;
747  const char *to_url;  /* URL we're copying into, for correct copyfrom URLs */
748  svn_boolean_t called_open_root;
749  svn_boolean_t got_textdeltas;
750  svn_revnum_t base_revision;
751  svn_boolean_t quiet;
752  svn_boolean_t strip_mergeinfo;    /* Are we stripping svn:mergeinfo? */
753  svn_boolean_t migrate_svnmerge;   /* Are we converting svnmerge.py data? */
754  svn_boolean_t mergeinfo_stripped; /* Did we strip svn:mergeinfo? */
755  svn_boolean_t svnmerge_migrated;  /* Did we convert svnmerge.py data? */
756  svn_boolean_t svnmerge_blocked;   /* Was there any blocked svnmerge data? */
757} edit_baton_t;
758
759
760/* A dual-purpose baton for files and directories. */
761typedef struct {
762  void *edit_baton;
763  void *wrapped_node_baton;
764} node_baton_t;
765
766
767/*** Editor vtable functions ***/
768
769static svn_error_t *
770set_target_revision(void *edit_baton,
771                    svn_revnum_t target_revision,
772                    apr_pool_t *pool)
773{
774  edit_baton_t *eb = edit_baton;
775  return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
776                                                 target_revision, pool);
777}
778
779static svn_error_t *
780open_root(void *edit_baton,
781          svn_revnum_t base_revision,
782          apr_pool_t *pool,
783          void **root_baton)
784{
785  edit_baton_t *eb = edit_baton;
786  node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
787
788  SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
789                                        base_revision, pool,
790                                        &dir_baton->wrapped_node_baton));
791
792  eb->called_open_root = TRUE;
793  dir_baton->edit_baton = edit_baton;
794  *root_baton = dir_baton;
795
796  return SVN_NO_ERROR;
797}
798
799static svn_error_t *
800delete_entry(const char *path,
801             svn_revnum_t base_revision,
802             void *parent_baton,
803             apr_pool_t *pool)
804{
805  node_baton_t *pb = parent_baton;
806  edit_baton_t *eb = pb->edit_baton;
807
808  return eb->wrapped_editor->delete_entry(path, base_revision,
809                                          pb->wrapped_node_baton, pool);
810}
811
812static svn_error_t *
813add_directory(const char *path,
814              void *parent_baton,
815              const char *copyfrom_path,
816              svn_revnum_t copyfrom_rev,
817              apr_pool_t *pool,
818              void **child_baton)
819{
820  node_baton_t *pb = parent_baton;
821  edit_baton_t *eb = pb->edit_baton;
822  node_baton_t *b = apr_palloc(pool, sizeof(*b));
823
824  if (copyfrom_path)
825    copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
826                                 svn_path_uri_encode(copyfrom_path, pool));
827
828  SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
829                                            copyfrom_path,
830                                            copyfrom_rev, pool,
831                                            &b->wrapped_node_baton));
832
833  b->edit_baton = eb;
834  *child_baton = b;
835
836  return SVN_NO_ERROR;
837}
838
839static svn_error_t *
840open_directory(const char *path,
841               void *parent_baton,
842               svn_revnum_t base_revision,
843               apr_pool_t *pool,
844               void **child_baton)
845{
846  node_baton_t *pb = parent_baton;
847  edit_baton_t *eb = pb->edit_baton;
848  node_baton_t *db = apr_palloc(pool, sizeof(*db));
849
850  SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
851                                             base_revision, pool,
852                                             &db->wrapped_node_baton));
853
854  db->edit_baton = eb;
855  *child_baton = db;
856
857  return SVN_NO_ERROR;
858}
859
860static svn_error_t *
861add_file(const char *path,
862         void *parent_baton,
863         const char *copyfrom_path,
864         svn_revnum_t copyfrom_rev,
865         apr_pool_t *pool,
866         void **file_baton)
867{
868  node_baton_t *pb = parent_baton;
869  edit_baton_t *eb = pb->edit_baton;
870  node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
871
872  if (copyfrom_path)
873    copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
874                                 svn_path_uri_encode(copyfrom_path, pool));
875
876  SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
877                                       copyfrom_path, copyfrom_rev,
878                                       pool, &fb->wrapped_node_baton));
879
880  fb->edit_baton = eb;
881  *file_baton = fb;
882
883  return SVN_NO_ERROR;
884}
885
886static svn_error_t *
887open_file(const char *path,
888          void *parent_baton,
889          svn_revnum_t base_revision,
890          apr_pool_t *pool,
891          void **file_baton)
892{
893  node_baton_t *pb = parent_baton;
894  edit_baton_t *eb = pb->edit_baton;
895  node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
896
897  SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
898                                        base_revision, pool,
899                                        &fb->wrapped_node_baton));
900
901  fb->edit_baton = eb;
902  *file_baton = fb;
903
904  return SVN_NO_ERROR;
905}
906
907static svn_error_t *
908apply_textdelta(void *file_baton,
909                const char *base_checksum,
910                apr_pool_t *pool,
911                svn_txdelta_window_handler_t *handler,
912                void **handler_baton)
913{
914  node_baton_t *fb = file_baton;
915  edit_baton_t *eb = fb->edit_baton;
916
917  if (! eb->quiet)
918    {
919      if (! eb->got_textdeltas)
920        SVN_ERR(svn_cmdline_printf(pool, _("Transmitting file data ")));
921      SVN_ERR(svn_cmdline_printf(pool, "."));
922      SVN_ERR(svn_cmdline_fflush(stdout));
923    }
924
925  eb->got_textdeltas = TRUE;
926  return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
927                                             base_checksum, pool,
928                                             handler, handler_baton);
929}
930
931static svn_error_t *
932close_file(void *file_baton,
933           const char *text_checksum,
934           apr_pool_t *pool)
935{
936  node_baton_t *fb = file_baton;
937  edit_baton_t *eb = fb->edit_baton;
938  return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
939                                        text_checksum, pool);
940}
941
942static svn_error_t *
943absent_file(const char *path,
944            void *file_baton,
945            apr_pool_t *pool)
946{
947  node_baton_t *fb = file_baton;
948  edit_baton_t *eb = fb->edit_baton;
949  return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
950}
951
952static svn_error_t *
953close_directory(void *dir_baton,
954                apr_pool_t *pool)
955{
956  node_baton_t *db = dir_baton;
957  edit_baton_t *eb = db->edit_baton;
958  return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
959}
960
961static svn_error_t *
962absent_directory(const char *path,
963                 void *dir_baton,
964                 apr_pool_t *pool)
965{
966  node_baton_t *db = dir_baton;
967  edit_baton_t *eb = db->edit_baton;
968  return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
969                                              pool);
970}
971
972static svn_error_t *
973change_file_prop(void *file_baton,
974                 const char *name,
975                 const svn_string_t *value,
976                 apr_pool_t *pool)
977{
978  node_baton_t *fb = file_baton;
979  edit_baton_t *eb = fb->edit_baton;
980
981  /* only regular properties can pass over libsvn_ra */
982  if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
983    return SVN_NO_ERROR;
984
985  /* Maybe drop svn:mergeinfo.  */
986  if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
987    {
988      eb->mergeinfo_stripped = TRUE;
989      return SVN_NO_ERROR;
990    }
991
992  /* Maybe drop (errantly set, as this is a file) svnmerge.py properties. */
993  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
994    {
995      eb->svnmerge_migrated = TRUE;
996      return SVN_NO_ERROR;
997    }
998
999  /* Remember if we see any svnmerge-blocked properties.  (They really
1000     shouldn't be here, as this is a file, but whatever...)  */
1001  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
1002    {
1003      eb->svnmerge_blocked = TRUE;
1004    }
1005
1006  return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
1007                                              name, value, pool);
1008}
1009
1010static svn_error_t *
1011change_dir_prop(void *dir_baton,
1012                const char *name,
1013                const svn_string_t *value,
1014                apr_pool_t *pool)
1015{
1016  node_baton_t *db = dir_baton;
1017  edit_baton_t *eb = db->edit_baton;
1018  svn_string_t *real_value = (svn_string_t *)value;
1019
1020  /* Only regular properties can pass over libsvn_ra */
1021  if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
1022    return SVN_NO_ERROR;
1023
1024  /* Maybe drop svn:mergeinfo.  */
1025  if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
1026    {
1027      eb->mergeinfo_stripped = TRUE;
1028      return SVN_NO_ERROR;
1029    }
1030
1031  /* Maybe convert svnmerge-integrated data into svn:mergeinfo.  (We
1032     ignore svnmerge-blocked for now.) */
1033  /* ### FIXME: Consult the mirror repository's HEAD prop values and
1034     ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */
1035  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
1036    {
1037      if (value)
1038        {
1039          /* svnmerge-integrated differs from svn:mergeinfo in a pair
1040             of ways.  First, it can use tabs, newlines, or spaces to
1041             delimit source information.  Secondly, the source paths
1042             are relative URLs, whereas svn:mergeinfo uses relative
1043             paths (not URI-encoded). */
1044          svn_error_t *err;
1045          svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create("", pool);
1046          svn_mergeinfo_t mergeinfo;
1047          int i;
1048          apr_array_header_t *sources =
1049            svn_cstring_split(value->data, " \t\n", TRUE, pool);
1050
1051          for (i = 0; i < sources->nelts; i++)
1052            {
1053              const char *rel_path;
1054              apr_array_header_t *path_revs =
1055                svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *),
1056                                  ":", TRUE, pool);
1057
1058              /* ### TODO: Warn? */
1059              if (path_revs->nelts != 2)
1060                continue;
1061
1062              /* Append this source's mergeinfo data. */
1063              rel_path = APR_ARRAY_IDX(path_revs, 0, const char *);
1064              rel_path = svn_path_uri_decode(rel_path, pool);
1065              svn_stringbuf_appendcstr(mergeinfo_buf, rel_path);
1066              svn_stringbuf_appendcstr(mergeinfo_buf, ":");
1067              svn_stringbuf_appendcstr(mergeinfo_buf,
1068                                       APR_ARRAY_IDX(path_revs, 1,
1069                                                     const char *));
1070              svn_stringbuf_appendcstr(mergeinfo_buf, "\n");
1071            }
1072
1073          /* Try to parse the mergeinfo string we've created, just to
1074             check for bogosity.  If all goes well, we'll unparse it
1075             again and use that as our property value.  */
1076          err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool);
1077          if (err)
1078            {
1079              svn_error_clear(err);
1080              return SVN_NO_ERROR;
1081            }
1082          SVN_ERR(svn_mergeinfo_to_string(&real_value, mergeinfo, pool));
1083        }
1084      name = SVN_PROP_MERGEINFO;
1085      eb->svnmerge_migrated = TRUE;
1086    }
1087
1088  /* Remember if we see any svnmerge-blocked properties. */
1089  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
1090    {
1091      eb->svnmerge_blocked = TRUE;
1092    }
1093
1094  return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
1095                                             name, real_value, pool);
1096}
1097
1098static svn_error_t *
1099close_edit(void *edit_baton,
1100           apr_pool_t *pool)
1101{
1102  edit_baton_t *eb = edit_baton;
1103
1104  /* If we haven't opened the root yet, that means we're transfering
1105     an empty revision, probably because we aren't allowed to see the
1106     contents for some reason.  In any event, we need to open the root
1107     and close it again, before we can close out the edit, or the
1108     commit will fail. */
1109
1110  if (! eb->called_open_root)
1111    {
1112      void *baton;
1113      SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
1114                                            eb->base_revision, pool,
1115                                            &baton));
1116      SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
1117    }
1118
1119  if (! eb->quiet)
1120    {
1121      if (eb->got_textdeltas)
1122        SVN_ERR(svn_cmdline_printf(pool, "\n"));
1123      if (eb->mergeinfo_stripped)
1124        SVN_ERR(svn_cmdline_printf(pool,
1125                                   "NOTE: Dropped Subversion mergeinfo "
1126                                   "from this revision.\n"));
1127      if (eb->svnmerge_migrated)
1128        SVN_ERR(svn_cmdline_printf(pool,
1129                                   "NOTE: Migrated 'svnmerge-integrated' in "
1130                                   "this revision.\n"));
1131      if (eb->svnmerge_blocked)
1132        SVN_ERR(svn_cmdline_printf(pool,
1133                                   "NOTE: Saw 'svnmerge-blocked' in this "
1134                                   "revision (but didn't migrate it).\n"));
1135    }
1136
1137  return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
1138}
1139
1140/*** Editor factory function ***/
1141
1142/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
1143 * that wraps our own commit EDITOR/EDIT_BATON.  BASE_REVISION is the
1144 * revision on which the driver of this returned editor will be basing
1145 * the commit.  TO_URL is the URL of the root of the repository into
1146 * which the commit is being made.
1147 */
1148static svn_error_t *
1149get_sync_editor(const svn_delta_editor_t *wrapped_editor,
1150                void *wrapped_edit_baton,
1151                svn_revnum_t base_revision,
1152                const char *to_url,
1153                svn_boolean_t quiet,
1154                const svn_delta_editor_t **editor,
1155                void **edit_baton,
1156                apr_pool_t *pool)
1157{
1158  svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
1159  edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
1160
1161  tree_editor->set_target_revision = set_target_revision;
1162  tree_editor->open_root = open_root;
1163  tree_editor->delete_entry = delete_entry;
1164  tree_editor->add_directory = add_directory;
1165  tree_editor->open_directory = open_directory;
1166  tree_editor->change_dir_prop = change_dir_prop;
1167  tree_editor->close_directory = close_directory;
1168  tree_editor->absent_directory = absent_directory;
1169  tree_editor->add_file = add_file;
1170  tree_editor->open_file = open_file;
1171  tree_editor->apply_textdelta = apply_textdelta;
1172  tree_editor->change_file_prop = change_file_prop;
1173  tree_editor->close_file = close_file;
1174  tree_editor->absent_file = absent_file;
1175  tree_editor->close_edit = close_edit;
1176
1177  eb->wrapped_editor = wrapped_editor;
1178  eb->wrapped_edit_baton = wrapped_edit_baton;
1179  eb->base_revision = base_revision;
1180  eb->to_url = to_url;
1181  eb->quiet = quiet;
1182
1183  if (getenv("SVNSYNC_UNSUPPORTED_STRIP_MERGEINFO"))
1184    {
1185      eb->strip_mergeinfo = TRUE;
1186    }
1187  if (getenv("SVNSYNC_UNSUPPORTED_MIGRATE_SVNMERGE"))
1188    {
1189      /* Current we can't merge property values.  That's only possible
1190         if all the properties to be merged were always modified in
1191         exactly the same revisions, or if we allow ourselves to
1192         lookup the current state of properties in the sync
1193         destination.  So for now, migrating svnmerge.py data implies
1194         stripping pre-existing svn:mergeinfo. */
1195      /* ### FIXME: Do a real migration by consulting the mirror
1196         ### repository's HEAD propvalues and merging svn:mergeinfo,
1197         ### svnmerge-integrated, and svnmerge-blocked together. */
1198      eb->migrate_svnmerge = TRUE;
1199      eb->strip_mergeinfo = TRUE;
1200    }
1201
1202  *editor = tree_editor;
1203  *edit_baton = eb;
1204
1205  return SVN_NO_ERROR;
1206}
1207
1208
1209
1210/*** `svnsync sync' ***/
1211
1212/* Implements `svn_commit_callback2_t' interface. */
1213static svn_error_t *
1214commit_callback(const svn_commit_info_t *commit_info,
1215                void *baton,
1216                apr_pool_t *pool)
1217{
1218  subcommand_baton_t *sb = baton;
1219
1220  if (! sb->quiet)
1221    {
1222      SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
1223                                 commit_info->revision));
1224    }
1225
1226  sb->committed_rev = commit_info->revision;
1227
1228  return SVN_NO_ERROR;
1229}
1230
1231
1232/* Set *FROM_SESSION to an RA session associated with the source
1233 * repository of the synchronization, as determined by reading
1234 * svn:sync- properties from the destination repository (associated
1235 * with TO_SESSION).  Set LAST_MERGED_REV to the value of the property
1236 * which records the most recently synchronized revision.
1237 *
1238 * CALLBACKS is a vtable of RA callbacks to provide when creating
1239 * *FROM_SESSION.  CONFIG is a configuration hash.
1240 */
1241static svn_error_t *
1242open_source_session(svn_ra_session_t **from_session,
1243                    svn_string_t **last_merged_rev,
1244                    svn_ra_session_t *to_session,
1245                    svn_ra_callbacks2_t *callbacks,
1246                    apr_hash_t *config,
1247                    void *baton,
1248                    apr_pool_t *pool)
1249{
1250  svn_string_t *from_url, *from_uuid;
1251
1252  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
1253                          &from_url, pool));
1254  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
1255                          &from_uuid, pool));
1256  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
1257                          last_merged_rev, pool));
1258
1259  if (! from_url || ! from_uuid || ! *last_merged_rev)
1260    return svn_error_create
1261      (APR_EINVAL, NULL,
1262       _("Destination repository has not been initialized"));
1263
1264  /* Open the session to copy the revision data. */
1265  SVN_ERR(svn_ra_open3(from_session, from_url->data, from_uuid->data,
1266                       callbacks, baton, config, pool));
1267
1268  return SVN_NO_ERROR;
1269}
1270
1271/* Replay baton, used during sychnronization. */
1272typedef struct {
1273  svn_ra_session_t *from_session;
1274  svn_ra_session_t *to_session;
1275  subcommand_baton_t *sb;
1276  svn_boolean_t has_commit_revprops_capability;
1277} replay_baton_t;
1278
1279/* Return a replay baton allocated from POOL and populated with
1280   data from the provided parameters. */
1281static replay_baton_t *
1282make_replay_baton(svn_ra_session_t *from_session,
1283                  svn_ra_session_t *to_session,
1284                  subcommand_baton_t *sb, apr_pool_t *pool)
1285{
1286  replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
1287  rb->from_session = from_session;
1288  rb->to_session = to_session;
1289  rb->sb = sb;
1290  return rb;
1291}
1292
1293/* Filter out svn:date and svn:author properties. */
1294static svn_boolean_t
1295filter_exclude_date_author_sync(const char *key)
1296{
1297  if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
1298    return TRUE;
1299  else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
1300    return TRUE;
1301  else if (strncmp(key, SVNSYNC_PROP_PREFIX,
1302                   sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
1303    return TRUE;
1304
1305  return FALSE;
1306}
1307
1308/* Filter out all properties except svn:date and svn:author */
1309static svn_boolean_t
1310filter_include_date_author_sync(const char *key)
1311{
1312  return ! filter_exclude_date_author_sync(key);
1313}
1314
1315
1316/* Only exclude svn:log .*/
1317static svn_boolean_t
1318filter_exclude_log(const char *key)
1319{
1320  if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
1321    return TRUE;
1322  else
1323    return FALSE;
1324}
1325
1326/* Only include svn:log. */
1327static svn_boolean_t
1328filter_include_log(const char *key)
1329{
1330  return ! filter_exclude_log(key);
1331}
1332
1333
1334/* Callback function for svn_ra_replay_range, invoked when starting to parse
1335 * a replay report.
1336 */
1337static svn_error_t *
1338replay_rev_started(svn_revnum_t revision,
1339                   void *replay_baton,
1340                   const svn_delta_editor_t **editor,
1341                   void **edit_baton,
1342                   apr_hash_t *rev_props,
1343                   apr_pool_t *pool)
1344{
1345  const svn_delta_editor_t *commit_editor;
1346  const svn_delta_editor_t *cancel_editor;
1347  const svn_delta_editor_t *sync_editor;
1348  void *commit_baton;
1349  void *cancel_baton;
1350  void *sync_baton;
1351  replay_baton_t *rb = replay_baton;
1352  apr_hash_t *filtered;
1353  int filtered_count;
1354
1355  /* We set this property so that if we error out for some reason
1356     we can later determine where we were in the process of
1357     merging a revision.  If we had committed the change, but we
1358     hadn't finished copying the revprops we need to know that, so
1359     we can go back and finish the job before we move on.
1360
1361     NOTE: We have to set this before we start the commit editor,
1362     because ra_svn doesn't let you change rev props during a
1363     commit. */
1364  SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0,
1365                                 SVNSYNC_PROP_CURRENTLY_COPYING,
1366                                 svn_string_createf(pool, "%ld",
1367                                                    revision),
1368                                 pool));
1369
1370  /* The actual copy is just a replay hooked up to a commit.  Include
1371     all the revision properties from the source repositories, except
1372     'svn:author' and 'svn:date', those are not guaranteed to get
1373     through the editor anyway.
1374     If we're syncing to an non-commit-revprops capable server, filter
1375     out all revprops except svn:log and add them later in
1376     revplay_rev_finished. */
1377  filtered = filter_props(&filtered_count, rev_props,
1378                          (rb->has_commit_revprops_capability
1379                            ? filter_exclude_date_author_sync
1380                            : filter_include_log),
1381                          pool);
1382
1383  /* svn_ra_get_commit_editor3 requires the log message to be
1384     set. It's possible that we didn't receive 'svn:log' here, so we
1385     have to set it to at least the empty string. If there's a svn:log
1386     property on this revision, we will write the actual value in the
1387     replay_rev_finished callback. */
1388  if (! apr_hash_get(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING))
1389    apr_hash_set(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
1390                 svn_string_create("", pool));
1391
1392  SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
1393                                    &commit_baton,
1394                                    filtered,
1395                                    commit_callback, rb->sb,
1396                                    NULL, FALSE, pool));
1397
1398  /* There's one catch though, the diff shows us props we can't send
1399     over the RA interface, so we need an editor that's smart enough
1400     to filter those out for us.  */
1401  SVN_ERR(get_sync_editor(commit_editor, commit_baton, revision - 1,
1402                          rb->sb->to_url, rb->sb->quiet,
1403                          &sync_editor, &sync_baton, pool));
1404
1405  SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1406                                            sync_editor, sync_baton,
1407                                            &cancel_editor,
1408                                            &cancel_baton,
1409                                            pool));
1410  *editor = cancel_editor;
1411  *edit_baton = cancel_baton;
1412
1413  return SVN_NO_ERROR;
1414}
1415
1416/* Callback function for svn_ra_replay_range, invoked when finishing parsing
1417 * a replay report.
1418 */
1419static svn_error_t *
1420replay_rev_finished(svn_revnum_t revision,
1421                    void *replay_baton,
1422                    const svn_delta_editor_t *editor,
1423                    void *edit_baton,
1424                    apr_hash_t *rev_props,
1425                    apr_pool_t *pool)
1426{
1427  apr_pool_t *subpool = svn_pool_create(pool);
1428  replay_baton_t *rb = replay_baton;
1429  apr_hash_t *filtered, *existing_props;
1430  int filtered_count;
1431
1432  SVN_ERR(editor->close_edit(edit_baton, pool));
1433
1434  /* Sanity check that we actually committed the revision we meant to. */
1435  if (rb->sb->committed_rev != revision)
1436    return svn_error_createf
1437             (APR_EINVAL, NULL,
1438              _("Commit created rev %ld but should have created %ld"),
1439              rb->sb->committed_rev, revision);
1440
1441  SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
1442                              subpool));
1443
1444
1445  /* Ok, we're done with the data, now we just need to copy the remaining
1446     'svn:date' and 'svn:author' revprops and we're all set.
1447     If the server doesn't support revprops-in-a-commit, we still have to
1448     set all revision properties except svn:log. */
1449  filtered = filter_props(&filtered_count, rev_props,
1450                          (rb->has_commit_revprops_capability
1451                            ? filter_include_date_author_sync
1452                            : filter_exclude_log),
1453                          subpool);
1454  SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
1455                         subpool));
1456
1457  /* Remove all extra properties in TARGET. */
1458  SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
1459                                     rev_props, existing_props, subpool));
1460
1461  svn_pool_clear(subpool);
1462
1463  /* Ok, we're done, bring the last-merged-rev property up to date. */
1464  SVN_ERR(svn_ra_change_rev_prop
1465          (rb->to_session,
1466           0,
1467           SVNSYNC_PROP_LAST_MERGED_REV,
1468           svn_string_create(apr_psprintf(pool, "%ld", revision),
1469                             subpool),
1470           subpool));
1471
1472  /* And finally drop the currently copying prop, since we're done
1473     with this revision. */
1474  SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0,
1475                                 SVNSYNC_PROP_CURRENTLY_COPYING,
1476                                 NULL, subpool));
1477
1478  /* Notify the user that we copied revision properties. */
1479  if (! rb->sb->quiet)
1480    SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
1481
1482  svn_pool_destroy(subpool);
1483
1484  return SVN_NO_ERROR;
1485}
1486
1487/* Synchronize the repository associated with RA session TO_SESSION,
1488 * using information found in baton B, while the repository is
1489 * locked.  Implements `with_locked_func_t' interface.
1490 */
1491static svn_error_t *
1492do_synchronize(svn_ra_session_t *to_session,
1493               subcommand_baton_t *baton, apr_pool_t *pool)
1494{
1495  svn_string_t *last_merged_rev;
1496  svn_revnum_t from_latest;
1497  svn_ra_session_t *from_session;
1498  svn_string_t *currently_copying;
1499  svn_revnum_t to_latest, copying, last_merged;
1500  svn_revnum_t start_revision, end_revision;
1501  replay_baton_t *rb;
1502
1503  SVN_ERR(open_source_session(&from_session,
1504                              &last_merged_rev, to_session,
1505                              &(baton->source_callbacks), baton->config,
1506                              baton, pool));
1507
1508  /* Check to see if we have revprops that still need to be copied for
1509     a prior revision we didn't finish copying.  But first, check for
1510     state sanity.  Remember, mirroring is not an atomic action,
1511     because revision properties are copied separately from the
1512     revision's contents.
1513
1514     So, any time that currently-copying is not set, then
1515     last-merged-rev should be the HEAD revision of the destination
1516     repository.  That is, if we didn't fall over in the middle of a
1517     previous synchronization, then our destination repository should
1518     have exactly as many revisions in it as we've synchronized.
1519
1520     Alternately, if currently-copying *is* set, it must
1521     be either last-merged-rev or last-merged-rev + 1, and the HEAD
1522     revision must be equal to either last-merged-rev or
1523     currently-copying. If this is not the case, somebody has meddled
1524     with the destination without using svnsync.
1525  */
1526
1527  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
1528                          &currently_copying, pool));
1529
1530  SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
1531
1532  last_merged = SVN_STR_TO_REV(last_merged_rev->data);
1533
1534  if (currently_copying)
1535    {
1536      copying = SVN_STR_TO_REV(currently_copying->data);
1537
1538      if ((copying < last_merged)
1539          || (copying > (last_merged + 1))
1540          || ((to_latest != last_merged) && (to_latest != copying)))
1541        {
1542          return svn_error_createf
1543            (APR_EINVAL, NULL,
1544             _("Revision being currently copied (%ld), last merged revision "
1545               "(%ld), and destination HEAD (%ld) are inconsistent; have you "
1546               "committed to the destination without using svnsync?"),
1547             copying, last_merged, to_latest);
1548        }
1549      else if (copying == to_latest)
1550        {
1551          if (copying > last_merged)
1552            {
1553              SVN_ERR(copy_revprops(from_session, to_session,
1554                                    to_latest, TRUE, baton->quiet,
1555                                    pool));
1556              last_merged = copying;
1557              last_merged_rev = svn_string_create
1558                (apr_psprintf(pool, "%ld", last_merged), pool);
1559            }
1560
1561          /* Now update last merged rev and drop currently changing.
1562             Note that the order here is significant, if we do them
1563             in the wrong order there are race conditions where we
1564             end up not being able to tell if there have been bogus
1565             (i.e. non-svnsync) commits to the dest repository. */
1566
1567          SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
1568                                         SVNSYNC_PROP_LAST_MERGED_REV,
1569                                         last_merged_rev, pool));
1570          SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
1571                                         SVNSYNC_PROP_CURRENTLY_COPYING,
1572                                         NULL, pool));
1573        }
1574      /* If copying > to_latest, then we just fall through to
1575         attempting to copy the revision again. */
1576    }
1577  else
1578    {
1579      if (to_latest != last_merged)
1580        return svn_error_createf(APR_EINVAL, NULL,
1581                                 _("Destination HEAD (%ld) is not the last "
1582                                   "merged revision (%ld); have you "
1583                                   "committed to the destination without "
1584                                   "using svnsync?"),
1585                                 to_latest, last_merged);
1586    }
1587
1588  /* Now check to see if there are any revisions to copy. */
1589  SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
1590
1591  if (from_latest < atol(last_merged_rev->data))
1592    return SVN_NO_ERROR;
1593
1594  /* Ok, so there are new revisions, iterate over them copying them
1595     into the destination repository. */
1596  rb = make_replay_baton(from_session, to_session, baton, pool);
1597
1598  /* For compatibility with older svnserve versions, check first if we
1599     support adding revprops to the commit. */
1600  SVN_ERR(svn_ra_has_capability(rb->to_session,
1601                                &rb->has_commit_revprops_capability,
1602                                SVN_RA_CAPABILITY_COMMIT_REVPROPS,
1603                                pool));
1604
1605  start_revision = atol(last_merged_rev->data) + 1;
1606  end_revision = from_latest;
1607
1608  SVN_ERR(check_cancel(NULL));
1609
1610  SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
1611                              0, TRUE, replay_rev_started,
1612                              replay_rev_finished, rb, pool));
1613
1614  return SVN_NO_ERROR;
1615}
1616
1617
1618/* SUBCOMMAND: sync */
1619static svn_error_t *
1620synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1621{
1622  svn_ra_session_t *to_session;
1623  opt_baton_t *opt_baton = b;
1624  apr_array_header_t *targets;
1625  subcommand_baton_t *baton;
1626  const char *to_url;
1627
1628  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1629                                        apr_array_make(pool, 0,
1630                                                       sizeof(const char *)),
1631                                        pool));
1632  if (targets->nelts < 1)
1633    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1634  if (targets->nelts > 1)
1635    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1636
1637  to_url = APR_ARRAY_IDX(targets, 0, const char *);
1638
1639  if (! svn_path_is_url(to_url))
1640    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1641                             _("Path '%s' is not a URL"), to_url);
1642
1643  baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1644  SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL,
1645                       &(baton->sync_callbacks), baton, baton->config, pool));
1646  SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
1647  SVN_ERR(with_locked(to_session, do_synchronize, baton, pool));
1648
1649  return SVN_NO_ERROR;
1650}
1651
1652
1653
1654/*** `svnsync copy-revprops' ***/
1655
1656/* Copy revision properties to the repository associated with RA
1657 * session TO_SESSION, using information found in baton B, while the
1658 * repository is locked.  Implements `with_locked_func_t' interface.
1659 */
1660static svn_error_t *
1661do_copy_revprops(svn_ra_session_t *to_session,
1662                 subcommand_baton_t *baton, apr_pool_t *pool)
1663{
1664  svn_ra_session_t *from_session;
1665  svn_string_t *last_merged_rev;
1666  svn_revnum_t i;
1667  svn_revnum_t step = 1;
1668
1669  SVN_ERR(open_source_session(&from_session, &last_merged_rev,
1670                              to_session,
1671                              &(baton->source_callbacks), baton->config,
1672                              baton, pool));
1673
1674  /* An invalid revision means "last-synced" */
1675  if (! SVN_IS_VALID_REVNUM(baton->start_rev))
1676    baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
1677  if (! SVN_IS_VALID_REVNUM(baton->end_rev))
1678    baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
1679
1680  /* Make sure we have revisions within the valid range. */
1681  if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
1682    return svn_error_createf
1683      (APR_EINVAL, NULL,
1684       _("Cannot copy revprops for a revision (%ld) that has not "
1685         "been synchronized yet"), baton->start_rev);
1686  if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
1687    return svn_error_createf
1688      (APR_EINVAL, NULL,
1689       _("Cannot copy revprops for a revision (%ld) that has not "
1690         "been synchronized yet"), baton->end_rev);
1691
1692  /* Now, copy all the requested revisions, in the requested order. */
1693  step = (baton->start_rev > baton->end_rev) ? -1 : 1;
1694  for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
1695    {
1696      SVN_ERR(check_cancel(NULL));
1697      SVN_ERR(copy_revprops(from_session, to_session, i, FALSE,
1698                            baton->quiet, pool));
1699    }
1700
1701  return SVN_NO_ERROR;
1702}
1703
1704
1705/* SUBCOMMAND: copy-revprops */
1706static svn_error_t *
1707copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1708{
1709  svn_ra_session_t *to_session;
1710  opt_baton_t *opt_baton = b;
1711  apr_array_header_t *targets;
1712  subcommand_baton_t *baton;
1713  const char *to_url;
1714  svn_opt_revision_t start_revision, end_revision;
1715  svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
1716
1717  /* There should be either one or two arguments left to parse. */
1718  if (os->argc - os->ind > 2)
1719    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1720  if (os->argc - os->ind < 1)
1721    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1722
1723  /* If there are two args, the last one is a revision range.  We'll
1724     effectively pop it from the end of the list.  Why?  Because
1725     svn_opt__args_to_target_array() does waaaaay too many useful
1726     things for us not to use it.  */
1727  if (os->argc - os->ind == 2)
1728    {
1729      const char *rev_str = os->argv[--(os->argc)];
1730
1731      start_revision.kind = svn_opt_revision_unspecified;
1732      end_revision.kind = svn_opt_revision_unspecified;
1733      if ((svn_opt_parse_revision(&start_revision, &end_revision,
1734                                  rev_str, pool) != 0)
1735          || ((start_revision.kind != svn_opt_revision_number)
1736              && (start_revision.kind != svn_opt_revision_head))
1737          || ((end_revision.kind != svn_opt_revision_number)
1738              && (end_revision.kind != svn_opt_revision_head)
1739              && (end_revision.kind != svn_opt_revision_unspecified)))
1740        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1741                                 _("'%s' is not a valid revision range"),
1742                                 rev_str);
1743
1744      /* Get the start revision, which must be either HEAD or a number
1745         (which is required to be a valid one). */
1746      if (start_revision.kind == svn_opt_revision_head)
1747        {
1748          start_rev = SVN_INVALID_REVNUM;
1749        }
1750      else
1751        {
1752          start_rev = start_revision.value.number;
1753          if (! SVN_IS_VALID_REVNUM(start_rev))
1754            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1755                                     _("Invalid revision number (%ld)"),
1756                                     start_rev);
1757        }
1758
1759      /* Get the end revision, which must be unspecified (meaning,
1760         "same as the start_rev"), HEAD, or a number (which is
1761         required to be a valid one). */
1762      if (end_revision.kind == svn_opt_revision_unspecified)
1763        {
1764          end_rev = start_rev;
1765        }
1766      else if (end_revision.kind == svn_opt_revision_head)
1767        {
1768          end_rev = SVN_INVALID_REVNUM;
1769        }
1770      else
1771        {
1772          end_rev = end_revision.value.number;
1773          if (! SVN_IS_VALID_REVNUM(end_rev))
1774            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1775                                     _("Invalid revision number (%ld)"),
1776                                     end_rev);
1777        }
1778    }
1779
1780  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1781                                        apr_array_make(pool, 1,
1782                                                       sizeof(const char *)),
1783                                        pool));
1784  if (targets->nelts != 1)
1785    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1786
1787  to_url = APR_ARRAY_IDX(targets, 0, const char *);
1788
1789  if (! svn_path_is_url(to_url))
1790    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1791                             _("Path '%s' is not a URL"), to_url);
1792
1793  baton = make_subcommand_baton(opt_baton, to_url, NULL,
1794                                start_rev, end_rev, pool);
1795  SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL,
1796                       &(baton->sync_callbacks), baton, baton->config, pool));
1797  SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
1798  SVN_ERR(with_locked(to_session, do_copy_revprops, baton, pool));
1799
1800  return SVN_NO_ERROR;
1801}
1802
1803
1804
1805/*** `svnsync info' ***/
1806
1807
1808/* SUBCOMMAND: info */
1809static svn_error_t *
1810info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
1811{
1812  svn_ra_session_t *to_session;
1813  opt_baton_t *opt_baton = b;
1814  apr_array_header_t *targets;
1815  subcommand_baton_t *baton;
1816  const char *to_url;
1817  svn_string_t *from_url, *from_uuid, *last_merged_rev;
1818
1819  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1820                                        apr_array_make(pool, 0,
1821                                                       sizeof(const char *)),
1822                                        pool));
1823  if (targets->nelts < 1)
1824    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
1825  if (targets->nelts > 1)
1826    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1827
1828  /* Get the mirror repository URL, and verify that it is URL-ish. */
1829  to_url = APR_ARRAY_IDX(targets, 0, const char *);
1830  if (! svn_path_is_url(to_url))
1831    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1832                             _("Path '%s' is not a URL"), to_url);
1833
1834  /* Open an RA session to the mirror repository URL. */
1835  baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
1836  SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL,
1837                       &(baton->sync_callbacks), baton, baton->config, pool));
1838  SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool));
1839
1840  /* Verify that the repos has been initialized for synchronization. */
1841  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
1842                          &from_url, pool));
1843  if (! from_url)
1844    return svn_error_createf
1845      (SVN_ERR_BAD_URL, NULL,
1846       _("Repository '%s' is not initialized for synchronization"), to_url);
1847
1848  /* Fetch more of the magic properties, which are the source of our info. */
1849  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
1850                          &from_uuid, pool));
1851  SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
1852                          &last_merged_rev, pool));
1853
1854  /* Print the info. */
1855  SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
1856  if (from_uuid)
1857    SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
1858                               from_uuid->data));
1859  if (last_merged_rev)
1860    SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
1861                               last_merged_rev->data));
1862  return SVN_NO_ERROR;
1863}
1864
1865
1866
1867/*** `svnsync help' ***/
1868
1869
1870/* SUBCOMMAND: help */
1871static svn_error_t *
1872help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1873{
1874  opt_baton_t *opt_baton = baton;
1875
1876  const char *header =
1877    _("general usage: svnsync SUBCOMMAND DEST_URL  [ARGS & OPTIONS ...]\n"
1878      "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
1879      "Type 'svnsync --version' to see the program version and RA modules.\n"
1880      "\n"
1881      "Available subcommands:\n");
1882
1883  const char *ra_desc_start
1884    = _("The following repository access (RA) modules are available:\n\n");
1885
1886  svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
1887                                                         pool);
1888
1889  SVN_ERR(svn_ra_print_modules(version_footer, pool));
1890
1891  SVN_ERR(svn_opt_print_help3(os, "svnsync",
1892                              opt_baton ? opt_baton->version : FALSE,
1893                              FALSE, version_footer->data, header,
1894                              svnsync_cmd_table, svnsync_options, NULL,
1895                              NULL, pool));
1896
1897  return SVN_NO_ERROR;
1898}
1899
1900
1901
1902/*** Main ***/
1903
1904int
1905main(int argc, const char *argv[])
1906{
1907  const svn_opt_subcommand_desc2_t *subcommand = NULL;
1908  apr_array_header_t *received_opts;
1909  opt_baton_t opt_baton;
1910  svn_config_t *config;
1911  apr_status_t apr_err;
1912  apr_getopt_t *os;
1913  apr_pool_t *pool;
1914  svn_error_t *err;
1915  int opt_id, i;
1916  const char *username = NULL, *source_username = NULL, *sync_username = NULL;
1917  const char *password = NULL, *source_password = NULL, *sync_password = NULL;
1918
1919  if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
1920    {
1921      return EXIT_FAILURE;
1922    }
1923
1924  err = check_lib_versions();
1925  if (err)
1926    return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
1927
1928  pool = svn_pool_create(NULL);
1929
1930  err = svn_ra_initialize(pool);
1931  if (err)
1932    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1933
1934  memset(&opt_baton, 0, sizeof(opt_baton));
1935
1936  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1937
1938  if (argc <= 1)
1939    {
1940      help_cmd(NULL, NULL, pool);
1941      svn_pool_destroy(pool);
1942      return EXIT_FAILURE;
1943    }
1944
1945  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1946  if (err)
1947    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
1948
1949  os->interleave = 1;
1950
1951  for (;;)
1952    {
1953      const char *opt_arg;
1954
1955      apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
1956      if (APR_STATUS_IS_EOF(apr_err))
1957        break;
1958      else if (apr_err)
1959        {
1960          help_cmd(NULL, NULL, pool);
1961          svn_pool_destroy(pool);
1962          return EXIT_FAILURE;
1963        }
1964
1965      APR_ARRAY_PUSH(received_opts, int) = opt_id;
1966
1967      switch (opt_id)
1968        {
1969          case svnsync_opt_non_interactive:
1970            opt_baton.non_interactive = TRUE;
1971            break;
1972
1973          case svnsync_opt_trust_server_cert:
1974            opt_baton.trust_server_cert = TRUE;
1975            break;
1976
1977          case svnsync_opt_no_auth_cache:
1978            opt_baton.no_auth_cache = TRUE;
1979            break;
1980
1981          case svnsync_opt_auth_username:
1982            username = opt_arg;
1983            break;
1984
1985          case svnsync_opt_auth_password:
1986            password = opt_arg;
1987            break;
1988
1989          case svnsync_opt_source_username:
1990            source_username = opt_arg;
1991            break;
1992
1993          case svnsync_opt_source_password:
1994            source_password = opt_arg;
1995            break;
1996
1997          case svnsync_opt_sync_username:
1998            sync_username = opt_arg;
1999            break;
2000
2001          case svnsync_opt_sync_password:
2002            sync_password = opt_arg;
2003            break;
2004
2005          case svnsync_opt_config_dir:
2006            opt_baton.config_dir = opt_arg;
2007            break;
2008
2009          case svnsync_opt_version:
2010            opt_baton.version = TRUE;
2011            break;
2012
2013          case 'q':
2014            opt_baton.quiet = TRUE;
2015            break;
2016
2017          case '?':
2018          case 'h':
2019            opt_baton.help = TRUE;
2020            break;
2021
2022          default:
2023            {
2024              help_cmd(NULL, NULL, pool);
2025              svn_pool_destroy(pool);
2026              return EXIT_FAILURE;
2027            }
2028        }
2029    }
2030
2031  if (opt_baton.help)
2032    subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
2033
2034  /* Disallow the mixing --username/password with their --source- and
2035     --sync- variants.  Treat "--username FOO" as "--source-username
2036     FOO --sync-username FOO"; ditto for "--password FOO". */
2037  if ((username || password)
2038      && (source_username || sync_username
2039          || source_password || sync_password))
2040    {
2041      err = svn_error_create
2042        (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2043         _("Cannot use --username or --password with any of "
2044           "--source-username, --source-password, --sync-username, "
2045           "or --sync-password.\n"));
2046      return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2047    }
2048  if (username)
2049    {
2050      source_username = username;
2051      sync_username = username;
2052    }
2053  if (password)
2054    {
2055      source_password = password;
2056      sync_password = password;
2057    }
2058  opt_baton.source_username = source_username;
2059  opt_baton.source_password = source_password;
2060  opt_baton.sync_username = sync_username;
2061  opt_baton.sync_password = sync_password;
2062
2063  /* --trust-server-cert can only be used with --non-interactive */
2064  if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
2065    {
2066      err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2067                             _("--trust-server-cert requires "
2068                               "--non-interactive"));
2069      return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2070    }
2071
2072  err = svn_config_ensure(opt_baton.config_dir, pool);
2073  if (err)
2074    return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
2075
2076  if (subcommand == NULL)
2077    {
2078      if (os->ind >= os->argc)
2079        {
2080          if (opt_baton.version)
2081            {
2082              /* Use the "help" subcommand to handle "--version". */
2083              static const svn_opt_subcommand_desc2_t pseudo_cmd =
2084                { "--version", help_cmd, {0}, "",
2085                  {svnsync_opt_version,  /* must accept its own option */
2086                  } };
2087
2088              subcommand = &pseudo_cmd;
2089            }
2090          else
2091            {
2092              help_cmd(NULL, NULL, pool);
2093              svn_pool_destroy(pool);
2094              return EXIT_FAILURE;
2095            }
2096        }
2097      else
2098        {
2099          const char *first_arg = os->argv[os->ind++];
2100          subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
2101                                                         first_arg);
2102          if (subcommand == NULL)
2103            {
2104              help_cmd(NULL, NULL, pool);
2105              svn_pool_destroy(pool);
2106              return EXIT_FAILURE;
2107            }
2108        }
2109    }
2110
2111  for (i = 0; i < received_opts->nelts; ++i)
2112    {
2113      opt_id = APR_ARRAY_IDX(received_opts, i, int);
2114
2115      if (opt_id == 'h' || opt_id == '?')
2116        continue;
2117
2118      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2119        {
2120          const char *optstr;
2121          const apr_getopt_option_t *badopt =
2122            svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
2123                                          pool);
2124          svn_opt_format_option(&optstr, badopt, FALSE, pool);
2125          if (subcommand->name[0] == '-')
2126            {
2127              help_cmd(NULL, NULL, pool);
2128            }
2129          else
2130            {
2131              err = svn_error_createf
2132                (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2133                 _("Subcommand '%s' doesn't accept option '%s'\n"
2134                   "Type 'svnsync help %s' for usage.\n"),
2135                 subcommand->name, optstr, subcommand->name);
2136              return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2137            }
2138        }
2139    }
2140
2141  err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
2142  if (err)
2143    return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2144
2145  config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
2146                        APR_HASH_KEY_STRING);
2147
2148  apr_signal(SIGINT, signal_handler);
2149
2150#ifdef SIGBREAK
2151  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2152  apr_signal(SIGBREAK, signal_handler);
2153#endif
2154
2155#ifdef SIGHUP
2156  apr_signal(SIGHUP, signal_handler);
2157#endif
2158
2159#ifdef SIGTERM
2160  apr_signal(SIGTERM, signal_handler);
2161#endif
2162
2163#ifdef SIGPIPE
2164  /* Disable SIGPIPE generation for the platforms that have it. */
2165  apr_signal(SIGPIPE, SIG_IGN);
2166#endif
2167
2168#ifdef SIGXFSZ
2169  /* Disable SIGXFSZ generation for the platforms that have it,
2170     otherwise working with large files when compiled against an APR
2171     that doesn't have large file support will crash the program,
2172     which is uncool. */
2173  apr_signal(SIGXFSZ, SIG_IGN);
2174#endif
2175
2176  err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton,
2177                                      opt_baton.non_interactive,
2178                                      opt_baton.source_username,
2179                                      opt_baton.source_password,
2180                                      opt_baton.config_dir,
2181                                      opt_baton.no_auth_cache,
2182                                      opt_baton.trust_server_cert,
2183                                      config,
2184                                      check_cancel, NULL,
2185                                      pool);
2186  if (! err)
2187    err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton,
2188                                        opt_baton.non_interactive,
2189                                        opt_baton.sync_username,
2190                                        opt_baton.sync_password,
2191                                        opt_baton.config_dir,
2192                                        opt_baton.no_auth_cache,
2193                                        opt_baton.trust_server_cert,
2194                                        config,
2195                                        check_cancel, NULL,
2196                                        pool);
2197  if (! err)
2198    err = (*subcommand->cmd_func)(os, &opt_baton, pool);
2199  if (err)
2200    {
2201      /* For argument-related problems, suggest using the 'help'
2202         subcommand. */
2203      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2204          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2205        {
2206          err = svn_error_quick_wrap(err,
2207                                     _("Try 'svnsync help' for more info"));
2208        }
2209
2210      return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
2211    }
2212
2213  svn_pool_destroy(pool);
2214
2215  return EXIT_SUCCESS;
2216}
Note: See TracBrowser for help on using the repository browser.