VirtualBox

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

Last change on this file since 56301 was 48253, checked in by vboxsync, 11 years ago

svnsync-vbox/main.c: rip out some incorrect code again, was worse than before

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

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