VirtualBox

source: vbox/trunk/src/apps/svnsync-vbox/main.c@ 109308

Last change on this file since 109308 was 109308, checked in by vboxsync, 9 days ago

svnsync-vbox: Add option to control censoring of author, and set up a parallel repo which gets updated as well.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 115.6 KB
Line 
1/* $Id: main.c 109308 2025-05-16 21:10:21Z vboxsync $ */
2/** @file
3 * svnsync tool. Modified by Oracle.
4 */
5/*
6 * ====================================================================
7 * Copyright (c) 2005-2006 CollabNet. All rights reserved.
8 *
9 * This software is licensed as described in the file COPYING, which
10 * you should have received as part of this distribution. The terms
11 * are also available at http://subversion.tigris.org/license-1.html.
12 * If newer versions of this license are posted there, you may use a
13 * newer version instead, at your option.
14 *
15 * This software consists of voluntary contributions made by many
16 * individuals. For exact contribution history, see the revision
17 * history and logs, available at http://subversion.tigris.org/.
18 * ====================================================================
19 */
20
21#ifdef VBOX
22#include <svn_version.h>
23#include <svn_cmdline.h>
24#include <svn_config.h>
25#include <svn_pools.h>
26#include <svn_delta.h>
27#include <svn_path.h>
28#include <svn_props.h>
29#include <svn_auth.h>
30#include <svn_opt.h>
31#include <svn_ra.h>
32
33/* Debug conditional code. */
34#ifdef DEBUG
35#define DX(x) do { x } while (0);
36#else /* !DEBUG */
37#define DX(x) do { } while (0);
38#endif /* !DEBUG */
39
40#define _(x) x
41#define N_(x) x
42
43#define SVNSYNC_PROP_START_REV SVNSYNC_PROP_PREFIX "start-rev"
44#define SVNSYNC_PROP_DEFAULT SVNSYNC_PROP_PREFIX "default"
45#define SVNSYNC_PROP_PROCESS SVNSYNC_PROP_PREFIX "process"
46#define SVNSYNC_PROP_EXTERNALS SVNSYNC_PROP_PREFIX "externals"
47#define SVNSYNC_PROP_LICENSE SVNSYNC_PROP_PREFIX "license"
48#define SVNSYNC_PROP_DEFAULT_PROCESS SVNSYNC_PROP_PREFIX "default-process"
49#define SVNSYNC_PROP_CENSOR_AUTHOR SVNSYNC_PROP_PREFIX "censor-author"
50#define SVNSYNC_PROP_REPLACE_EXTERNALS SVNSYNC_PROP_PREFIX "replace-externals"
51#define SVNSYNC_PROP_REPLACE_LICENSE SVNSYNC_PROP_PREFIX "replace-license"
52#define SVNSYNC_PROP_IGNORE_CHANGESET SVNSYNC_PROP_PREFIX "ignore-changeset"
53#define SVNSYNC_PROP_REV__FMT SVNSYNC_PROP_PREFIX "rev-%ld"
54#define SVNSYNC_PROP_XREF_SRC_REPO_REV SVNSYNC_PROP_PREFIX "xref-src-repo-rev"
55
56#define SVN_PROP_LICENSE "license"
57
58#define STRIP_LEADING_SLASH(x) (*(x) == '/' ? ((x)+1) : (x))
59
60static svn_error_t * add_file(const char *, void *, const char *, svn_revnum_t, apr_pool_t *, void **);
61static svn_error_t * add_directory(const char *, void *, const char *, svn_revnum_t, apr_pool_t *, void **);
62static svn_error_t * close_file(void *, const char *, apr_pool_t *);
63static svn_error_t * close_directory(void *, apr_pool_t *);
64static svn_error_t * change_dir_prop(void *, const char *, const svn_string_t *, apr_pool_t *);
65static svn_error_t * apply_textdelta(void *, const char *, apr_pool_t *, svn_txdelta_window_handler_t *, void **);
66static svn_error_t * change_file_prop(void *, const char *, const svn_string_t *, apr_pool_t *);
67
68/* The base for this code is version 1.5 from the subversion repository,
69 * revision 22364. The VBOX code has been updated to use the 1.6 API. */
70
71#else /* !VBOX */
72#include "svn_cmdline.h"
73#include "svn_config.h"
74#include "svn_pools.h"
75#include "svn_delta.h"
76#include "svn_path.h"
77#include "svn_props.h"
78#include "svn_auth.h"
79#include "svn_opt.h"
80#include "svn_ra.h"
81
82#include "svn_private_config.h"
83#endif /* !VBOX */
84
85#include <apr_network_io.h>
86#include <apr_signal.h>
87#include <apr_uuid.h>
88
89static svn_opt_subcommand_t initialize_cmd,
90 synchronize_cmd,
91 copy_revprops_cmd,
92 help_cmd;
93
94enum {
95 svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
96 svnsync_opt_no_auth_cache,
97 svnsync_opt_auth_username,
98 svnsync_opt_auth_password,
99 svnsync_opt_config_dir,
100#ifdef VBOX
101 svnsync_opt_censor_author,
102 svnsync_opt_start_rev,
103 svnsync_opt_default_process,
104 svnsync_opt_replace_externals,
105 svnsync_opt_replace_license,
106#endif /* VBOX */
107 svnsync_opt_version
108};
109
110#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
111 svnsync_opt_no_auth_cache, \
112 svnsync_opt_auth_username, \
113 svnsync_opt_auth_password, \
114 svnsync_opt_config_dir
115
116#ifdef VBOX
117#define SVNSYNC_OPTS_INITIALIZE SVNSYNC_OPTS_DEFAULT, \
118 svnsync_opt_censor_author, \
119 svnsync_opt_start_rev, \
120 svnsync_opt_default_process, \
121 svnsync_opt_replace_externals, \
122 svnsync_opt_replace_license
123#endif /* VBOX */
124
125#ifdef VBOX
126static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
127#else /* !VBOX */
128static const svn_opt_subcommand_desc_t svnsync_cmd_table[] =
129#endif /* !VBOX */
130 {
131 { "initialize", initialize_cmd, { "init" },
132 N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
133 "\n"
134 "Initialize a destination repository for synchronization from\n"
135 "another repository.\n"
136 "\n"
137 "The destination URL must point to the root of a repository with\n"
138 "no committed revisions. The destination repository must allow\n"
139 "revision property changes.\n"
140 "\n"
141 "You should not commit to, or make revision property changes in,\n"
142 "the destination repository by any method other than 'svnsync'.\n"
143 "In other words, the destination repository should be a read-only\n"
144 "mirror of the source repository.\n"),
145#ifdef VBOX
146 { SVNSYNC_OPTS_INITIALIZE } },
147#else /* !VBOX */
148 { SVNSYNC_OPTS_DEFAULT } },
149#endif /* !VBOX */
150 { "synchronize", synchronize_cmd, { "sync" },
151 N_("usage: svnsync synchronize DEST_URL\n"
152 "\n"
153 "Transfer all pending revisions from source to destination.\n"),
154 { SVNSYNC_OPTS_DEFAULT } },
155 { "copy-revprops", copy_revprops_cmd, { 0 },
156 N_("usage: svnsync copy-revprops DEST_URL REV\n"
157 "\n"
158 "Copy all revision properties for revision REV from source to\n"
159 "destination.\n"),
160 { SVNSYNC_OPTS_DEFAULT } },
161 { "help", help_cmd, { "?", "h" },
162 N_("usage: svnsync help [SUBCOMMAND...]\n"
163 "\n"
164 "Describe the usage of this program or its subcommands.\n"),
165 { 0 } },
166 { NULL, NULL, { 0 }, NULL, { 0 } }
167 };
168
169static const apr_getopt_option_t svnsync_options[] =
170 {
171 {"non-interactive", svnsync_opt_non_interactive, 0,
172 N_("do no interactive prompting") },
173 {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
174 N_("do not cache authentication tokens") },
175 {"username", svnsync_opt_auth_username, 1,
176 N_("specify a username ARG") },
177 {"password", svnsync_opt_auth_password, 1,
178 N_("specify a password ARG") },
179 {"config-dir", svnsync_opt_config_dir, 1,
180 N_("read user configuration files from directory ARG")},
181#ifdef VBOX
182 {"censor-author", svnsync_opt_censor_author, 0,
183 N_("ignore all revisions before ARG")},
184 {"start-rev", svnsync_opt_start_rev, 1,
185 N_("ignore all revisions before ARG")},
186 {"default-process", svnsync_opt_default_process, 1,
187 N_("set default for processing files and directories to ARG")},
188 {"replace-externals", svnsync_opt_replace_externals, 0,
189 N_("replace svn:externals properties")},
190 {"replace-license", svnsync_opt_replace_license, 0,
191 N_("replace license properties")},
192#endif /* VBOX */
193 {"version", svnsync_opt_version, 0,
194 N_("show program version information")},
195 {"help", 'h', 0,
196 N_("show help on a subcommand")},
197 {NULL, '?', 0,
198 N_("show help on a subcommand")},
199 { 0, 0, 0, 0 }
200 };
201
202typedef struct {
203 svn_auth_baton_t *auth_baton;
204 svn_boolean_t non_interactive;
205 svn_boolean_t no_auth_cache;
206 const char *auth_username;
207 const char *auth_password;
208 const char *config_dir;
209#ifdef VBOX
210 svn_boolean_t censor_author;
211 svn_revnum_t start_rev;
212 const char *default_process;
213 svn_boolean_t replace_externals;
214 svn_boolean_t replace_license;
215#endif /* VBOX */
216 apr_hash_t *config;
217 svn_boolean_t version;
218 svn_boolean_t help;
219} opt_baton_t;
220
221
222
223
224
225/*** Helper functions ***/
226
227
228/* Global record of whether the user has requested cancellation. */
229static volatile sig_atomic_t cancelled = FALSE;
230
231
232/* Callback function for apr_signal(). */
233static void
234signal_handler(int signum)
235{
236 apr_signal(signum, SIG_IGN);
237 cancelled = TRUE;
238}
239
240
241/* Cancellation callback function. */
242static svn_error_t *
243check_cancel(void *baton)
244{
245 if (cancelled)
246 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
247 else
248 return SVN_NO_ERROR;
249}
250
251
252/* Check that the version of libraries in use match what we expect. */
253static svn_error_t *
254check_lib_versions(void)
255{
256 static const svn_version_checklist_t checklist[] =
257 {
258 { "svn_subr", svn_subr_version },
259 { "svn_delta", svn_delta_version },
260 { "svn_ra", svn_ra_version },
261 { NULL, NULL }
262 };
263
264 SVN_VERSION_DEFINE(my_version);
265
266 return svn_ver_check_list(&my_version, checklist);
267}
268
269
270#ifdef VBOX
271/* Get the export properties of the file/directory in PATH, as of REVISION.
272 * Cannot be done in the change_*_props callbacks, as they are invoked too
273 * late. Need to know before adding/opening a file/directory. */
274static svn_error_t *
275get_props_sync(svn_ra_session_t *session,
276 const char *default_process,
277 svn_boolean_t parent_deflt,
278 svn_boolean_t parent_rec,
279 const char *path,
280 svn_revnum_t revision,
281 svn_boolean_t *proc,
282 svn_boolean_t *deflt,
283 svn_boolean_t *rec,
284 apr_pool_t *pool)
285{
286 apr_hash_t *props;
287 svn_string_t *value;
288 svn_node_kind_t nodekind;
289
290 SVN_ERR(svn_ra_check_path(session, path, revision, &nodekind, pool));
291 if (nodekind == svn_node_file)
292 SVN_ERR(svn_ra_get_file(session, path, revision, NULL, NULL, &props, pool));
293 else
294 SVN_ERR(svn_ra_get_dir2(session, NULL, NULL, &props, path, revision, 0, pool));
295 value = apr_hash_get(props, SVNSYNC_PROP_PROCESS, APR_HASH_KEY_STRING);
296 if (value)
297 *proc = !strcmp(value->data, "export");
298 else
299 *proc = parent_deflt;
300 if (deflt)
301 {
302 value = apr_hash_get(props, SVNSYNC_PROP_DEFAULT, APR_HASH_KEY_STRING);
303 if (value)
304 {
305 if (!strcmp(value->data, "export"))
306 {
307 *deflt = TRUE;
308 *rec = FALSE;
309 }
310 else if (!strcmp(value->data, "export-recursive"))
311 {
312 *proc = TRUE;
313 *deflt = TRUE;
314 *rec = TRUE;
315 }
316 else
317 {
318 *deflt = FALSE;
319 *rec = TRUE;
320 }
321 }
322 else
323 {
324 if (parent_rec)
325 {
326 *deflt = parent_deflt;
327 *rec = TRUE;
328 }
329 else
330 {
331 *deflt = !strcmp(default_process, "export");
332 *rec = FALSE;
333 }
334 }
335 }
336
337 return SVN_NO_ERROR;
338}
339#endif /* VBOX */
340
341
342/* Acquire a lock (of sorts) on the repository associated with the
343 * given RA SESSION.
344 */
345static svn_error_t *
346get_lock(svn_ra_session_t *session, apr_pool_t *pool)
347{
348 char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
349 svn_string_t *mylocktoken, *reposlocktoken;
350 apr_status_t apr_err;
351 apr_pool_t *subpool;
352 int i;
353
354 apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
355 if (apr_err)
356 return svn_error_wrap_apr(apr_err, _("Can't get local hostname"));
357
358 mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
359 svn_uuid_generate(pool));
360
361 subpool = svn_pool_create(pool);
362
363 for (i = 0; i < 10; ++i)
364 {
365 svn_pool_clear(subpool);
366
367 SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken,
368 subpool));
369
370 if (reposlocktoken)
371 {
372 /* Did we get it? If so, we're done, otherwise we sleep. */
373 if (strcmp(reposlocktoken->data, mylocktoken->data) == 0)
374 return SVN_NO_ERROR;
375 else
376 {
377 SVN_ERR(svn_cmdline_printf
378 (pool, _("Failed to get lock on destination "
379 "repos, currently held by '%s'\n"),
380 reposlocktoken->data));
381
382 apr_sleep(apr_time_from_sec(1));
383 }
384 }
385 else
386 {
387#ifdef VBOX
388 SVN_ERR(svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK,
389 NULL, mylocktoken, subpool));
390#else /* !VBOX */
391 SVN_ERR(svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK,
392 mylocktoken, subpool));
393#endif /* !VBOX */
394 }
395 }
396
397 return svn_error_createf(APR_EINVAL, NULL,
398 "Couldn't get lock on destination repos "
399 "after %d attempts\n", i);
400}
401
402
403typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
404 void *baton,
405 apr_pool_t *pool);
406
407
408/* Lock the repository associated with RA SESSION, then execute the
409 * given FUNC/BATON pair while holding the lock. Finally, drop the
410 * lock once it finishes.
411 */
412static svn_error_t *
413with_locked(svn_ra_session_t *session,
414 with_locked_func_t func,
415 void *baton,
416 apr_pool_t *pool)
417{
418 svn_error_t *err, *err2;
419
420 SVN_ERR(get_lock(session, pool));
421
422 err = func(session, baton, pool);
423
424#ifdef VBOX
425 err2 = svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK, NULL, NULL, pool);
426#else /* !VBOX */
427 err2 = svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, NULL, pool);
428#endif /* !VBOX */
429 if (err2 && err)
430 {
431 svn_error_clear(err2); /* XXX what to do here? */
432
433 return err;
434 }
435 else if (err2)
436 {
437 return err2;
438 }
439 else
440 {
441 return err;
442 }
443}
444
445
446/* Callback function for the RA session's open_tmp_file()
447 * requirements.
448 */
449static svn_error_t *
450open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
451{
452#ifdef VBOX
453 return svn_io_open_unique_file3(fp, NULL, NULL,
454 svn_io_file_del_on_pool_cleanup,
455 pool, pool);
456#else /* !VBOX */
457 const char *path;
458
459 SVN_ERR(svn_io_temp_dir(&path, pool));
460
461 path = svn_path_join(path, "tempfile", pool);
462
463 SVN_ERR(svn_io_open_unique_file2(fp, NULL, path, ".tmp",
464 svn_io_file_del_on_close, pool));
465
466 return SVN_NO_ERROR;
467#endif
468}
469
470
471/* Return SVN_NO_ERROR iff URL identifies the root directory of the
472 * repository associated with RA session SESS.
473 */
474static svn_error_t *
475check_if_session_is_at_repos_root(svn_ra_session_t *sess,
476 const char *url,
477 apr_pool_t *pool)
478{
479 const char *sess_root;
480
481#ifdef VBOX
482 SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
483#else /* !VBOX */
484 SVN_ERR(svn_ra_get_repos_root(sess, &sess_root, pool));
485#endif /* !VBOX */
486
487 if (strcmp(url, sess_root) == 0)
488 return SVN_NO_ERROR;
489 else
490 return svn_error_createf
491 (APR_EINVAL, NULL,
492 _("Session is rooted at '%s' but the repos root is '%s'"),
493 url, sess_root);
494}
495
496
497/* Copy all the revision properties, except for those that have the
498 * "svn:sync-" prefix, from revision REV of the repository associated
499 * with RA session FROM_SESSION, to the repository associated with RA
500 * session TO_SESSION.
501 *
502 * If SYNC is TRUE, then properties on the destination revision that
503 * do not exist on the source revision will be removed.
504 */
505static svn_error_t *
506copy_revprops(svn_ra_session_t *from_session,
507 svn_ra_session_t *to_session,
508 svn_revnum_t rev,
509#ifdef VBOX
510 svn_revnum_t rev_to,
511 svn_boolean_t censor_author,
512#endif /* VBOX */
513 svn_boolean_t sync,
514 apr_pool_t *pool)
515{
516 apr_pool_t *subpool = svn_pool_create(pool);
517 apr_hash_t *revprops, *existing_props;
518 svn_boolean_t saw_sync_props = FALSE;
519 apr_hash_index_t *hi;
520
521 if (sync)
522#ifdef VBOX
523 SVN_ERR(svn_ra_rev_proplist(to_session, rev_to, &existing_props, pool));
524#else /* !VBOX */
525 SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, pool));
526#endif /* !VBOX */
527
528 SVN_ERR(svn_ra_rev_proplist(from_session, rev, &revprops, pool));
529
530 for (hi = apr_hash_first(pool, revprops); hi; hi = apr_hash_next(hi))
531 {
532 const void *key;
533 void *val;
534
535 svn_pool_clear(subpool);
536 apr_hash_this(hi, &key, NULL, &val);
537
538 if (strncmp(key, SVNSYNC_PROP_PREFIX,
539 sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
540 saw_sync_props = TRUE;
541 else
542#ifdef VBOX
543 if ( (!censor_author || strncmp(key, SVN_PROP_REVISION_AUTHOR,
544 sizeof(SVN_PROP_REVISION_AUTHOR) - 1))
545 && ((rev_to != 1) || (rev == rev_to) || strncmp(key, SVN_PROP_REVISION_LOG,
546 sizeof(SVN_PROP_REVISION_LOG)-1)))
547 SVN_ERR(svn_ra_change_rev_prop2(to_session, rev_to, key, NULL, val, subpool));
548#else /* !VBOX */
549 SVN_ERR(svn_ra_change_rev_prop(to_session, rev, key, val, subpool));
550#endif /* !VBOX */
551
552 if (sync)
553 apr_hash_set(existing_props, key, APR_HASH_KEY_STRING, NULL);
554 }
555
556 if (sync)
557 {
558 for (hi = apr_hash_first(pool, existing_props);
559 hi;
560 hi = apr_hash_next(hi))
561 {
562 const void *name;
563
564 svn_pool_clear(subpool);
565
566 apr_hash_this(hi, &name, NULL, NULL);
567
568#ifdef VBOX
569 SVN_ERR(svn_ra_change_rev_prop2(to_session, rev_to, name, NULL, NULL,
570 subpool));
571#else /* !VBOX */
572 SVN_ERR(svn_ra_change_rev_prop(to_session, rev, name, NULL,
573 subpool));
574#endif /* !VBOX */
575 }
576 }
577
578#ifdef VBOX
579 if (saw_sync_props)
580 {
581 if (rev_to == rev)
582 SVN_ERR(svn_cmdline_printf(subpool,
583 _("Copied properties for revision %ld "
584 "(%s* properties skipped).\n"),
585 rev_to, SVNSYNC_PROP_PREFIX));
586 else
587 SVN_ERR(svn_cmdline_printf(subpool,
588 _("Copied properties for revision %ld "
589 "(%ld in source repository) "
590 "(%s* properties skipped).\n"),
591 rev_to, rev, SVNSYNC_PROP_PREFIX));
592 }
593 else
594 {
595 if (rev_to == rev)
596 SVN_ERR(svn_cmdline_printf(subpool,
597 _("Copied properties for revision %ld.\n"),
598 rev_to));
599 else
600 SVN_ERR(svn_cmdline_printf(subpool,
601 _("Copied properties for revision %ld "
602 "(%ld in source repository).\n"),
603 rev_to, rev));
604 }
605#else /* !VBOX */
606 if (saw_sync_props)
607 SVN_ERR(svn_cmdline_printf(subpool,
608 _("Copied properties for revision %ld "
609 "(%s* properties skipped).\n"),
610 rev, SVNSYNC_PROP_PREFIX));
611 else
612 SVN_ERR(svn_cmdline_printf(subpool,
613 _("Copied properties for revision %ld.\n"),
614 rev));
615#endif /* !VBOX */
616
617 svn_pool_destroy(subpool);
618
619 return SVN_NO_ERROR;
620}
621
622
623#ifdef VBOX
624
625
626/*** Initialization Editor ***/
627
628/* This editor has the job of creating the initial state for a destination
629 * repository that starts without history before a certain starting revision.
630 * Going the export/import way would lose the versioned properties. Unversioned
631 * properties are dropped, because they don't belong to the initial snapshot.
632 *
633 * It needs to create an entire tree in a single commit.
634 */
635
636
637/* InitEdit baton */
638typedef struct {
639 const svn_delta_editor_t *wrapped_editor;
640 void *wrapped_edit_baton;
641 svn_ra_session_t *from_session_prop;
642 svn_boolean_t censor_author;
643 svn_revnum_t current;
644 const char *default_process;
645 svn_boolean_t replace_externals;
646 svn_boolean_t replace_license;
647} initedit_baton_t;
648
649
650/* InitDir baton */
651typedef struct {
652 initedit_baton_t *edit_baton;
653 void *wrapped_dir_baton;
654 svn_boolean_t process_default;
655 svn_boolean_t process_recursive;
656 svn_boolean_t process;
657} initdir_baton_t;
658
659
660/* InitFile baton */
661typedef struct {
662 initedit_baton_t *edit_baton;
663 void *wrapped_file_baton;
664 svn_boolean_t process;
665} initfile_baton_t;
666
667
668/*** Editor vtable functions ***/
669
670static svn_error_t *
671init_set_target_revision(void *edit_baton,
672 svn_revnum_t target_revision,
673 apr_pool_t *pool)
674{
675 initedit_baton_t *eb = edit_baton;
676
677 DX(fprintf(stderr, "init set_target_revision %ld\n", target_revision);)
678 target_revision = 1;
679 DX(fprintf(stderr, "init override set_target_revision %ld\n", target_revision);)
680 SVN_ERR(eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
681 target_revision, pool));
682
683 return SVN_NO_ERROR;
684}
685
686static svn_error_t *
687init_open_root(void *edit_baton,
688 svn_revnum_t base_revision,
689 apr_pool_t *pool,
690 void **root_baton)
691{
692 initedit_baton_t *eb = edit_baton;
693 initdir_baton_t *db = apr_pcalloc(pool, sizeof(*db));
694
695 DX(fprintf(stderr, "init open_root %ld\n", base_revision);)
696 base_revision = 1;
697 DX(fprintf(stderr, "init override open_root %ld\n", base_revision);)
698 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process, TRUE,
699 FALSE,"", eb->current, &db->process,
700 &db->process_default, &db->process_recursive, pool));
701 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
702 if (db->process)
703 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
704 base_revision - 1, pool,
705 &db->wrapped_dir_baton));
706
707 db->edit_baton = edit_baton;
708 *root_baton = db;
709
710 return SVN_NO_ERROR;
711}
712
713static svn_error_t *
714init_add_directory(const char *path,
715 void *parent_baton,
716 const char *copyfrom_path,
717 svn_revnum_t copyfrom_rev,
718 apr_pool_t *pool,
719 void **child_baton)
720{
721 initdir_baton_t *pb = parent_baton;
722 initedit_baton_t *eb = pb->edit_baton;
723 initdir_baton_t *db = apr_pcalloc(pool, sizeof(*db));
724
725 DX(fprintf(stderr, "init add_directory %s\n", path);)
726 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
727 pb->process_default, pb->process_recursive, path,
728 eb->current, &db->process, &db->process_default,
729 &db->process_recursive, pool));
730 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
731 if (db->process && !pb->process)
732 {
733 /* Parent directory is not exported, but this directory is. Warn user,
734 * because this can lead to destination repository weirdness. */
735 SVN_ERR(svn_cmdline_printf(pool,
736 _("The parent of directory %s is not exported, "
737 "but the directory is. FIX ASAP!\n"), path));
738 db->process = FALSE;
739 }
740 if (db->process)
741 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_dir_baton,
742 NULL, SVN_IGNORED_REVNUM, pool,
743 &db->wrapped_dir_baton));
744
745 db->edit_baton = eb;
746 *child_baton = db;
747
748 return SVN_NO_ERROR;
749}
750
751static svn_error_t *
752init_close_directory(void *dir_baton,
753 apr_pool_t *pool)
754{
755 initdir_baton_t *db = dir_baton;
756 initedit_baton_t *eb = db->edit_baton;
757
758 DX(fprintf(stderr, "init close_directory\n");)
759 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
760 if (db->process)
761 SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_dir_baton, pool));
762
763 return SVN_NO_ERROR;
764}
765
766static svn_error_t *
767init_add_file(const char *path,
768 void *parent_baton,
769 const char *copyfrom_path,
770 svn_revnum_t copyfrom_rev,
771 apr_pool_t *pool,
772 void **file_baton)
773{
774 initdir_baton_t *pb = parent_baton;
775 initedit_baton_t *eb = pb->edit_baton;
776 initfile_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
777
778 DX(fprintf(stderr, "init add_file %s\n", path);)
779 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
780 pb->process_default, pb->process_recursive,
781 path, eb->current, &fb->process, NULL, NULL, pool));
782 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
783 if (fb->process && !pb->process)
784 {
785 /* Parent directory is not exported, but this file is. Warn user,
786 * because this can lead to destination repository weirdness. */
787 SVN_ERR(svn_cmdline_printf(pool,
788 _("The parent of file %s is not exported, "
789 "but the file is. FIX ASAP!\n"), path));
790 fb->process = FALSE;
791 }
792 if (fb->process)
793 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_dir_baton,
794 NULL, SVN_IGNORED_REVNUM, pool,
795 &fb->wrapped_file_baton));
796
797 fb->edit_baton = eb;
798 *file_baton = fb;
799
800 return SVN_NO_ERROR;
801}
802
803
804static svn_error_t *
805init_apply_textdelta(void *file_baton,
806 const char *base_checksum,
807 apr_pool_t *pool,
808 svn_txdelta_window_handler_t *handler,
809 void **handler_baton)
810{
811 initfile_baton_t *fb = file_baton;
812 initedit_baton_t *eb = fb->edit_baton;
813
814 DX(fprintf(stderr, "init apply_textdelta\n");)
815 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
816 if (fb->process)
817 SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton,
818 base_checksum, pool,
819 handler, handler_baton));
820 else
821 {
822 /* Must provide a window handler, there's no way of telling our caller
823 * to throw away its data as we're not interested. */
824 *handler = svn_delta_noop_window_handler;
825 *handler_baton = NULL;
826 }
827
828 return SVN_NO_ERROR;
829}
830
831static svn_error_t *
832init_close_file(void *file_baton,
833 const char *text_checksum,
834 apr_pool_t *pool)
835{
836 initfile_baton_t *fb = file_baton;
837 initedit_baton_t *eb = fb->edit_baton;
838
839 DX(fprintf(stderr, "init close_file\n");)
840 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
841 if (fb->process)
842 SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_file_baton,
843 text_checksum, pool));
844
845 return SVN_NO_ERROR;
846}
847
848static svn_error_t *
849init_change_file_prop(void *file_baton,
850 const char *name,
851 const svn_string_t *value,
852 apr_pool_t *pool)
853{
854 initfile_baton_t *fb = file_baton;
855 initedit_baton_t *eb = fb->edit_baton;
856
857 DX(fprintf(stderr, "init change_file_prop %s\n", name);)
858 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
859 if (svn_property_kind2(name) != svn_prop_regular_kind)
860 return SVN_NO_ERROR;
861 if (!strcmp(name, "cvs2svn:cvs-rev"))
862 return SVN_NO_ERROR;
863 if (eb->replace_license)
864 {
865 /* Throw away the normal license property and replace it by the value
866 * of svn:sync-license, if present. */
867 if (!strcmp(name, SVN_PROP_LICENSE))
868 return SVN_NO_ERROR;
869 if (!strcmp(name, SVNSYNC_PROP_LICENSE))
870 name = SVN_PROP_LICENSE;
871 }
872 /* Never export any svn:sync-* properties. */
873 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
874 return SVN_NO_ERROR;
875
876 if (fb->process)
877 SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton,
878 name, value, pool));
879
880 return SVN_NO_ERROR;
881}
882
883static svn_error_t *
884init_change_dir_prop(void *dir_baton,
885 const char *name,
886 const svn_string_t *value,
887 apr_pool_t *pool)
888{
889 initdir_baton_t *db = dir_baton;
890 initedit_baton_t *eb = db->edit_baton;
891
892 DX(fprintf(stderr, "init change_dir_prop %s\n", name);)
893 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
894 if (svn_property_kind2(name) != svn_prop_regular_kind)
895 return SVN_NO_ERROR;
896 if (!strcmp(name, "cvs2svn:cvs-rev"))
897 return SVN_NO_ERROR;
898 if (eb->replace_externals)
899 {
900 /* Throw away the normal externals and replace them by the value of
901 * svn:sync-externals, if present. */
902 if (!strcmp(name, SVN_PROP_EXTERNALS))
903 return SVN_NO_ERROR;
904 if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
905 name = SVN_PROP_EXTERNALS;
906 }
907 /* Never export any svn:sync-* properties. */
908 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
909 return SVN_NO_ERROR;
910
911 if (db->process)
912 SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton,
913 name, value, pool));
914
915 return SVN_NO_ERROR;
916}
917
918static svn_error_t *
919init_close_edit(void *edit_baton,
920 apr_pool_t *pool)
921{
922 initedit_baton_t *eb = edit_baton;
923
924 DX(fprintf(stderr, "init close_edit\n");)
925 SVN_ERR(eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool));
926
927 return SVN_NO_ERROR;
928}
929
930/*** Initialization Editor factory function ***/
931
932/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
933 * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
934 * revision on which the driver of this returned editor will be basing
935 * the commit. TO_URL is the URL of the root of the repository into
936 * which the commit is being made.
937 */
938static svn_error_t *
939get_init_editor(const svn_delta_editor_t *wrapped_editor,
940 void *wrapped_edit_baton,
941 svn_revnum_t start_rev,
942 svn_ra_session_t *prop_session,
943 const char *default_process,
944 svn_boolean_t censor_author,
945 svn_boolean_t replace_externals,
946 svn_boolean_t replace_license,
947 const svn_delta_editor_t **editor,
948 void **edit_baton,
949 apr_pool_t *pool)
950{
951 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
952 initedit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
953
954 tree_editor->set_target_revision = init_set_target_revision;
955 tree_editor->open_root = init_open_root;
956 tree_editor->add_directory = init_add_directory;
957 tree_editor->change_dir_prop = init_change_dir_prop;
958 tree_editor->close_directory = init_close_directory;
959 tree_editor->add_file = init_add_file;
960 tree_editor->apply_textdelta = init_apply_textdelta;
961 tree_editor->close_file = init_close_file;
962 tree_editor->change_file_prop = init_change_file_prop;
963 tree_editor->close_edit = init_close_edit;
964
965 eb->wrapped_editor = wrapped_editor;
966 eb->wrapped_edit_baton = wrapped_edit_baton;
967 eb->current = start_rev;
968 eb->default_process = default_process;
969 eb->replace_externals = replace_externals;
970 eb->replace_license = replace_license;
971 eb->from_session_prop = prop_session;
972
973 *editor = tree_editor;
974 *edit_baton = eb;
975
976 return SVN_NO_ERROR;
977}
978
979
980#endif /* VBOX */
981
982
983/*** `svnsync init' ***/
984
985/* Baton for initializing the destination repository while locked. */
986typedef struct {
987 const char *from_url;
988 const char *to_url;
989 apr_hash_t *config;
990#ifdef VBOX
991 svn_boolean_t censor_author;
992 svn_revnum_t start_rev;
993 const char *default_process;
994 svn_boolean_t replace_externals;
995 svn_boolean_t replace_license;
996#endif /* VBOX */
997 svn_ra_callbacks2_t *callbacks;
998} init_baton_t;
999
1000
1001#ifdef VBOX
1002/* Implements `svn_commit_callback2_t' interface. */
1003static svn_error_t *
1004init_commit_callback(const svn_commit_info_t *commit_info,
1005 void *baton,
1006 apr_pool_t *pool)
1007{
1008 init_baton_t *sb = baton;
1009
1010 SVN_ERR(svn_cmdline_printf(pool, _("Imported source revision %ld as revision %ld.\n"),
1011 sb->start_rev, commit_info->revision));
1012
1013 return SVN_NO_ERROR;
1014}
1015#endif /* VBOX */
1016
1017
1018/* Initialize the repository associated with RA session TO_SESSION,
1019 * using information found in baton B, while the repository is
1020 * locked. Implements `with_locked_func_t' interface.
1021 */
1022static svn_error_t *
1023do_initialize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
1024{
1025 svn_ra_session_t *from_session;
1026 init_baton_t *baton = b;
1027 svn_string_t *from_url;
1028 svn_revnum_t latest;
1029 const char *uuid;
1030#ifdef VBOX
1031 svn_string_t *start_rev_str;
1032 const char *default_process;
1033 svn_ra_session_t *from_session_prop;
1034#endif /* VBOX */
1035
1036 /* First, sanity check to see that we're copying into a brand new repos. */
1037
1038 SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
1039
1040 if (latest != 0)
1041 return svn_error_create
1042 (APR_EINVAL, NULL,
1043 _("Cannot initialize a repository with content in it"));
1044
1045 /* And check to see if anyone's run initialize on it before... We
1046 may want a --force option to override this check. */
1047
1048 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
1049 &from_url, pool));
1050
1051 if (from_url)
1052 return svn_error_createf
1053 (APR_EINVAL, NULL,
1054 _("Destination repository is already synchronizing from '%s'"),
1055 from_url->data);
1056
1057 /* Now fill in our bookkeeping info in the dest repository. */
1058
1059#ifdef VBOX
1060 SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL, baton->callbacks,
1061 baton, baton->config, pool));
1062#else /* !VBOX */
1063 SVN_ERR(svn_ra_open2(&from_session, baton->from_url, baton->callbacks,
1064 baton, baton->config, pool));
1065#endif /* !VBOX */
1066
1067 SVN_ERR(check_if_session_is_at_repos_root(from_session, baton->from_url,
1068 pool));
1069
1070#ifdef VBOX
1071 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
1072 svn_string_create(baton->from_url, pool),
1073 pool));
1074#else /* !VBOX */
1075 SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
1076 svn_string_create(baton->from_url, pool),
1077 pool));
1078#endif /* !VBOX */
1079
1080#ifdef VBOX
1081 SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
1082#else /* !VBOX */
1083 SVN_ERR(svn_ra_get_uuid(from_session, &uuid, pool));
1084#endif /* !VBOX */
1085
1086#ifdef VBOX
1087 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
1088 svn_string_create(uuid, pool), pool));
1089#else /* !VBOX */
1090 SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
1091 svn_string_create(uuid, pool), pool));
1092#endif /* !VBOX */
1093
1094#ifdef VBOX
1095 start_rev_str = svn_string_create(apr_psprintf(pool, "%ld", baton->start_rev),
1096 pool);
1097 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_START_REV, NULL,
1098 start_rev_str, pool));
1099 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, NULL,
1100 start_rev_str, pool));
1101 if (!baton->default_process)
1102 default_process = "export";
1103 else
1104 default_process = baton->default_process;
1105 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS, NULL,
1106 svn_string_create(default_process, pool),
1107 pool));
1108 if (baton->censor_author)
1109 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1110 SVNSYNC_PROP_CENSOR_AUTHOR, NULL,
1111 svn_string_create("", pool), pool));
1112 if (baton->replace_externals)
1113 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1114 SVNSYNC_PROP_REPLACE_EXTERNALS, NULL,
1115 svn_string_create("", pool), pool));
1116 if (baton->replace_license)
1117 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
1118 SVNSYNC_PROP_REPLACE_LICENSE, NULL,
1119 svn_string_create("", pool), pool));
1120#else /* !VBOX */
1121 SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
1122 svn_string_create("0", pool), pool));
1123#endif /* !VBOX */
1124
1125 /* Finally, copy all non-svnsync revprops from rev 0 of the source
1126 repos into the dest repos. */
1127
1128#ifdef VBOX
1129 SVN_ERR(copy_revprops(from_session, to_session, 0, 0, baton->censor_author, FALSE, pool));
1130#else /* !VBOX */
1131 SVN_ERR(copy_revprops(from_session, to_session, 0, FALSE, pool));
1132#endif /* !VBOX */
1133
1134 /** @todo It would be nice if we could set the dest repos UUID to be
1135 equal to the UUID of the source repos, at least optionally. That
1136 way people could check out/log/diff using a local fast mirror,
1137 but switch --relocate to the actual final repository in order to
1138 make changes... But at this time, the RA layer doesn't have a
1139 way to set a UUID. */
1140
1141#ifdef VBOX
1142 if (baton->start_rev > 0)
1143 {
1144 const svn_delta_editor_t *commit_editor;
1145 const svn_delta_editor_t *cancel_editor;
1146 const svn_delta_editor_t *init_editor;
1147 const svn_ra_reporter3_t *reporter;
1148 void *commit_baton;
1149 void *cancel_baton;
1150 void *init_baton;
1151 void *report_baton;
1152 apr_hash_t *logrevprop;
1153
1154 logrevprop = apr_hash_make(pool);
1155 apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
1156 svn_string_create("import", pool));
1157 SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor, &commit_baton,
1158 logrevprop,
1159 init_commit_callback, baton,
1160 NULL, FALSE, pool));
1161
1162 SVN_ERR(svn_ra_open4(&from_session_prop, NULL, baton->from_url, NULL,
1163 baton->callbacks, baton, baton->config, pool));
1164
1165 SVN_ERR(get_init_editor(commit_editor, commit_baton, baton->start_rev,
1166 from_session_prop, baton->default_process,
1167 baton->censor_author,
1168 baton->replace_externals, baton->replace_license,
1169 &init_editor, &init_baton, pool));
1170
1171 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
1172 init_editor, init_baton,
1173 &cancel_editor, &cancel_baton,
1174 pool));
1175
1176 /* Run it via an update reporter. */
1177 SVN_ERR(svn_ra_do_update3(from_session, &reporter, &report_baton,
1178 baton->start_rev, "", svn_depth_infinity, FALSE,
1179 FALSE, cancel_editor, cancel_baton, pool, pool));
1180 SVN_ERR(reporter->set_path(report_baton, "", baton->start_rev,
1181 svn_depth_infinity, TRUE, NULL, pool));
1182 SVN_ERR(reporter->finish_report(report_baton, pool));
1183 }
1184
1185 /* Finally, copy all non-svnsync revprops from rev start_rev of the source
1186 repos into rev 1 of the dest repos. */
1187
1188 SVN_ERR(copy_revprops(from_session, to_session, baton->start_rev, 1, baton->censor_author, FALSE, pool));
1189#endif /* VBOX */
1190
1191 return SVN_NO_ERROR;
1192}
1193
1194
1195/* SUBCOMMAND: init */
1196static svn_error_t *
1197initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
1198{
1199 svn_ra_callbacks2_t callbacks = { 0 };
1200 const char *to_url, *from_url;
1201 svn_ra_session_t *to_session;
1202 opt_baton_t *opt_baton = b;
1203 apr_array_header_t *args;
1204 init_baton_t baton;
1205
1206 SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
1207
1208 to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
1209 from_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 1, const char *), pool);
1210
1211 if (! svn_path_is_url(to_url))
1212 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1213 _("Path '%s' is not a URL"), to_url);
1214 if (! svn_path_is_url(from_url))
1215 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1216 _("Path '%s' is not a URL"), from_url);
1217
1218 baton.to_url = to_url;
1219 baton.from_url = from_url;
1220 baton.config = opt_baton->config;
1221#ifdef VBOX
1222 baton.start_rev = opt_baton->start_rev;
1223 baton.default_process = opt_baton->default_process;
1224 baton.censor_author = opt_baton->censor_author;
1225 baton.replace_externals = opt_baton->replace_externals;
1226 baton.replace_license = opt_baton->replace_license;
1227#endif /* VBOX */
1228
1229 callbacks.open_tmp_file = open_tmp_file;
1230 callbacks.auth_baton = opt_baton->auth_baton;
1231
1232 baton.callbacks = &callbacks;
1233
1234#ifdef VBOX
1235 SVN_ERR(svn_ra_open4(&to_session, NULL, baton.to_url, NULL,
1236 &callbacks, &baton, baton.config, pool));
1237#else /* !VBOX */
1238 SVN_ERR(svn_ra_open2(&to_session,
1239 baton.to_url,
1240 &callbacks,
1241 &baton,
1242 baton.config,
1243 pool));
1244#endif /* !VBOX */
1245
1246 SVN_ERR(check_if_session_is_at_repos_root(to_session, baton.to_url, pool));
1247
1248 SVN_ERR(with_locked(to_session, do_initialize, &baton, pool));
1249
1250 return SVN_NO_ERROR;
1251}
1252
1253
1254
1255
1256/*** Synchronization Editor ***/
1257
1258/* This editor has a couple of jobs.
1259 *
1260 * First, it needs to filter out the propchanges that can't be passed over
1261 * libsvn_ra.
1262 *
1263 * Second, it needs to adjust for the fact that we might not actually have
1264 * permission to see all of the data from the remote repository, which means
1265 * we could get revisions that are totally empty from our point of view.
1266 *
1267 * Third, it needs to adjust copyfrom paths, adding the root url for the
1268 * destination repository to the beginning of them.
1269 */
1270
1271
1272/* Edit baton */
1273typedef struct {
1274 const svn_delta_editor_t *wrapped_editor;
1275 void *wrapped_edit_baton;
1276 const char *to_url; /* URL we're copying into, for correct copyfrom URLs */
1277 svn_boolean_t called_open_root;
1278#ifdef VBOX
1279 svn_ra_session_t *from_session_prop;
1280 svn_ra_session_t *to_session_prop;
1281 svn_boolean_t changeset_live;
1282 svn_revnum_t start_rev;
1283 svn_revnum_t current;
1284 const char *default_process;
1285 svn_boolean_t replace_externals;
1286 svn_boolean_t replace_license;
1287#endif /* VBOX */
1288 svn_revnum_t base_revision;
1289} edit_baton_t;
1290
1291
1292/* A dual-purpose baton for files and directories. */
1293typedef struct {
1294 void *edit_baton;
1295#ifdef VBOX
1296 svn_boolean_t prev_process, process;
1297 svn_boolean_t prev_process_default, process_default;
1298 svn_boolean_t prev_process_recursive, process_recursive;
1299 svn_boolean_t ignore_everything; /* Ignore operations on this dir/file. */
1300 svn_boolean_t ignore_everything_rec; /* Recursively ignore operations on subdirs/files. */
1301#endif /* VBOX */
1302 void *wrapped_node_baton;
1303} node_baton_t;
1304
1305
1306#ifdef VBOX
1307static svn_revnum_t
1308lookup_revnum(svn_ra_session_t *to_session,
1309 svn_revnum_t revnum,
1310 apr_pool_t *pool)
1311{
1312 svn_error_t *err;
1313 svn_string_t *revprop;
1314
1315 err = svn_ra_rev_prop(to_session, 0, apr_psprintf(pool,
1316 SVNSYNC_PROP_REV__FMT,
1317 revnum),
1318 &revprop, pool);
1319 if (err || !revprop)
1320 return SVN_INVALID_REVNUM;
1321 else
1322 return SVN_STR_TO_REV(revprop->data);
1323}
1324
1325
1326/* Helper which copies file contents and properties from src to dst. */
1327static svn_error_t *
1328copy_file(const char *src_path,
1329 svn_revnum_t src_rev,
1330 const char *dst_path,
1331 void *file_baton,
1332 void *wrapped_parent_node_baton,
1333 svn_ra_session_t *from_session,
1334 apr_pool_t *pool)
1335{
1336 node_baton_t *fb = file_baton;
1337 edit_baton_t *eb = fb->edit_baton;
1338 apr_pool_t *subpool;
1339 apr_file_t *tmpfile;
1340 apr_off_t offset = 0;
1341 svn_stream_t *filestream;
1342 svn_stream_t *emptystream = svn_stream_empty(pool);
1343 svn_txdelta_stream_t *deltastream;
1344 svn_txdelta_window_t *window;
1345 svn_txdelta_window_handler_t window_handler;
1346 void *window_handler_baton;
1347 apr_hash_t *fileprops;
1348 apr_hash_index_t *hi;
1349 svn_error_t *e = NULL;
1350
1351 e = eb->wrapped_editor->add_file(dst_path, wrapped_parent_node_baton,
1352 NULL, SVN_IGNORED_REVNUM, pool,
1353 &fb->wrapped_node_baton);
1354 if (e)
1355 {
1356 svn_error_clear(e);
1357 SVN_ERR(eb->wrapped_editor->open_file(dst_path, wrapped_parent_node_baton,
1358 SVN_IGNORED_REVNUM, pool,
1359 &fb->wrapped_node_baton));
1360 }
1361
1362 subpool = svn_pool_create(pool);
1363 /* Copy over contents from src revision in source repository. */
1364 SVN_ERR(open_tmp_file(&tmpfile, NULL, subpool));
1365 filestream = svn_stream_from_aprfile2(tmpfile, FALSE, subpool);
1366 SVN_ERR(svn_ra_get_file(from_session, STRIP_LEADING_SLASH(src_path), src_rev,
1367 filestream, NULL, &fileprops, subpool));
1368 SVN_ERR(svn_io_file_seek(tmpfile, APR_SET, &offset, subpool));
1369
1370 SVN_ERR(apply_textdelta(file_baton, NULL, subpool, &window_handler,
1371 &window_handler_baton));
1372 svn_txdelta2(&deltastream, emptystream, filestream, FALSE, subpool);
1373 do
1374 {
1375 SVN_ERR(svn_txdelta_next_window(&window, deltastream, subpool));
1376 window_handler(window, window_handler_baton);
1377 } while (window);
1378
1379 SVN_ERR(svn_stream_close(filestream));
1380
1381 /* Copy over properties from src revision in source repository. */
1382 for (hi = apr_hash_first(subpool, fileprops); hi; hi = apr_hash_next(hi))
1383 {
1384 const void *key;
1385 void *val;
1386
1387 apr_hash_this(hi, &key, NULL, &val);
1388 SVN_ERR(change_file_prop(file_baton, key, val, subpool));
1389 }
1390
1391 svn_pool_clear(subpool);
1392 return SVN_NO_ERROR;
1393}
1394
1395/* Helper which copies a directory and all contents from src to dst. */
1396static svn_error_t *
1397copy_dir_rec(const char *src_path,
1398 svn_revnum_t src_rev,
1399 const char *dst_path,
1400 void *dir_baton,
1401 void *wrapped_parent_node_baton,
1402 svn_ra_session_t *from_session,
1403 apr_pool_t *pool)
1404{
1405 node_baton_t *db = dir_baton;
1406 edit_baton_t *eb = db->edit_baton;
1407 apr_pool_t *subpool;
1408 apr_hash_t *dirents, *dirprops;
1409 apr_hash_index_t *hi;
1410
1411 SVN_ERR(eb->wrapped_editor->add_directory(dst_path, wrapped_parent_node_baton,
1412 NULL, SVN_IGNORED_REVNUM, pool,
1413 &db->wrapped_node_baton));
1414
1415 subpool = svn_pool_create(pool);
1416 SVN_ERR(svn_ra_get_dir2(from_session, &dirents, NULL, &dirprops, src_path,
1417 src_rev, SVN_DIRENT_KIND, subpool));
1418
1419 /* Copy over files and directories from src revision in source repository. */
1420 for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
1421 {
1422 const void *key;
1423 void *val;
1424 svn_dirent_t *val_ent;
1425 apr_pool_t *oppool;
1426 const char *from_path, *to_path;
1427
1428 apr_hash_this(hi, &key, NULL, &val);
1429 val_ent = (svn_dirent_t *)val;
1430 oppool = svn_pool_create(subpool);
1431 from_path = svn_relpath_join(src_path, key, oppool);
1432 to_path = svn_relpath_join(dst_path, key, oppool);
1433 switch (val_ent->kind)
1434 {
1435 case svn_node_file:
1436 {
1437 void *fb;
1438 /* Need to copy it from the to_path in the src repository (revision
1439 * current), because that's where the updated (including
1440 * deltas/properties) version is. */
1441 SVN_ERR(add_file(to_path, db, from_path, src_rev, oppool, &fb));
1442 SVN_ERR(close_file(fb, NULL, oppool));
1443 break;
1444 }
1445 case svn_node_dir:
1446 {
1447 void *cdb;
1448 /* Same as above, just for the directory. */
1449 SVN_ERR(add_directory(to_path, db, from_path, src_rev, oppool, &cdb));
1450 SVN_ERR(close_directory(cdb, oppool));
1451 break;
1452 }
1453 default:
1454 return svn_error_create(APR_EINVAL, NULL, _("unexpected svn node kind"));
1455 }
1456 svn_pool_clear(oppool);
1457 }
1458
1459 /* Copy over properties from src revision in source repository. */
1460 for (hi = apr_hash_first(subpool, dirprops); hi; hi = apr_hash_next(hi))
1461 {
1462 const void *key;
1463 void *val;
1464
1465 apr_hash_this(hi, &key, NULL, &val);
1466 SVN_ERR(change_dir_prop(dir_baton, key, val, subpool));
1467 }
1468
1469 svn_pool_clear(subpool);
1470 return SVN_NO_ERROR;
1471}
1472#endif /* VBOX */
1473
1474/*** Editor vtable functions ***/
1475
1476static svn_error_t *
1477set_target_revision(void *edit_baton,
1478 svn_revnum_t target_revision,
1479 apr_pool_t *pool)
1480{
1481 edit_baton_t *eb = edit_baton;
1482#ifdef VBOX
1483 DX(fprintf(stderr, "set_target_revision %ld\n", target_revision);)
1484#endif /* VBOX */
1485 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
1486 target_revision, pool);
1487}
1488
1489static svn_error_t *
1490open_root(void *edit_baton,
1491 svn_revnum_t base_revision,
1492 apr_pool_t *pool,
1493 void **root_baton)
1494{
1495 edit_baton_t *eb = edit_baton;
1496#ifdef VBOX
1497 node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
1498
1499 DX(fprintf(stderr, "open_root\n");)
1500 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process, TRUE,
1501 FALSE, "", eb->current-1, &db->prev_process,
1502 &db->prev_process_default,
1503 &db->prev_process_recursive, pool));
1504 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1505 TRUE, FALSE, "", eb->current, &db->process,
1506 &db->process_default, &db->process_recursive, pool));
1507 DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
1508 if (db->process)
1509 {
1510 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
1511 base_revision, pool,
1512 &db->wrapped_node_baton));
1513 eb->called_open_root = TRUE;
1514 }
1515 db->edit_baton = edit_baton;
1516 *root_baton = db;
1517#else /* !VBOX */
1518 node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
1519
1520 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
1521 base_revision, pool,
1522 &dir_baton->wrapped_node_baton));
1523
1524 eb->called_open_root = TRUE;
1525 dir_baton->edit_baton = edit_baton;
1526 *root_baton = dir_baton;
1527#endif /* !VBOX */
1528
1529 return SVN_NO_ERROR;
1530}
1531
1532static svn_error_t *
1533delete_entry(const char *path,
1534 svn_revnum_t base_revision,
1535 void *parent_baton,
1536 apr_pool_t *pool)
1537{
1538 node_baton_t *pb = parent_baton;
1539 edit_baton_t *eb = pb->edit_baton;
1540#ifdef VBOX
1541 svn_boolean_t prev_process = FALSE;
1542 svn_boolean_t ignore_everything;
1543#endif /* VBOX */
1544
1545#ifdef VBOX
1546 DX(fprintf(stderr, "delete_entry %s\n", path);)
1547 /* Apply sync properties here, too. Avoid deleting items which are
1548 * not in the exported tree, taking transient files into account (can happen
1549 * e.g. if a directory is renamed and in the same changeset a file is
1550 * deleted). Very tricky business. */
1551 ignore_everything = pb->ignore_everything;
1552 if (!ignore_everything)
1553 {
1554 svn_node_kind_t nodekind;
1555 /* Verify if the entry did actually exist. Note that some files exist
1556 * only temporarily within a changeset and get deleted. So there's no
1557 * reliable way for checking their presence. So always delete and hope
1558 * that subversion optimizes out deletes for files which don't exist. */
1559 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1560 eb->current-1, &nodekind, pool));
1561 if (nodekind == svn_node_none)
1562 prev_process = TRUE;
1563 else
1564 {
1565 /* Of course it doesn't make sense to get the properties of the current
1566 * revision - it is to be deleted, so it doesn't have any properties. */
1567 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1568 pb->prev_process_default, pb->prev_process_recursive,
1569 path, eb->current-1, &prev_process, NULL, NULL, pool));
1570 }
1571 DX(fprintf(stderr, " %s\n", prev_process ? "EXPORT" : "IGNORE");)
1572 if (prev_process && !pb->process)
1573 {
1574 /* Parent directory is not exported, but this entry is. Warn user,
1575 * because this can lead to destination repository weirdness. */
1576 SVN_ERR(svn_cmdline_printf(pool,
1577 _("The parent of %s is not exported, but the file/directory (scheduled for deletion) is. FIX ASAP!\n"), path));
1578 prev_process = FALSE;
1579 }
1580 }
1581 if (prev_process && !ignore_everything)
1582 {
1583 eb->changeset_live = TRUE;
1584 /* Deliberately ignore error, it's the only safe solution. */
1585 eb->wrapped_editor->delete_entry(path, base_revision,
1586 pb->wrapped_node_baton, pool);
1587 }
1588
1589 return SVN_NO_ERROR;
1590#else /* !VBOX */
1591 return eb->wrapped_editor->delete_entry(path, base_revision,
1592 pb->wrapped_node_baton, pool);
1593#endif
1594}
1595
1596static svn_error_t *
1597add_directory(const char *path,
1598 void *parent_baton,
1599 const char *copyfrom_path,
1600 svn_revnum_t copyfrom_rev,
1601 apr_pool_t *pool,
1602 void **child_baton)
1603{
1604 node_baton_t *pb = parent_baton;
1605 edit_baton_t *eb = pb->edit_baton;
1606#ifdef VBOX
1607 node_baton_t *b = apr_pcalloc(pool, sizeof(*b));
1608 svn_revnum_t dst_rev;
1609
1610 DX(fprintf(stderr, "add_directory %s\n", path);)
1611 b->ignore_everything_rec = pb->ignore_everything_rec;
1612 b->ignore_everything = pb->ignore_everything_rec;
1613 if (!b->ignore_everything)
1614 {
1615 /* Of course it doesn't make sense to get the properties of the previous
1616 * revision - it is to be added, so it didn't have any properties. */
1617 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1618 pb->process_default, pb->process_recursive, path,
1619 eb->current, &b->process, &b->process_default,
1620 &b->process_recursive, pool));
1621 DX(fprintf(stderr, " %s\n", b->process ? "EXPORT" : "IGNORE");)
1622 if (b->process && !pb->process)
1623 {
1624 /* Parent directory is not exported, but this directory is. Warn user,
1625 * because this can lead to destination repository weirdness. */
1626 SVN_ERR(svn_cmdline_printf(pool,
1627 _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
1628 b->process = FALSE;
1629 }
1630 /* Fake previous process settings, to avoid warnings later on. */
1631 b->prev_process = b->process;
1632 b->prev_process_default = b->process_default;
1633 b->prev_process_recursive = b->process_recursive;
1634 }
1635 else
1636 b->process = FALSE;
1637 b->edit_baton = eb;
1638 if (b->process && !b->ignore_everything)
1639 {
1640 eb->changeset_live = TRUE;
1641 if (copyfrom_path)
1642 {
1643 dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
1644 if (SVN_IS_VALID_REVNUM(dst_rev))
1645 {
1646 svn_node_kind_t nodekind;
1647 /* Verify that the copyfrom source was exported to the destination
1648 * repository. */
1649 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1650 STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
1651 &nodekind, pool));
1652 if (nodekind == svn_node_none || nodekind != svn_node_dir)
1653 dst_rev = SVN_INVALID_REVNUM;
1654 else
1655 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1656 svn_path_uri_encode(copyfrom_path, pool));
1657 }
1658 }
1659 else
1660 dst_rev = copyfrom_rev;
1661
1662 if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
1663 {
1664 /* Genuinely add a new dir, referring to other revision/name if known. */
1665 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1666 copyfrom_path,
1667 dst_rev, pool,
1668 &b->wrapped_node_baton));
1669 }
1670 else
1671 {
1672 if (!SVN_IS_VALID_REVNUM(copyfrom_rev))
1673 copyfrom_rev = eb->current;
1674 /* Detect copying from a branch and in that case copy from the
1675 * destination directory in the revision currently being processed. */
1676 if (copyfrom_path[0] == '/')
1677 {
1678 copyfrom_path = path;
1679 copyfrom_rev = eb->current;
1680 }
1681 /* The dir was renamed, need to copy previous contents because we
1682 * don't know which revnum to use for destination repository. */
1683 SVN_ERR(copy_dir_rec(copyfrom_path, copyfrom_rev, path, b,
1684 pb->wrapped_node_baton, eb->from_session_prop, pool));
1685 b->ignore_everything_rec = TRUE;
1686 b->ignore_everything = TRUE;
1687 }
1688 }
1689 else
1690 {
1691 /* In this changeset there may be changes to files/dirs in this ignored
1692 * directory. Make sure we ignore them all. */
1693 b->ignore_everything_rec = TRUE;
1694 b->ignore_everything = TRUE;
1695 }
1696#else /* !VBOX */
1697 node_baton_t *b = apr_palloc(pool, sizeof(*b));
1698
1699 if (copyfrom_path)
1700 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1701 svn_path_uri_encode(copyfrom_path, pool));
1702
1703 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1704 copyfrom_path,
1705 copyfrom_rev, pool,
1706 &b->wrapped_node_baton));
1707
1708 b->edit_baton = eb;
1709#endif /* !VBOX */
1710 *child_baton = b;
1711
1712 return SVN_NO_ERROR;
1713}
1714
1715static svn_error_t *
1716open_directory(const char *path,
1717 void *parent_baton,
1718 svn_revnum_t base_revision,
1719 apr_pool_t *pool,
1720 void **child_baton)
1721{
1722 node_baton_t *pb = parent_baton;
1723 edit_baton_t *eb = pb->edit_baton;
1724#ifdef VBOX
1725 node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
1726 svn_boolean_t dir_added_this_changeset = FALSE;
1727 svn_boolean_t dir_present_in_target = FALSE;
1728
1729 DX(fprintf(stderr, "open_directory %s\n", path);)
1730 db->ignore_everything_rec = pb->ignore_everything_rec;
1731 db->ignore_everything = db->ignore_everything_rec;
1732 if (!db->ignore_everything)
1733 {
1734 svn_node_kind_t nodekind;
1735 /* Verify that the directory was exported from the source
1736 * repository. Can happen to be not there if the rename and
1737 * a change to some file in the directory is in one changeset. */
1738 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1739 eb->current-1, &nodekind, pool));
1740 dir_added_this_changeset = (nodekind != svn_node_dir);
1741 if (!dir_added_this_changeset)
1742 {
1743 svn_revnum_t dst_rev;
1744
1745 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1746 pb->prev_process_default, pb->prev_process_recursive,
1747 path, eb->current-1, &db->prev_process,
1748 &db->prev_process_default, &db->prev_process_recursive,
1749 pool));
1750 dst_rev = lookup_revnum(eb->to_session_prop, eb->current-1, pool);
1751 if (SVN_IS_VALID_REVNUM(dst_rev))
1752 {
1753 SVN_ERR(svn_ra_check_path(eb->to_session_prop, STRIP_LEADING_SLASH(path),
1754 dst_rev, &nodekind, pool));
1755 dir_present_in_target = (nodekind == svn_node_dir);
1756 }
1757 }
1758 else
1759 {
1760 dir_present_in_target = TRUE;
1761 }
1762 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1763 pb->process_default, pb->process_recursive, path,
1764 eb->current, &db->process, &db->process_default,
1765 &db->process_recursive, pool));
1766 if (dir_added_this_changeset)
1767 {
1768 db->prev_process = db->process;
1769 db->prev_process_default = db->process_default;
1770 db->prev_process_recursive = db->process_recursive;
1771 }
1772 DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
1773 if (db->process && !pb->process)
1774 {
1775 /* Parent directory is not exported, but this directory is. Warn user,
1776 * because this can lead to destination repository weirdness. */
1777 SVN_ERR(svn_cmdline_printf(pool,
1778 _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
1779 db->process = FALSE;
1780 db->ignore_everything_rec = TRUE;
1781 db->ignore_everything = TRUE;
1782 }
1783 if (db->process && db->prev_process && !dir_added_this_changeset && !dir_present_in_target)
1784 {
1785 /* Directory is supposed to be there, but actually is not. Warn user,
1786 * because this can lead to destination repository weirdness. */
1787 SVN_ERR(svn_cmdline_printf(pool,
1788 _("The directory %s is exported but not present in the target repository. Ignoring it. FIX ASAP!\n"), path));
1789 db->process = FALSE;
1790 db->ignore_everything_rec = TRUE;
1791 db->ignore_everything = TRUE;
1792 }
1793 }
1794 else
1795 db->process = FALSE;
1796 db->edit_baton = eb;
1797 if (!db->ignore_everything)
1798 {
1799 if (db->process)
1800 {
1801 if (db->prev_process)
1802 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
1803 base_revision, pool,
1804 &db->wrapped_node_baton));
1805 else
1806 {
1807 apr_hash_t *dirprops;
1808 apr_hash_index_t *hi;
1809
1810 /* Directory appears due to changes to the process settings. */
1811 eb->changeset_live = TRUE;
1812 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1813 NULL, SVN_IGNORED_REVNUM, pool,
1814 &db->wrapped_node_baton));
1815 /* Copy over properties from current revision in source repo */
1816 SVN_ERR(svn_ra_get_dir2(eb->from_session_prop, NULL, NULL, &dirprops,
1817 path, eb->current, 0, pool));
1818 for (hi = apr_hash_first(pool, dirprops); hi; hi = apr_hash_next(hi))
1819 {
1820 const void *key;
1821 void *val;
1822
1823 apr_hash_this(hi, &key, NULL, &val);
1824 SVN_ERR(change_dir_prop(db, key, val, pool));
1825 }
1826 /* Suppress change_dir_prop for this directory. Done already. */
1827 db->ignore_everything = TRUE;
1828
1829 /** @todo copy over files in this directory which were already exported
1830 * due to inconsistent export settings (e.g. directory is not exported,
1831 * but file in it is exported). */
1832 }
1833 }
1834 else
1835 {
1836 if (db->prev_process && dir_present_in_target)
1837 {
1838 /* Directory disappears due to changes to the process settings. */
1839 eb->changeset_live = TRUE;
1840 SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
1841 pb->wrapped_node_baton, pool));
1842 }
1843 db->ignore_everything_rec = TRUE;
1844 }
1845 }
1846#else /* !VBOX */
1847 node_baton_t *db = apr_palloc(pool, sizeof(*db));
1848
1849 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
1850 base_revision, pool,
1851 &db->wrapped_node_baton));
1852
1853 db->edit_baton = eb;
1854#endif /* !VBOX */
1855 *child_baton = db;
1856
1857 return SVN_NO_ERROR;
1858}
1859
1860static svn_error_t *
1861add_file(const char *path,
1862 void *parent_baton,
1863 const char *copyfrom_path,
1864 svn_revnum_t copyfrom_rev,
1865 apr_pool_t *pool,
1866 void **file_baton)
1867{
1868 node_baton_t *pb = parent_baton;
1869 edit_baton_t *eb = pb->edit_baton;
1870#ifdef VBOX
1871 node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
1872 svn_revnum_t dst_rev;
1873
1874 DX(fprintf(stderr, "add_file %s\n", path);)
1875 fb->ignore_everything_rec = pb->ignore_everything_rec;
1876 fb->ignore_everything = fb->ignore_everything_rec;
1877 if (!fb->ignore_everything)
1878 {
1879 /* Of course it doesn't make sense to get the properties of the previous
1880 * revision - it is to be added, so it didn't have any properties. */
1881 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1882 pb->process_default, pb->process_recursive, path,
1883 eb->current, &fb->process, NULL, NULL, pool));
1884 fb->process_default = FALSE;
1885 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
1886 if (fb->process && !pb->process)
1887 {
1888 /* Parent directory is not exported, but this file is. Warn user,
1889 * because this can lead to destination repository weirdness. */
1890 SVN_ERR(svn_cmdline_printf(pool,
1891 _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
1892 fb->process = FALSE;
1893 }
1894 /* Fake previous process settings, to avoid warnings later on. */
1895 fb->prev_process = fb->process;
1896 fb->prev_process_default = fb->process_default;
1897 }
1898 else
1899 fb->process = FALSE;
1900 fb->edit_baton = eb;
1901 if (fb->process && !fb->ignore_everything)
1902 {
1903 eb->changeset_live = TRUE;
1904 if (copyfrom_path)
1905 {
1906 dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
1907 if (SVN_IS_VALID_REVNUM(dst_rev))
1908 {
1909 svn_node_kind_t nodekind;
1910 /* Verify that the copyfrom source was exported to the destination
1911 * repository. */
1912 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1913 STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
1914 &nodekind, pool));
1915 if (nodekind == svn_node_none || nodekind != svn_node_file)
1916 dst_rev = SVN_INVALID_REVNUM;
1917 else
1918 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1919 svn_path_uri_encode(copyfrom_path, pool));
1920 }
1921 }
1922 else
1923 dst_rev = copyfrom_rev;
1924
1925 if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
1926 {
1927 /* Genuinely add a new file, referring to other revision/name if known. */
1928 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
1929 copyfrom_path, dst_rev,
1930 pool, &fb->wrapped_node_baton));
1931 }
1932 else
1933 {
1934 /* The file was renamed, need to copy previous contents because we
1935 * don't know which revnum to use for destination repository. */
1936 SVN_ERR(copy_file(copyfrom_path, copyfrom_rev, path, fb,
1937 pb->wrapped_node_baton, eb->from_session_prop, pool));
1938 }
1939 }
1940#else /* !VBOX */
1941 node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
1942
1943 if (copyfrom_path)
1944 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1945 svn_path_uri_encode(copyfrom_path, pool));
1946
1947 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
1948 copyfrom_path, copyfrom_rev,
1949 pool, &fb->wrapped_node_baton));
1950
1951 fb->edit_baton = eb;
1952#endif /* !VBOX */
1953 *file_baton = fb;
1954
1955 return SVN_NO_ERROR;
1956}
1957
1958static svn_error_t *
1959open_file(const char *path,
1960 void *parent_baton,
1961 svn_revnum_t base_revision,
1962 apr_pool_t *pool,
1963 void **file_baton)
1964{
1965 node_baton_t *pb = parent_baton;
1966 edit_baton_t *eb = pb->edit_baton;
1967#ifdef VBOX
1968 node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
1969 svn_boolean_t file_added_this_changeset = FALSE;
1970
1971 DX(fprintf(stderr, "open_file %s\n", path);)
1972 fb->ignore_everything_rec = pb->ignore_everything_rec;
1973 fb->ignore_everything = fb->ignore_everything_rec;
1974 if (!fb->ignore_everything)
1975 {
1976 svn_node_kind_t nodekind;
1977 /* Check whether the file was added in this changeset. If it was added
1978 * there, the export check for the previous revision would fail. */
1979 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1980 eb->current-1, &nodekind, pool));
1981 file_added_this_changeset = (nodekind != svn_node_file);
1982 if (!file_added_this_changeset)
1983 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1984 pb->prev_process_default,
1985 pb->prev_process_recursive,
1986 path, eb->current-1, &fb->prev_process,
1987 NULL, NULL, pool));
1988 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1989 pb->process_default, pb->process_recursive, path,
1990 eb->current, &fb->process, NULL, NULL, pool));
1991 if (file_added_this_changeset)
1992 fb->prev_process = FALSE;
1993 fb->prev_process_default = FALSE;
1994 fb->process_default = FALSE;
1995 DX(fprintf(stderr, " %s (prev %s)\n", fb->process ? "EXPORT" : "IGNORE", fb->prev_process ? "EXPORT" : "IGNORE");)
1996 if (fb->process && !pb->process)
1997 {
1998 /* Parent directory is not exported, but this file is. Warn user,
1999 * because this can lead to destination repository weirdness. */
2000 SVN_ERR(svn_cmdline_printf(pool,
2001 _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
2002 fb->process = FALSE;
2003 fb->ignore_everything = TRUE;
2004 }
2005 }
2006 else
2007 fb->process = FALSE;
2008 fb->edit_baton = eb;
2009 if (!fb->ignore_everything)
2010 {
2011 if (fb->process)
2012 {
2013 if (!file_added_this_changeset)
2014 {
2015 svn_node_kind_t nodekind;
2016 /* Verify that the previous source was exported to the destination
2017 * repository. */
2018 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
2019 STRIP_LEADING_SLASH(path),
2020 SVN_IGNORED_REVNUM, &nodekind, pool));
2021 if (nodekind == svn_node_none || nodekind != svn_node_file)
2022 fb->prev_process = FALSE;
2023 }
2024
2025 if (fb->prev_process)
2026 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
2027 base_revision, pool,
2028 &fb->wrapped_node_baton));
2029 else
2030 {
2031 /* File appears due to changes to the process settings. */
2032 eb->changeset_live = TRUE;
2033
2034 SVN_ERR(copy_file(path, eb->current, path, fb, pb->wrapped_node_baton,
2035 eb->from_session_prop, pool));
2036 /* Suppress change_file_prop/apply_textdelta this file. Done already. */
2037 fb->ignore_everything = TRUE;
2038 }
2039 }
2040 else
2041 {
2042 if (!file_added_this_changeset)
2043 {
2044 svn_node_kind_t nodekind;
2045 /* Verify that the previous source was exported to the destination
2046 * repository. */
2047 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
2048 STRIP_LEADING_SLASH(path),
2049 SVN_IGNORED_REVNUM, &nodekind, pool));
2050 if (nodekind == svn_node_none || nodekind != svn_node_file)
2051 fb->prev_process = FALSE;
2052 }
2053
2054 if (fb->prev_process)
2055 {
2056 /* File disappears due to changes to the process settings. */
2057 eb->changeset_live = TRUE;
2058 SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
2059 pb->wrapped_node_baton, pool));
2060 fb->ignore_everything = TRUE;
2061 }
2062 }
2063 }
2064#else /* !VBOX */
2065 node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
2066
2067 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
2068 base_revision, pool,
2069 &fb->wrapped_node_baton));
2070
2071 fb->edit_baton = eb;
2072#endif /* !VBOX */
2073 *file_baton = fb;
2074
2075 return SVN_NO_ERROR;
2076}
2077
2078static svn_error_t *
2079apply_textdelta(void *file_baton,
2080 const char *base_checksum,
2081 apr_pool_t *pool,
2082 svn_txdelta_window_handler_t *handler,
2083 void **handler_baton)
2084{
2085 node_baton_t *fb = file_baton;
2086 edit_baton_t *eb = fb->edit_baton;
2087
2088#ifdef VBOX
2089 DX(fprintf(stderr, "apply_textdelta\n");)
2090 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2091 if (fb->process && !fb->ignore_everything)
2092 {
2093 eb->changeset_live = TRUE;
2094 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2095 base_checksum, pool,
2096 handler, handler_baton);
2097 }
2098 else
2099 {
2100 /* Must provide a window handler, there's no way of telling our caller
2101 * to throw away its data as we're not interested. */
2102 *handler = svn_delta_noop_window_handler;
2103 *handler_baton = NULL;
2104 return SVN_NO_ERROR;
2105 }
2106#else /* !VBOX */
2107 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2108 base_checksum, pool,
2109 handler, handler_baton);
2110#endif /* VBOX */
2111}
2112
2113static svn_error_t *
2114close_file(void *file_baton,
2115 const char *text_checksum,
2116 apr_pool_t *pool)
2117{
2118 node_baton_t *fb = file_baton;
2119 edit_baton_t *eb = fb->edit_baton;
2120#ifdef VBOX
2121 DX(fprintf(stderr, "close_file\n");)
2122 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2123 if (!fb->process)
2124 return SVN_NO_ERROR;
2125#endif /* VBOX */
2126 return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
2127 text_checksum, pool);
2128}
2129
2130static svn_error_t *
2131absent_file(const char *path,
2132 void *file_baton,
2133 apr_pool_t *pool)
2134{
2135 node_baton_t *fb = file_baton;
2136 edit_baton_t *eb = fb->edit_baton;
2137#ifdef VBOX
2138 DX(fprintf(stderr, "absent_file\n");)
2139 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2140 if (!fb->process)
2141 return SVN_NO_ERROR;
2142#endif /* VBOX */
2143 return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
2144}
2145
2146static svn_error_t *
2147close_directory(void *dir_baton,
2148 apr_pool_t *pool)
2149{
2150 node_baton_t *db = dir_baton;
2151 edit_baton_t *eb = db->edit_baton;
2152#ifdef VBOX
2153 DX(fprintf(stderr, "close_directory\n");)
2154 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2155 if (!db->process)
2156 return SVN_NO_ERROR;
2157#endif /* VBOX */
2158 return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
2159}
2160
2161static svn_error_t *
2162absent_directory(const char *path,
2163 void *dir_baton,
2164 apr_pool_t *pool)
2165{
2166 node_baton_t *db = dir_baton;
2167 edit_baton_t *eb = db->edit_baton;
2168#ifdef VBOX
2169 DX(fprintf(stderr, "absent_directory\n");)
2170 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2171 if (!db->process)
2172 return SVN_NO_ERROR;
2173#endif /* VBOX */
2174 return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
2175 pool);
2176}
2177
2178static svn_error_t *
2179change_file_prop(void *file_baton,
2180 const char *name,
2181 const svn_string_t *value,
2182 apr_pool_t *pool)
2183{
2184 node_baton_t *fb = file_baton;
2185 edit_baton_t *eb = fb->edit_baton;
2186
2187#ifdef VBOX
2188 DX(fprintf(stderr, "change_file_prop %s\n", name);)
2189 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2190#endif /* VBOX */
2191 /* only regular properties can pass over libsvn_ra */
2192#ifdef VBOX
2193 if (svn_property_kind2(name) != svn_prop_regular_kind)
2194 return SVN_NO_ERROR;
2195 if (!strcmp(name, "cvs2svn:cvs-rev"))
2196 return SVN_NO_ERROR;
2197 if (eb->replace_license)
2198 {
2199 /* Throw away the normal license property and replace it by the value
2200 * of svn:sync-license, if present. */
2201 if (!strcmp(name, SVN_PROP_LICENSE))
2202 return SVN_NO_ERROR;
2203 if (!strcmp(name, SVNSYNC_PROP_LICENSE))
2204 name = SVN_PROP_LICENSE;
2205 }
2206 /* Never export any svn:sync-* properties. */
2207 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2208 return SVN_NO_ERROR;
2209 if (!fb->process || fb->ignore_everything)
2210 return SVN_NO_ERROR;
2211 eb->changeset_live = TRUE;
2212#else /* !VBOX */
2213 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2214 return SVN_NO_ERROR;
2215#endif /* !VBOX */
2216
2217 return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
2218 name, value, pool);
2219}
2220
2221static svn_error_t *
2222change_dir_prop(void *dir_baton,
2223 const char *name,
2224 const svn_string_t *value,
2225 apr_pool_t *pool)
2226{
2227 node_baton_t *db = dir_baton;
2228 edit_baton_t *eb = db->edit_baton;
2229
2230#ifdef VBOX
2231 DX(fprintf(stderr, "change_dir_prop %s\n", name);)
2232 DX(fprintf(stderr, " %s (ignore_everything %d)\n", db->process ? "EXPORT" : "IGNORE", db->ignore_everything);)
2233#endif /* VBOX */
2234 /* only regular properties can pass over libsvn_ra */
2235#ifdef VBOX
2236 if (svn_property_kind2(name) != svn_prop_regular_kind)
2237 return SVN_NO_ERROR;
2238 if (!strcmp(name, "cvs2svn:cvs-rev"))
2239 return SVN_NO_ERROR;
2240 if (eb->replace_externals)
2241 {
2242 /* Throw away the normal externals and replace them by the value of
2243 * svn:sync-externals, if present. */
2244 if (!strcmp(name, SVN_PROP_EXTERNALS))
2245 return SVN_NO_ERROR;
2246 if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
2247 name = SVN_PROP_EXTERNALS;
2248 }
2249 /* Never export any svn:sync-* properties. */
2250 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2251 return SVN_NO_ERROR;
2252 if (!db->process || db->ignore_everything)
2253 return SVN_NO_ERROR;
2254 eb->changeset_live = TRUE;
2255#else /* !VBOX */
2256 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2257 return SVN_NO_ERROR;
2258#endif /* !VBOX */
2259
2260 return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
2261 name, value, pool);
2262}
2263
2264static svn_error_t *
2265close_edit(void *edit_baton,
2266 apr_pool_t *pool)
2267{
2268 edit_baton_t *eb = edit_baton;
2269
2270#ifdef VBOX
2271 DX(fprintf(stderr, "close_edit\n");)
2272 /* Suppress empty commits. No need to record something in the
2273 * repository if the entire contents of a changeset is to be ignored. */
2274 if (eb->start_rev && !eb->changeset_live)
2275 {
2276 DX(fprintf(stderr, " discard empty commit\n");)
2277 SVN_ERR(eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool));
2278 SVN_ERR(svn_cmdline_printf(pool, _("Skipped revision %ld in source "
2279 "repository, empty commit.\n"),
2280 eb->current));
2281 return SVN_NO_ERROR;
2282 }
2283#endif /* VBOX */
2284
2285 /* If we haven't opened the root yet, that means we're transferring
2286 an empty revision, probably because we aren't allowed to see the
2287 contents for some reason. In any event, we need to open the root
2288 and close it again, before we can close out the edit, or the
2289 commit will fail. */
2290
2291 if (! eb->called_open_root)
2292 {
2293 void *baton;
2294#ifdef VBOX
2295 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2296 eb->current, pool,
2297 &baton));
2298#else /* !VBOX */
2299 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2300 eb->base_revision, pool,
2301 &baton));
2302#endif /* !VBOX */
2303 SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
2304 }
2305
2306 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
2307}
2308
2309/*** Editor factory function ***/
2310
2311/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
2312 * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
2313 * revision on which the driver of this returned editor will be basing
2314 * the commit. TO_URL is the URL of the root of the repository into
2315 * which the commit is being made.
2316 */
2317static svn_error_t *
2318get_sync_editor(const svn_delta_editor_t *wrapped_editor,
2319 void *wrapped_edit_baton,
2320 svn_revnum_t base_revision,
2321#ifdef VBOX
2322 svn_revnum_t start_rev,
2323 svn_revnum_t current,
2324 svn_ra_session_t *prop_session_from,
2325 svn_ra_session_t *prop_session_to,
2326 const char *default_process,
2327 svn_boolean_t replace_externals,
2328 svn_boolean_t replace_license,
2329#endif /* VBOX */
2330 const char *to_url,
2331 const svn_delta_editor_t **editor,
2332 void **edit_baton,
2333 apr_pool_t *pool)
2334{
2335 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
2336 edit_baton_t *eb = apr_palloc(pool, sizeof(*eb));
2337
2338 tree_editor->set_target_revision = set_target_revision;
2339 tree_editor->open_root = open_root;
2340 tree_editor->delete_entry = delete_entry;
2341 tree_editor->add_directory = add_directory;
2342 tree_editor->open_directory = open_directory;
2343 tree_editor->change_dir_prop = change_dir_prop;
2344 tree_editor->close_directory = close_directory;
2345 tree_editor->absent_directory = absent_directory;
2346 tree_editor->add_file = add_file;
2347 tree_editor->open_file = open_file;
2348 tree_editor->apply_textdelta = apply_textdelta;
2349 tree_editor->change_file_prop = change_file_prop;
2350 tree_editor->close_file = close_file;
2351 tree_editor->absent_file = absent_file;
2352 tree_editor->close_edit = close_edit;
2353
2354 eb->wrapped_editor = wrapped_editor;
2355 eb->wrapped_edit_baton = wrapped_edit_baton;
2356 eb->called_open_root = FALSE;
2357 eb->base_revision = base_revision;
2358#ifdef VBOX
2359 eb->changeset_live = FALSE;
2360 eb->start_rev = start_rev;
2361 eb->current = current;
2362 eb->default_process = default_process;
2363 eb->replace_externals = replace_externals;
2364 eb->replace_license = replace_license;
2365 eb->from_session_prop = prop_session_from;
2366 eb->to_session_prop = prop_session_to;
2367#endif /* VBOX */
2368 eb->to_url = to_url;
2369
2370 *editor = tree_editor;
2371 *edit_baton = eb;
2372
2373 return SVN_NO_ERROR;
2374}
2375
2376
2377
2378
2379/*** `svnsync sync' ***/
2380
2381/* Baton for synchronizing the destination repository while locked. */
2382typedef struct {
2383 apr_hash_t *config;
2384 svn_ra_callbacks2_t *callbacks;
2385 const char *to_url;
2386 svn_revnum_t committed_rev;
2387#ifdef VBOX
2388 svn_revnum_t from_rev;
2389#endif /* VBOX */
2390} sync_baton_t;
2391
2392
2393/* Implements `svn_commit_callback2_t' interface. */
2394static svn_error_t *
2395commit_callback(const svn_commit_info_t *commit_info,
2396 void *baton,
2397 apr_pool_t *pool)
2398{
2399 sync_baton_t *sb = baton;
2400
2401#ifdef VBOX
2402 if (sb->from_rev != commit_info->revision)
2403 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld (%ld in source repository).\n"),
2404 commit_info->revision, sb->from_rev));
2405 else
2406 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2407 commit_info->revision));
2408#else /* !VBOX */
2409 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2410 commit_info->revision));
2411#endif /* !VBOX */
2412
2413 sb->committed_rev = commit_info->revision;
2414
2415 return SVN_NO_ERROR;
2416}
2417
2418
2419/* Set *FROM_SESSION to an RA session associated with the source
2420 * repository of the synchronization, as determined by reading
2421 * svn:sync- properties from the destination repository (associated
2422 * with TO_SESSION). Set LAST_MERGED_REV to the value of the property
2423 * which records the most recently synchronized revision.
2424*** VBOX
2425 * Set START_REV_STR to the property which records the starting revision.
2426*** VBOX
2427 *
2428 * CALLBACKS is a vtable of RA callbacks to provide when creating
2429 * *FROM_SESSION. CONFIG is a configuration hash.
2430 */
2431static svn_error_t *
2432open_source_session(svn_ra_session_t **from_session,
2433 svn_string_t **last_merged_rev,
2434#ifdef VBOX
2435 svn_revnum_t *start_rev,
2436#endif /* VBOX */
2437 svn_ra_session_t *to_session,
2438 svn_ra_callbacks2_t *callbacks,
2439 apr_hash_t *config,
2440 void *baton,
2441 apr_pool_t *pool)
2442{
2443#ifdef VBOX
2444 svn_string_t *start_rev_str;
2445#endif /* VBOX */
2446 svn_string_t *from_url, *from_uuid;
2447 const char *uuid;
2448
2449 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2450 &from_url, pool));
2451 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
2452 &from_uuid, pool));
2453 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
2454 last_merged_rev, pool));
2455#ifdef VBOX
2456 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_START_REV,
2457 &start_rev_str, pool));
2458#endif /* VBOX */
2459
2460#ifdef VBOX
2461 if (! from_url || ! from_uuid || ! *last_merged_rev || ! start_rev_str)
2462#else /* !VBOX */
2463 if (! from_url || ! from_uuid || ! *last_merged_rev)
2464#endif /* !VBOX */
2465 return svn_error_create
2466 (APR_EINVAL, NULL, _("Destination repository has not been initialized"));
2467
2468#ifdef VBOX
2469 *start_rev = SVN_STR_TO_REV(start_rev_str->data);
2470#endif /* VBOX */
2471
2472#ifdef VBOX
2473 SVN_ERR(svn_ra_open4(from_session, NULL, from_url->data, NULL, callbacks, baton,
2474 config, pool));
2475#else /* !VBOX */
2476 SVN_ERR(svn_ra_open2(from_session, from_url->data, callbacks, baton,
2477 config, pool));
2478#endif /* !VBOX */
2479
2480 SVN_ERR(check_if_session_is_at_repos_root(*from_session, from_url->data,
2481 pool));
2482
2483 /* Ok, now sanity check the UUID of the source repository, it
2484 wouldn't be a good thing to sync from a different repository. */
2485
2486#ifdef VBOX
2487 SVN_ERR(svn_ra_get_uuid2(*from_session, &uuid, pool));
2488#else /* !VBOX */
2489 SVN_ERR(svn_ra_get_uuid(*from_session, &uuid, pool));
2490#endif /* !VBOX */
2491
2492 if (strcmp(uuid, from_uuid->data) != 0)
2493 return svn_error_createf(APR_EINVAL, NULL,
2494 _("UUID of source repository (%s) does not "
2495 "match expected UUID (%s)"),
2496 uuid, from_uuid->data);
2497
2498 return SVN_NO_ERROR;
2499}
2500
2501
2502/* Synchronize the repository associated with RA session TO_SESSION,
2503 * using information found in baton B, while the repository is
2504 * locked. Implements `with_locked_func_t' interface.
2505 */
2506static svn_error_t *
2507do_synchronize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2508{
2509 svn_string_t *last_merged_rev;
2510 svn_revnum_t from_latest, current;
2511 svn_ra_session_t *from_session;
2512 sync_baton_t *baton = b;
2513 apr_pool_t *subpool;
2514 svn_string_t *currently_copying;
2515 svn_revnum_t to_latest, copying, last_merged;
2516#ifdef VBOX
2517 svn_string_t *censor_author_str;
2518 svn_boolean_t censor_author;
2519 svn_revnum_t start_rev;
2520 svn_string_t *from_url;
2521 svn_string_t *default_process;
2522 svn_string_t *replace_externals_str;
2523 svn_boolean_t replace_externals;
2524 svn_string_t *replace_license_str;
2525 svn_boolean_t replace_license;
2526 svn_string_t *ignoreprop;
2527 svn_ra_session_t *from_session_prop;
2528 svn_ra_session_t *to_session_prop;
2529#endif /* VBOX */
2530
2531#ifdef VBOX
2532 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2533 to_session, baton->callbacks, baton->config,
2534 baton, pool));
2535 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2536 &from_url, pool));
2537 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS,
2538 &default_process, pool));
2539 if (!default_process)
2540 default_process = svn_string_create("export", pool);
2541 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CENSOR_AUTHOR,
2542 &censor_author_str, pool));
2543 censor_author = !!censor_author_str;
2544 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_EXTERNALS,
2545 &replace_externals_str, pool));
2546 replace_externals = !!replace_externals_str;
2547 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_LICENSE,
2548 &replace_license_str, pool));
2549 replace_license = !!replace_license_str;
2550 SVN_ERR(svn_ra_open4(&from_session_prop, NULL, from_url->data, NULL,
2551 baton->callbacks, baton, baton->config, pool));
2552 SVN_ERR(svn_ra_open4(&to_session_prop, NULL, baton->to_url, NULL,
2553 baton->callbacks, baton, baton->config, pool));
2554#else /* !VBOX */
2555 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2556 baton->callbacks, baton->config, baton, pool));
2557#endif /* !VBOX */
2558
2559 /* Check to see if we have revprops that still need to be copied for
2560 a prior revision we didn't finish copying. But first, check for
2561 state sanity. Remember, mirroring is not an atomic action,
2562 because revision properties are copied separately from the
2563 revision's contents.
2564
2565 So, any time that currently-copying is not set, then
2566 last-merged-rev should be the HEAD revision of the destination
2567 repository. That is, if we didn't fall over in the middle of a
2568 previous synchronization, then our destination repository should
2569 have exactly as many revisions in it as we've synchronized.
2570
2571 Alternately, if currently-copying *is* set, it must
2572 be either last-merged-rev or last-merged-rev + 1, and the HEAD
2573 revision must be equal to either last-merged-rev or
2574 currently-copying. If this is not the case, somebody has meddled
2575 with the destination without using svnsync.
2576 */
2577
2578 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
2579 &currently_copying, pool));
2580
2581#ifndef VBOX
2582 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2583#endif /* !VBOX */
2584
2585 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
2586
2587#ifdef VBOX
2588 if (start_rev)
2589 {
2590 /* Fake the destination repository revnum to be what the complete sync
2591 * code expects. TODO: this probably breaks continuing after an abort.*/
2592 to_latest = last_merged;
2593 }
2594 else
2595 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2596#endif /* VBOX */
2597
2598 if (currently_copying)
2599 {
2600 copying = SVN_STR_TO_REV(currently_copying->data);
2601
2602 if ((copying < last_merged)
2603 || (copying > (last_merged + 1))
2604 || ((to_latest != last_merged) && (to_latest != copying)))
2605 {
2606 return svn_error_createf
2607 (APR_EINVAL, NULL,
2608 _("Revision being currently copied (%ld), last merged revision "
2609 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
2610 "committed to the destination without using svnsync?"),
2611 copying, last_merged, to_latest);
2612 }
2613 else if (copying == to_latest)
2614 {
2615 if (copying > last_merged)
2616 {
2617#ifdef VBOX
2618/** @todo fix use of from/to revision numbers. */
2619 SVN_ERR(copy_revprops(from_session, to_session,
2620 to_latest, to_latest, censor_author, TRUE, pool));
2621#else /* !VBOX */
2622 SVN_ERR(copy_revprops(from_session, to_session,
2623 to_latest, TRUE, pool));
2624#endif /* !VBOX */
2625 last_merged = copying;
2626 last_merged_rev = svn_string_create
2627 (apr_psprintf(pool, "%ld", last_merged), pool);
2628 }
2629
2630 /* Now update last merged rev and drop currently changing.
2631 Note that the order here is significant, if we do them
2632 in the wrong order there are race conditions where we
2633 end up not being able to tell if there have been bogus
2634 (i.e. non-svnsync) commits to the dest repository. */
2635
2636#ifdef VBOX
2637 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2638 SVNSYNC_PROP_LAST_MERGED_REV, NULL,
2639 last_merged_rev, pool));
2640 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2641 SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
2642 NULL, pool));
2643#else /* !VBOX */
2644 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2645 SVNSYNC_PROP_LAST_MERGED_REV,
2646 last_merged_rev, pool));
2647 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2648 SVNSYNC_PROP_CURRENTLY_COPYING,
2649 NULL, pool));
2650#endif /* !VBOX */
2651 }
2652 /* If copying > to_latest, then we just fall through to
2653 attempting to copy the revision again. */
2654 }
2655 else
2656 {
2657 if (to_latest != last_merged)
2658 {
2659 return svn_error_createf
2660 (APR_EINVAL, NULL,
2661 _("Destination HEAD (%ld) is not the last merged revision (%ld); "
2662 "have you committed to the destination without using svnsync?"),
2663 to_latest, last_merged);
2664 }
2665 }
2666
2667 /* Now check to see if there are any revisions to copy. */
2668
2669 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
2670
2671 if (from_latest < atol(last_merged_rev->data))
2672 return SVN_NO_ERROR;
2673
2674 subpool = svn_pool_create(pool);
2675
2676 /* Ok, so there are new revisions, iterate over them copying them
2677 into the destination repository. */
2678
2679 for (current = atol(last_merged_rev->data) + 1;
2680 current <= from_latest;
2681 ++current)
2682 {
2683 const svn_delta_editor_t *commit_editor;
2684 const svn_delta_editor_t *cancel_editor;
2685 const svn_delta_editor_t *sync_editor;
2686 void *commit_baton;
2687 void *cancel_baton;
2688 void *sync_baton;
2689#ifdef VBOX
2690 apr_hash_t *logrevprop;
2691#endif /* VBOX */
2692
2693 svn_pool_clear(subpool);
2694
2695 /* We set this property so that if we error out for some reason
2696 we can later determine where we were in the process of
2697 merging a revision. If we had committed the change, but we
2698 hadn't finished copying the revprops we need to know that, so
2699 we can go back and finish the job before we move on.
2700
2701 NOTE: We have to set this before we start the commit editor,
2702 because ra_svn doesn't let you change rev props during a
2703 commit. */
2704#ifdef VBOX
2705 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2706 SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
2707 svn_string_createf(subpool, "%ld",
2708 current),
2709 subpool));
2710#else /* !VBOX */
2711 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2712 SVNSYNC_PROP_CURRENTLY_COPYING,
2713 svn_string_createf(subpool, "%ld",
2714 current),
2715 subpool));
2716#endif /* !VBOX */
2717
2718 /* The actual copy is just a replay hooked up to a commit. */
2719
2720#ifdef VBOX
2721 logrevprop = apr_hash_make(pool);
2722 apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
2723 svn_string_create("", pool));
2724 SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor,
2725 &commit_baton,
2726 logrevprop,
2727 commit_callback, baton,
2728 NULL, FALSE, subpool));
2729#else /* !VBOX */
2730 SVN_ERR(svn_ra_get_commit_editor2(to_session, &commit_editor,
2731 &commit_baton,
2732 "", /* empty log */
2733 commit_callback, baton,
2734 NULL, FALSE, subpool));
2735#endif /* !VBOX */
2736
2737 /* There's one catch though, the diff shows us props we can't
2738 send over the RA interface, so we need an editor that's smart
2739 enough to filter those out for us. */
2740
2741#ifdef VBOX
2742 baton->from_rev = current;
2743 baton->committed_rev = SVN_INVALID_REVNUM;
2744 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2745 start_rev, current, from_session_prop,
2746 to_session_prop, default_process->data,
2747 replace_externals, replace_license,
2748 baton->to_url, &sync_editor, &sync_baton,
2749 subpool));
2750#else /* !VBOX */
2751 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2752 baton->to_url, &sync_editor, &sync_baton,
2753 subpool));
2754#endif /* !VBOX */
2755
2756 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
2757 sync_editor, sync_baton,
2758 &cancel_editor,
2759 &cancel_baton,
2760 subpool));
2761
2762#ifdef VBOX
2763 /* If svn:sync-ignore-changeset revprop exists in changeset, skip it. */
2764 SVN_ERR(svn_ra_rev_prop(from_session, current,
2765 SVNSYNC_PROP_IGNORE_CHANGESET,
2766 &ignoreprop, subpool));
2767 if (!ignoreprop)
2768 SVN_ERR(svn_ra_replay(from_session, current, start_rev, TRUE,
2769 cancel_editor, cancel_baton, subpool));
2770#else /* !VBOX */
2771 SVN_ERR(svn_ra_replay(from_session, current, 0, TRUE,
2772 cancel_editor, cancel_baton, subpool));
2773#endif /* !VBOX */
2774
2775 SVN_ERR(cancel_editor->close_edit(cancel_baton, subpool));
2776
2777#ifdef VBOX
2778 if (!start_rev)
2779 {
2780 /* Sanity check that we actually committed the revision we meant to. */
2781 if (baton->committed_rev != current)
2782 return svn_error_createf
2783 (APR_EINVAL, NULL,
2784 _("Commit created rev %ld but should have created %ld"),
2785 baton->committed_rev, current);
2786 }
2787#else /* !VBOX */
2788 /* Sanity check that we actually committed the revision we meant to. */
2789 if (baton->committed_rev != current)
2790 return svn_error_createf
2791 (APR_EINVAL, NULL,
2792 _("Commit created rev %ld but should have created %ld"),
2793 baton->committed_rev, current);
2794#endif /* !VBOX */
2795
2796 /* Ok, we're done with the data, now we just need to do the
2797 revprops and we're all set. */
2798
2799#ifdef VBOX
2800 if (SVN_IS_VALID_REVNUM(baton->committed_rev))
2801 {
2802 SVN_ERR(copy_revprops(from_session, to_session, current,
2803 baton->committed_rev, censor_author, TRUE, subpool));
2804
2805 /* Add a revision cross-reference revprop visible to the public. */
2806 SVN_ERR(svn_ra_change_rev_prop2(to_session, baton->committed_rev,
2807 SVNSYNC_PROP_XREF_SRC_REPO_REV, NULL,
2808 svn_string_create(apr_psprintf(subpool,
2809 "%ld",
2810 current),
2811 subpool),
2812 subpool));
2813
2814 /* Add a revision cross-reference revprop for sync purposes. */
2815 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2816 apr_psprintf(subpool,
2817 SVNSYNC_PROP_REV__FMT,
2818 current), NULL,
2819 svn_string_create(apr_psprintf(subpool,
2820 "%ld",
2821 baton->committed_rev),
2822 subpool),
2823 subpool));
2824 }
2825 else
2826 {
2827 /* Add a revision cross-reference revprop for an empty commit,
2828 * referring to the previous commit (this avoids unnecessary copy_file
2829 * operation just because a source file was not modified when it
2830 * appears in the destination repository. */
2831 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, subpool));
2832 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2833 apr_psprintf(subpool,
2834 SVNSYNC_PROP_REV__FMT,
2835 current), NULL,
2836 svn_string_create(apr_psprintf(subpool,
2837 "%ld",
2838 to_latest),
2839 subpool),
2840 subpool));
2841 }
2842#else /* !VBOX */
2843 SVN_ERR(copy_revprops(from_session, to_session, current, TRUE, subpool));
2844#endif /* !VBOX */
2845
2846 /* Ok, we're done, bring the last-merged-rev property up to date. */
2847
2848#ifdef VBOX
2849 SVN_ERR(svn_ra_change_rev_prop2
2850 (to_session,
2851 0,
2852 SVNSYNC_PROP_LAST_MERGED_REV, NULL,
2853 svn_string_create(apr_psprintf(subpool, "%ld", current),
2854 subpool),
2855 subpool));
2856#else /* !VBOX */
2857 SVN_ERR(svn_ra_change_rev_prop
2858 (to_session,
2859 0,
2860 SVNSYNC_PROP_LAST_MERGED_REV,
2861 svn_string_create(apr_psprintf(subpool, "%ld", current),
2862 subpool),
2863 subpool));
2864#endif /* !VBOX */
2865
2866 /* And finally drop the currently copying prop, since we're done
2867 with this revision. */
2868
2869#ifdef VBOX
2870 SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
2871 SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
2872 NULL, subpool));
2873#else /* !VBOX */
2874 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2875 SVNSYNC_PROP_CURRENTLY_COPYING,
2876 NULL, subpool));
2877#endif /* !VBOX */
2878 }
2879
2880 return SVN_NO_ERROR;
2881}
2882
2883
2884/* SUBCOMMAND: sync */
2885static svn_error_t *
2886synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2887{
2888 svn_ra_callbacks2_t callbacks = { 0 };
2889 svn_ra_session_t *to_session;
2890 opt_baton_t *opt_baton = b;
2891 apr_array_header_t *args;
2892 sync_baton_t baton;
2893 const char *to_url;
2894
2895 SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
2896
2897 to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
2898
2899 if (! svn_path_is_url(to_url))
2900 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2901 _("Path '%s' is not a URL"), to_url);
2902
2903 callbacks.open_tmp_file = open_tmp_file;
2904 callbacks.auth_baton = opt_baton->auth_baton;
2905
2906 baton.callbacks = &callbacks;
2907 baton.config = opt_baton->config;
2908 baton.to_url = to_url;
2909
2910#ifdef VBOX
2911 SVN_ERR(svn_ra_open4(&to_session, NULL, to_url, NULL,
2912 baton.callbacks, &baton, baton.config, pool));
2913#else /* !VBOX */
2914 SVN_ERR(svn_ra_open2(&to_session,
2915 to_url,
2916 baton.callbacks,
2917 &baton,
2918 baton.config,
2919 pool));
2920#endif /* !VBOX */
2921
2922 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
2923
2924 SVN_ERR(with_locked(to_session, do_synchronize, &baton, pool));
2925
2926 return SVN_NO_ERROR;
2927}
2928
2929
2930
2931
2932/*** `svnsync copy-revprops' ***/
2933
2934
2935/* Baton for copying revision properties to the destination repository
2936 * while locked.
2937 */
2938typedef struct {
2939 apr_hash_t *config;
2940 svn_ra_callbacks2_t *callbacks;
2941 const char *to_url;
2942 svn_revnum_t rev;
2943} copy_revprops_baton_t;
2944
2945
2946/* Copy revision properties to the repository associated with RA
2947 * session TO_SESSION, using information found in baton B, while the
2948 * repository is locked. Implements `with_locked_func_t' interface.
2949 */
2950static svn_error_t *
2951do_copy_revprops(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2952{
2953 copy_revprops_baton_t *baton = b;
2954 svn_ra_session_t *from_session;
2955 svn_string_t *last_merged_rev;
2956#ifdef VBOX
2957 svn_revnum_t start_rev;
2958 svn_string_t *censor_author_str;
2959 svn_boolean_t censor_author;
2960#endif /* VBOX */
2961
2962#ifdef VBOX
2963 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2964 to_session, baton->callbacks, baton->config,
2965 baton, pool));
2966 if (start_rev)
2967 return svn_error_create
2968 (APR_EINVAL, NULL, _("Cannot copy revprops for repositories using "
2969 "the start-rev feature (unimplemented)"));
2970
2971 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CENSOR_AUTHOR,
2972 &censor_author_str, pool));
2973 censor_author = !!censor_author_str;
2974#else /* !VBOX */
2975 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2976 baton->callbacks, baton->config, baton, pool));
2977#endif /* !VBOX */
2978
2979 if (baton->rev > SVN_STR_TO_REV(last_merged_rev->data))
2980 return svn_error_create
2981 (APR_EINVAL, NULL, _("Cannot copy revprops for a revision that has not "
2982 "been synchronized yet"));
2983
2984#ifdef VBOX
2985 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, baton->rev, censor_author, FALSE, pool));
2986#else /* !VBOX */
2987 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, FALSE, pool));
2988#endif /* !VBOX */
2989
2990 return SVN_NO_ERROR;
2991}
2992
2993
2994/* SUBCOMMAND: copy-revprops */
2995static svn_error_t *
2996copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2997{
2998 svn_ra_callbacks2_t callbacks = { 0 };
2999 svn_ra_session_t *to_session;
3000 opt_baton_t *opt_baton = b;
3001 apr_array_header_t *args;
3002 copy_revprops_baton_t baton;
3003 const char *to_url;
3004 svn_revnum_t revision = SVN_INVALID_REVNUM;
3005 char *digits_end = NULL;
3006
3007 SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
3008
3009 to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
3010 revision = strtol(APR_ARRAY_IDX(args, 1, const char *), &digits_end, 10);
3011
3012 if (! svn_path_is_url(to_url))
3013 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3014 _("Path '%s' is not a URL"), to_url);
3015 if ((! SVN_IS_VALID_REVNUM(revision)) || (! digits_end) || *digits_end)
3016 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3017 _("Invalid revision number"));
3018
3019 callbacks.open_tmp_file = open_tmp_file;
3020 callbacks.auth_baton = opt_baton->auth_baton;
3021
3022 baton.callbacks = &callbacks;
3023 baton.config = opt_baton->config;
3024 baton.to_url = to_url;
3025 baton.rev = revision;
3026
3027#ifdef VBOX
3028 SVN_ERR(svn_ra_open4(&to_session, NULL, to_url, NULL,
3029 baton.callbacks, &baton, baton.config, pool));
3030#else /* !VBOX */
3031 SVN_ERR(svn_ra_open2(&to_session,
3032 to_url,
3033 baton.callbacks,
3034 &baton,
3035 baton.config,
3036 pool));
3037#endif /* !VBOX */
3038
3039 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
3040
3041 SVN_ERR(with_locked(to_session, do_copy_revprops, &baton, pool));
3042
3043 return SVN_NO_ERROR;
3044}
3045
3046
3047
3048
3049/*** `svnsync help' ***/
3050
3051
3052/* SUBCOMMAND: help */
3053static svn_error_t *
3054help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
3055{
3056 opt_baton_t *opt_baton = baton;
3057
3058 const char *header =
3059 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
3060 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
3061 "Type 'svnsync --version' to see the program version and RA modules.\n"
3062 "\n"
3063 "Available subcommands:\n");
3064
3065 const char *ra_desc_start
3066 = _("The following repository access (RA) modules are available:\n\n");
3067
3068 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
3069 pool);
3070
3071 SVN_ERR(svn_ra_print_modules(version_footer, pool));
3072
3073#ifdef VBOX
3074 SVN_ERR(svn_opt_print_help4(os, "svnsync",
3075 opt_baton ? opt_baton->version : FALSE,
3076 FALSE, FALSE, version_footer->data, header,
3077 svnsync_cmd_table, svnsync_options, NULL,
3078 NULL, pool));
3079#else /* !VBOX */
3080 SVN_ERR(svn_opt_print_help(os, "svnsync",
3081 opt_baton ? opt_baton->version : FALSE,
3082 FALSE, version_footer->data, header,
3083 svnsync_cmd_table, svnsync_options, NULL,
3084 pool));
3085#endif /* !VBOX */
3086
3087 return SVN_NO_ERROR;
3088}
3089
3090
3091
3092
3093/*** Main ***/
3094
3095int
3096main(int argc, const char *argv[])
3097{
3098#ifdef VBOX
3099 const svn_opt_subcommand_desc2_t *subcommand = NULL;
3100#else /* !VBOX */
3101 const svn_opt_subcommand_desc_t *subcommand = NULL;
3102#endif /* !VBOX */
3103 apr_array_header_t *received_opts;
3104 opt_baton_t opt_baton;
3105 svn_config_t *config;
3106 apr_status_t apr_err;
3107 apr_getopt_t *os;
3108 apr_pool_t *pool;
3109 svn_error_t *err;
3110 int opt_id, i;
3111
3112 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
3113 {
3114 return EXIT_FAILURE;
3115 }
3116
3117 err = check_lib_versions();
3118 if (err)
3119 {
3120 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3121 return EXIT_FAILURE;
3122 }
3123
3124 pool = svn_pool_create(NULL);
3125
3126 err = svn_ra_initialize(pool);
3127 if (err)
3128 {
3129 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3130 return EXIT_FAILURE;
3131 }
3132
3133 memset(&opt_baton, 0, sizeof(opt_baton));
3134
3135 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
3136
3137 if (argc <= 1)
3138 {
3139 help_cmd(NULL, NULL, pool);
3140 svn_pool_destroy(pool);
3141 return EXIT_FAILURE;
3142 }
3143
3144 {
3145 apr_status_t apr_err;
3146 apr_err = apr_getopt_init(&os, pool, argc, argv);
3147 if (apr_err)
3148 {
3149 err = svn_error_wrap_apr(apr_err, "Error initializing command line parsing");
3150 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3151 }
3152 }
3153
3154 os->interleave = 1;
3155
3156 for (;;)
3157 {
3158 const char *opt_arg;
3159
3160 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
3161 if (APR_STATUS_IS_EOF(apr_err))
3162 break;
3163 else if (apr_err)
3164 {
3165 help_cmd(NULL, NULL, pool);
3166 svn_pool_destroy(pool);
3167 return EXIT_FAILURE;
3168 }
3169
3170 APR_ARRAY_PUSH(received_opts, int) = opt_id;
3171
3172 switch (opt_id)
3173 {
3174 case svnsync_opt_non_interactive:
3175 opt_baton.non_interactive = TRUE;
3176 break;
3177
3178 case svnsync_opt_no_auth_cache:
3179 opt_baton.no_auth_cache = TRUE;
3180 break;
3181
3182 case svnsync_opt_auth_username:
3183 opt_baton.auth_username = opt_arg;
3184 break;
3185
3186 case svnsync_opt_auth_password:
3187 opt_baton.auth_password = opt_arg;
3188 break;
3189
3190 case svnsync_opt_config_dir:
3191 opt_baton.config_dir = opt_arg;
3192 break;
3193
3194#ifdef VBOX
3195 case svnsync_opt_censor_author:
3196 opt_baton.censor_author = TRUE;
3197 break;
3198
3199 case svnsync_opt_start_rev:
3200 opt_baton.start_rev = SVN_STR_TO_REV(opt_arg);
3201 break;
3202
3203 case svnsync_opt_default_process:
3204 opt_baton.default_process = opt_arg;
3205 break;
3206
3207 case svnsync_opt_replace_externals:
3208 opt_baton.replace_externals = TRUE;
3209 break;
3210
3211 case svnsync_opt_replace_license:
3212 opt_baton.replace_license = TRUE;
3213 break;
3214#endif /* VBOX */
3215
3216 case svnsync_opt_version:
3217 opt_baton.version = TRUE;
3218 break;
3219
3220 case '?':
3221 case 'h':
3222 opt_baton.help = TRUE;
3223 break;
3224
3225 default:
3226 {
3227 help_cmd(NULL, NULL, pool);
3228 svn_pool_destroy(pool);
3229 return EXIT_FAILURE;
3230 }
3231 }
3232 }
3233
3234 if (opt_baton.help)
3235#ifdef VBOX
3236 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
3237#else /* !VBOX */
3238 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table, "help");
3239#endif /* !VBOX */
3240
3241 if (subcommand == NULL)
3242 {
3243 if (os->ind >= os->argc)
3244 {
3245 if (opt_baton.version)
3246 {
3247 /* Use the "help" subcommand to handle the "--version" option. */
3248#ifdef VBOX
3249 static const svn_opt_subcommand_desc2_t pseudo_cmd =
3250#else /* !VBOX */
3251 static const svn_opt_subcommand_desc_t pseudo_cmd =
3252#endif /* !VBOX */
3253 { "--version", help_cmd, {0}, "",
3254 {svnsync_opt_version, /* must accept its own option */
3255 } };
3256
3257 subcommand = &pseudo_cmd;
3258 }
3259 else
3260 {
3261 help_cmd(NULL, NULL, pool);
3262 svn_pool_destroy(pool);
3263 return EXIT_FAILURE;
3264 }
3265 }
3266 else
3267 {
3268 const char *first_arg = os->argv[os->ind++];
3269#ifdef VBOX
3270 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
3271 first_arg);
3272#else /* !VBOX */
3273 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table,
3274 first_arg);
3275#endif /* !VBOX */
3276 if (subcommand == NULL)
3277 {
3278 help_cmd(NULL, NULL, pool);
3279 svn_pool_destroy(pool);
3280 return EXIT_FAILURE;
3281 }
3282 }
3283 }
3284
3285 for (i = 0; i < received_opts->nelts; ++i)
3286 {
3287 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3288
3289 if (opt_id == 'h' || opt_id == '?')
3290 continue;
3291
3292#ifdef VBOX
3293 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3294#else /* !VBOX */
3295 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
3296#endif /* !VBOX */
3297 {
3298 const char *optstr;
3299#ifdef VBOX
3300 const apr_getopt_option_t *badopt =
3301 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
3302 pool);
3303#else /* !VBOX */
3304 const apr_getopt_option_t *badopt =
3305 svn_opt_get_option_from_code(opt_id, svnsync_options);
3306#endif /* !VBOX */
3307 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3308 if (subcommand->name[0] == '-')
3309 help_cmd(NULL, NULL, pool);
3310 else
3311 svn_error_clear
3312 (svn_cmdline_fprintf
3313 (stderr, pool, _("subcommand '%s' doesn't accept option '%s'\n"
3314 "Type 'svnsync help %s' for usage.\n"),
3315 subcommand->name, optstr, subcommand->name));
3316 svn_pool_destroy(pool);
3317 return EXIT_FAILURE;
3318 }
3319 }
3320
3321 err = svn_config_get_config(&opt_baton.config, NULL, pool);
3322 if (err)
3323 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3324
3325 config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
3326 APR_HASH_KEY_STRING);
3327
3328 apr_signal(SIGINT, signal_handler);
3329
3330#ifdef SIGBREAK
3331 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
3332 apr_signal(SIGBREAK, signal_handler);
3333#endif
3334
3335#ifdef SIGHUP
3336 apr_signal(SIGHUP, signal_handler);
3337#endif
3338
3339#ifdef SIGTERM
3340 apr_signal(SIGTERM, signal_handler);
3341#endif
3342
3343#ifdef SIGPIPE
3344 /* Disable SIGPIPE generation for the platforms that have it. */
3345 apr_signal(SIGPIPE, SIG_IGN);
3346#endif
3347
3348#ifdef SIGXFSZ
3349 /* Disable SIGXFSZ generation for the platforms that have it,
3350 otherwise working with large files when compiled against an APR
3351 that doesn't have large file support will crash the program,
3352 which is uncool. */
3353 apr_signal(SIGXFSZ, SIG_IGN);
3354#endif
3355
3356#ifdef VBOX
3357 err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
3358 opt_baton.non_interactive,
3359 opt_baton.auth_username,
3360 opt_baton.auth_password,
3361 opt_baton.config_dir,
3362 opt_baton.no_auth_cache,
3363 1,
3364 config,
3365 check_cancel, NULL,
3366 pool);
3367 if (!err)
3368 err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
3369 opt_baton.non_interactive,
3370 opt_baton.auth_username,
3371 opt_baton.auth_password,
3372 opt_baton.config_dir,
3373 opt_baton.no_auth_cache,
3374 1,
3375 config,
3376 check_cancel, NULL,
3377 pool);
3378#else /* !VBOX */
3379 err = svn_cmdline_setup_auth_baton(&opt_baton.auth_baton,
3380 opt_baton.non_interactive,
3381 opt_baton.auth_username,
3382 opt_baton.auth_password,
3383 opt_baton.config_dir,
3384 opt_baton.no_auth_cache,
3385 config,
3386 check_cancel, NULL,
3387 pool);
3388#endif /* !VBOX */
3389
3390 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
3391 if (err)
3392 {
3393 /* For argument-related problems, suggest using the 'help'
3394 subcommand. */
3395 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3396 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3397 {
3398 err = svn_error_quick_wrap(err,
3399 _("Try 'svnsync help' for more info"));
3400 }
3401 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3402 svn_error_clear(err);
3403
3404 return EXIT_FAILURE;
3405 }
3406
3407 svn_pool_destroy(pool);
3408
3409 return EXIT_SUCCESS;
3410}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette