VirtualBox

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

Last change on this file since 32766 was 30851, checked in by vboxsync, 14 years ago

svnsync: fix unexporting files which aren't there (usually because the directory was exported later than the contents)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 109.9 KB
Line 
1/* $Id: main.c 30851 2010-07-14 18:02:23Z 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 * respository that starts witout history before a certain starting revision.
605 * Going the export/import way would lose the versioned properties. Unversioned
606 * properies 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/*** Syncronization 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
1294 SVN_ERR(eb->wrapped_editor->add_file(dst_path, wrapped_parent_node_baton,
1295 NULL, SVN_IGNORED_REVNUM, pool,
1296 &fb->wrapped_node_baton));
1297
1298 subpool = svn_pool_create(pool);
1299 /* Copy over contents from src revision in source repository. */
1300 SVN_ERR(open_tmp_file(&tmpfile, NULL, subpool));
1301 filestream = svn_stream_from_aprfile2(tmpfile, FALSE, subpool);
1302 /* svn_ra_get_file() insists on getting a file path without leading /,
1303 * and there is a leading / in our input parameter when copying from a
1304 * different branch. The assertion is annoying, as continuing would work. */
1305 if (src_path[0] == '/')
1306 src_path++;
1307 SVN_ERR(svn_ra_get_file(from_session, src_path, src_rev, filestream, NULL,
1308 &fileprops, subpool));
1309 SVN_ERR(svn_io_file_seek(tmpfile, APR_SET, &offset, subpool));
1310
1311 SVN_ERR(apply_textdelta(file_baton, NULL, subpool, &window_handler,
1312 &window_handler_baton));
1313 svn_txdelta(&deltastream, emptystream, filestream, subpool);
1314 do
1315 {
1316 SVN_ERR(svn_txdelta_next_window(&window, deltastream, subpool));
1317 window_handler(window, window_handler_baton);
1318 } while (window);
1319
1320 SVN_ERR(svn_stream_close(filestream));
1321
1322 /* Copy over properties from src revision in source repository. */
1323 for (hi = apr_hash_first(subpool, fileprops); hi; hi = apr_hash_next(hi))
1324 {
1325 const void *key;
1326 void *val;
1327
1328 apr_hash_this(hi, &key, NULL, &val);
1329 SVN_ERR(change_file_prop(file_baton, key, val, subpool));
1330 }
1331
1332 svn_pool_clear(subpool);
1333 return SVN_NO_ERROR;
1334}
1335
1336/* Helper which copies a directory and all contents from src to dst. */
1337static svn_error_t *
1338copy_dir_rec(const char *src_path,
1339 svn_revnum_t src_rev,
1340 const char *dst_path,
1341 void *dir_baton,
1342 void *wrapped_parent_node_baton,
1343 svn_ra_session_t *from_session,
1344 apr_pool_t *pool)
1345{
1346 node_baton_t *db = dir_baton;
1347 edit_baton_t *eb = db->edit_baton;
1348 apr_pool_t *subpool;
1349 apr_hash_t *dirents, *dirprops;
1350 apr_hash_index_t *hi;
1351
1352 SVN_ERR(eb->wrapped_editor->add_directory(dst_path, wrapped_parent_node_baton,
1353 NULL, SVN_IGNORED_REVNUM, pool,
1354 &db->wrapped_node_baton));
1355
1356 subpool = svn_pool_create(pool);
1357 SVN_ERR(svn_ra_get_dir2(from_session, &dirents, NULL, &dirprops, src_path,
1358 src_rev, SVN_DIRENT_KIND, subpool));
1359
1360 /* Copy over files and directories from src revision in source repository. */
1361 for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
1362 {
1363 const void *key;
1364 void *val;
1365 svn_dirent_t *val_ent;
1366 apr_pool_t *oppool;
1367 const char *from_path, *to_path;
1368
1369 apr_hash_this(hi, &key, NULL, &val);
1370 val_ent = (svn_dirent_t *)val;
1371 oppool = svn_pool_create(subpool);
1372 from_path = svn_path_join(src_path, key, oppool);
1373 to_path = svn_path_join(dst_path, key, oppool);
1374 switch (val_ent->kind)
1375 {
1376 case svn_node_file:
1377 {
1378 void *fb;
1379 /* Need to copy it from the to_path in the src repository (revision
1380 * current), because that's where the updated (including
1381 * deltas/properties) version is. */
1382 SVN_ERR(add_file(to_path, db, from_path, src_rev, oppool, &fb));
1383 SVN_ERR(close_file(fb, NULL, oppool));
1384 break;
1385 }
1386 case svn_node_dir:
1387 {
1388 void *cdb;
1389 /* Same as above, just for the directory. */
1390 SVN_ERR(add_directory(to_path, db, from_path, src_rev, oppool, &cdb));
1391 SVN_ERR(close_directory(cdb, oppool));
1392 break;
1393 }
1394 default:
1395 return svn_error_create(APR_EINVAL, NULL, _("unexpected svn node kind"));
1396 }
1397 svn_pool_clear(oppool);
1398 }
1399
1400 /* Copy over properties from src revision in source repository. */
1401 for (hi = apr_hash_first(subpool, dirprops); hi; hi = apr_hash_next(hi))
1402 {
1403 const void *key;
1404 void *val;
1405
1406 apr_hash_this(hi, &key, NULL, &val);
1407 SVN_ERR(change_dir_prop(dir_baton, key, val, subpool));
1408 }
1409
1410 svn_pool_clear(subpool);
1411 return SVN_NO_ERROR;
1412}
1413
1414/*** Editor vtable functions ***/
1415
1416static svn_error_t *
1417set_target_revision(void *edit_baton,
1418 svn_revnum_t target_revision,
1419 apr_pool_t *pool)
1420{
1421 edit_baton_t *eb = edit_baton;
1422#ifdef VBOX
1423 DX(fprintf(stderr, "set_target_revision %ld\n", target_revision);)
1424#endif /* VBOX */
1425 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
1426 target_revision, pool);
1427}
1428
1429static svn_error_t *
1430open_root(void *edit_baton,
1431 svn_revnum_t base_revision,
1432 apr_pool_t *pool,
1433 void **root_baton)
1434{
1435 edit_baton_t *eb = edit_baton;
1436#ifdef VBOX
1437 node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
1438
1439 DX(fprintf(stderr, "open_root\n");)
1440 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process, TRUE,
1441 FALSE, "", eb->current-1, &db->prev_process,
1442 &db->prev_process_default,
1443 &db->prev_process_recursive, pool));
1444 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1445 TRUE, FALSE, "", eb->current, &db->process,
1446 &db->process_default, &db->process_recursive, pool));
1447 DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
1448 if (db->process)
1449 {
1450 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
1451 base_revision, pool,
1452 &db->wrapped_node_baton));
1453 eb->called_open_root = TRUE;
1454 }
1455 db->edit_baton = edit_baton;
1456 *root_baton = db;
1457#else /* !VBOX */
1458 node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
1459
1460 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
1461 base_revision, pool,
1462 &dir_baton->wrapped_node_baton));
1463
1464 eb->called_open_root = TRUE;
1465 dir_baton->edit_baton = edit_baton;
1466 *root_baton = dir_baton;
1467#endif /* !VBOX */
1468
1469 return SVN_NO_ERROR;
1470}
1471
1472static svn_error_t *
1473delete_entry(const char *path,
1474 svn_revnum_t base_revision,
1475 void *parent_baton,
1476 apr_pool_t *pool)
1477{
1478 node_baton_t *pb = parent_baton;
1479 edit_baton_t *eb = pb->edit_baton;
1480#ifdef VBOX
1481 svn_boolean_t prev_process;
1482 svn_boolean_t ignore_everything;
1483#endif /* VBOX */
1484
1485#ifdef VBOX
1486 DX(fprintf(stderr, "delete_entry %s\n", path);)
1487 /* Apply sync properties here, too. Avoid deleting items which are
1488 * not in the exported tree, taking transient files into account (can happen
1489 * e.g. if a directory is renamed and in the same changeset a file is
1490 * deleted). Very tricky business. */
1491 ignore_everything = pb->ignore_everything;
1492 if (!ignore_everything)
1493 {
1494 svn_node_kind_t nodekind;
1495 /* Verify if the entry did actually exist. Note that some files exist
1496 * only temporarily within a changeset and get deleted. So there's no
1497 * reliable way for checking their presence. So always delete and hope
1498 * that subversion optimizes out deletes for files which don't exist. */
1499 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1500 eb->current-1, &nodekind, pool));
1501 if (nodekind == svn_node_none)
1502 prev_process = TRUE;
1503 else
1504 {
1505 /* Of course it doesn't make sense to get the properties of the current
1506 * revision - it is to be deleted, so it doesn't have any properties. */
1507 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1508 pb->prev_process_default, pb->prev_process_recursive,
1509 path, eb->current-1, &prev_process, NULL, NULL, pool));
1510 }
1511 DX(fprintf(stderr, " %s\n", prev_process ? "EXPORT" : "IGNORE");)
1512 if (prev_process && !pb->process)
1513 {
1514 /* Parent directory is not exported, but this entry is. Warn user,
1515 * because this can lead to destination repository weirdness. */
1516 SVN_ERR(svn_cmdline_printf(pool,
1517 _("The parent of %s is not exported, but the file/directory (scheduled for deletion) is. FIX ASAP!\n"), path));
1518 prev_process = FALSE;
1519 }
1520 }
1521 if (prev_process && !ignore_everything)
1522 {
1523 eb->changeset_live = TRUE;
1524 SVN_ERR(eb->wrapped_editor->delete_entry(path, base_revision,
1525 pb->wrapped_node_baton, pool));
1526 }
1527
1528 return SVN_NO_ERROR;
1529#else /* !VBOX */
1530 return eb->wrapped_editor->delete_entry(path, base_revision,
1531 pb->wrapped_node_baton, pool);
1532#endif
1533}
1534
1535static svn_error_t *
1536add_directory(const char *path,
1537 void *parent_baton,
1538 const char *copyfrom_path,
1539 svn_revnum_t copyfrom_rev,
1540 apr_pool_t *pool,
1541 void **child_baton)
1542{
1543 node_baton_t *pb = parent_baton;
1544 edit_baton_t *eb = pb->edit_baton;
1545#ifdef VBOX
1546 node_baton_t *b = apr_pcalloc(pool, sizeof(*b));
1547 svn_revnum_t dst_rev;
1548
1549 DX(fprintf(stderr, "add_directory %s\n", path);)
1550 b->ignore_everything_rec = pb->ignore_everything_rec;
1551 b->ignore_everything = pb->ignore_everything_rec;
1552 if (!b->ignore_everything)
1553 {
1554 /* Of course it doesn't make sense to get the properties of the previous
1555 * revision - it is to be added, so it didn't have any properties. */
1556 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1557 pb->process_default, pb->process_recursive, path,
1558 eb->current, &b->process, &b->process_default,
1559 &b->process_recursive, pool));
1560 DX(fprintf(stderr, " %s\n", b->process ? "EXPORT" : "IGNORE");)
1561 if (b->process && !pb->process)
1562 {
1563 /* Parent directory is not exported, but this directory is. Warn user,
1564 * because this can lead to destination repository weirdness. */
1565 SVN_ERR(svn_cmdline_printf(pool,
1566 _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
1567 b->process = FALSE;
1568 }
1569 /* Fake previous process settings, to avoid warnings later on. */
1570 b->prev_process = b->process;
1571 b->prev_process_default = b->process_default;
1572 b->prev_process_recursive = b->process_recursive;
1573 }
1574 else
1575 b->process = FALSE;
1576 b->edit_baton = eb;
1577 if (b->process && !b->ignore_everything)
1578 {
1579 eb->changeset_live = TRUE;
1580 if (copyfrom_path)
1581 {
1582 dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
1583 if (SVN_IS_VALID_REVNUM(dst_rev))
1584 {
1585 svn_node_kind_t nodekind;
1586 /* Verify that the copyfrom source was exported to the destination
1587 * repository. */
1588 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1589 STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
1590 &nodekind, pool));
1591 if (nodekind == svn_node_none || nodekind != svn_node_dir)
1592 dst_rev = SVN_INVALID_REVNUM;
1593 else
1594 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1595 svn_path_uri_encode(copyfrom_path, pool));
1596 }
1597 }
1598 else
1599 dst_rev = copyfrom_rev;
1600
1601 if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
1602 {
1603 /* Genuinely add a new dir, referring to other revision/name if known. */
1604 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1605 copyfrom_path,
1606 dst_rev, pool,
1607 &b->wrapped_node_baton));
1608 }
1609 else
1610 {
1611 if (!SVN_IS_VALID_REVNUM(copyfrom_rev))
1612 copyfrom_rev = eb->current;
1613 /* Detect copying from a branch and in that case copy from the
1614 * destination directory in the revision currently being processed. */
1615 if (copyfrom_path[0] == '/')
1616 {
1617 copyfrom_path = path;
1618 copyfrom_rev = eb->current;
1619 }
1620 /* The dir was renamed, need to copy previous contents because we
1621 * don't know which revnum to use for destination repository. */
1622 SVN_ERR(copy_dir_rec(copyfrom_path, copyfrom_rev, path, b,
1623 pb->wrapped_node_baton, eb->from_session_prop, pool));
1624 b->ignore_everything_rec = TRUE;
1625 b->ignore_everything = TRUE;
1626 }
1627 }
1628 else
1629 {
1630 /* In this changeset there may be changes to files/dirs in this ignored
1631 * directory. Make sure we ignore them all. */
1632 b->ignore_everything_rec = TRUE;
1633 b->ignore_everything = TRUE;
1634 }
1635#else /* !VBOX */
1636 node_baton_t *b = apr_palloc(pool, sizeof(*b));
1637
1638 if (copyfrom_path)
1639 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1640 svn_path_uri_encode(copyfrom_path, pool));
1641
1642 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1643 copyfrom_path,
1644 copyfrom_rev, pool,
1645 &b->wrapped_node_baton));
1646
1647 b->edit_baton = eb;
1648#endif /* !VBOX */
1649 *child_baton = b;
1650
1651 return SVN_NO_ERROR;
1652}
1653
1654static svn_error_t *
1655open_directory(const char *path,
1656 void *parent_baton,
1657 svn_revnum_t base_revision,
1658 apr_pool_t *pool,
1659 void **child_baton)
1660{
1661 node_baton_t *pb = parent_baton;
1662 edit_baton_t *eb = pb->edit_baton;
1663#ifdef VBOX
1664 node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
1665 svn_boolean_t dir_added_this_changeset = FALSE;
1666 svn_boolean_t dir_present_in_target = TRUE;
1667
1668 DX(fprintf(stderr, "open_directory %s\n", path);)
1669 db->ignore_everything_rec = pb->ignore_everything_rec;
1670 db->ignore_everything = db->ignore_everything_rec;
1671 if (!db->ignore_everything)
1672 {
1673 svn_node_kind_t nodekind;
1674 /* Verify that the directory was exported from the source
1675 * repository. Can happen to be not there if the rename and
1676 * a change to some file in the directory is in one changeset. */
1677 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1678 eb->current-1,
1679 &nodekind, pool));
1680 dir_added_this_changeset = (nodekind != svn_node_dir);
1681 if (!dir_added_this_changeset)
1682 {
1683 svn_revnum_t dst_rev;
1684
1685 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1686 pb->prev_process_default, pb->prev_process_recursive,
1687 path, eb->current-1, &db->prev_process,
1688 &db->prev_process_default, &db->prev_process_recursive,
1689 pool));
1690 dst_rev = lookup_revnum(eb->to_session_prop, eb->current-1, pool);
1691 if (SVN_IS_VALID_REVNUM(dst_rev))
1692 {
1693 SVN_ERR(svn_ra_check_path(eb->to_session_prop, STRIP_LEADING_SLASH(path),
1694 dst_rev, &nodekind, pool));
1695 dir_present_in_target = (nodekind == svn_node_dir);
1696 }
1697 }
1698 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1699 pb->process_default, pb->process_recursive, path,
1700 eb->current, &db->process, &db->process_default,
1701 &db->process_recursive, pool));
1702 if (dir_added_this_changeset)
1703 {
1704 db->prev_process = db->process;
1705 db->prev_process_default = db->process_default;
1706 db->prev_process_recursive = db->process_recursive;
1707 }
1708 DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
1709 if (db->process && !pb->process)
1710 {
1711 /* Parent directory is not exported, but this directory is. Warn user,
1712 * because this can lead to destination repository weirdness. */
1713 SVN_ERR(svn_cmdline_printf(pool,
1714 _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
1715 db->process = FALSE;
1716 db->ignore_everything_rec = TRUE;
1717 db->ignore_everything = TRUE;
1718 }
1719 if (db->process && db->prev_process && !dir_added_this_changeset && !dir_present_in_target)
1720 {
1721 /* Directory is supposed to be there, but actually is not. Warn user,
1722 * because this can lead to destination repository weirdness. */
1723 SVN_ERR(svn_cmdline_printf(pool,
1724 _("The directory %s is exported but not present in the target repository. Ignoring it. FIX ASAP!\n"), path));
1725 db->process = FALSE;
1726 db->ignore_everything_rec = TRUE;
1727 db->ignore_everything = TRUE;
1728 }
1729 }
1730 else
1731 db->process = FALSE;
1732 db->edit_baton = eb;
1733 if (!db->ignore_everything)
1734 {
1735 if (db->process)
1736 {
1737 if (db->prev_process)
1738 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
1739 base_revision, pool,
1740 &db->wrapped_node_baton));
1741 else
1742 {
1743 apr_hash_t *dirprops;
1744 apr_hash_index_t *hi;
1745
1746 /* Directory appears due to changes to the process settings. */
1747 eb->changeset_live = TRUE;
1748 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
1749 NULL, SVN_IGNORED_REVNUM, pool,
1750 &db->wrapped_node_baton));
1751 /* Copy over properties from current revision in source repo */
1752 SVN_ERR(svn_ra_get_dir2(eb->from_session_prop, NULL, NULL, &dirprops,
1753 path, eb->current, 0, pool));
1754 for (hi = apr_hash_first(pool, dirprops); hi; hi = apr_hash_next(hi))
1755 {
1756 const void *key;
1757 void *val;
1758
1759 apr_hash_this(hi, &key, NULL, &val);
1760 SVN_ERR(change_dir_prop(db, key, val, pool));
1761 }
1762 /* Suppress change_dir_prop for this directory. Done already. */
1763 db->ignore_everything = TRUE;
1764
1765 /* TODO: copy over files in this directory which were already exported
1766 * due to inconsistent export settings (e.g. directory is not exported,
1767 * but file in it is exported). */
1768 }
1769 }
1770 else
1771 {
1772 if (db->prev_process && dir_present_in_target)
1773 {
1774 /* Directory disappears due to changes to the process settings. */
1775 eb->changeset_live = TRUE;
1776 SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
1777 pb->wrapped_node_baton, pool));
1778 }
1779 db->ignore_everything_rec = TRUE;
1780 }
1781 }
1782#else /* !VBOX */
1783 node_baton_t *db = apr_palloc(pool, sizeof(*db));
1784
1785 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
1786 base_revision, pool,
1787 &db->wrapped_node_baton));
1788
1789 db->edit_baton = eb;
1790#endif /* !VBOX */
1791 *child_baton = db;
1792
1793 return SVN_NO_ERROR;
1794}
1795
1796static svn_error_t *
1797add_file(const char *path,
1798 void *parent_baton,
1799 const char *copyfrom_path,
1800 svn_revnum_t copyfrom_rev,
1801 apr_pool_t *pool,
1802 void **file_baton)
1803{
1804 node_baton_t *pb = parent_baton;
1805 edit_baton_t *eb = pb->edit_baton;
1806#ifdef VBOX
1807 node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
1808 svn_revnum_t dst_rev;
1809
1810 DX(fprintf(stderr, "add_file %s\n", path);)
1811 fb->ignore_everything_rec = pb->ignore_everything_rec;
1812 fb->ignore_everything = fb->ignore_everything_rec;
1813 if (!fb->ignore_everything)
1814 {
1815 /* Of course it doesn't make sense to get the properties of the previous
1816 * revision - it is to be added, so it didn't have any properties. */
1817 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1818 pb->process_default, pb->process_recursive, path,
1819 eb->current, &fb->process, NULL, NULL, pool));
1820 fb->process_default = FALSE;
1821 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
1822 if (fb->process && !pb->process)
1823 {
1824 /* Parent directory is not exported, but this file is. Warn user,
1825 * because this can lead to destination repository weirdness. */
1826 SVN_ERR(svn_cmdline_printf(pool,
1827 _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
1828 fb->process = FALSE;
1829 }
1830 /* Fake previous process settings, to avoid warnings later on. */
1831 fb->prev_process = fb->process;
1832 fb->prev_process_default = fb->process_default;
1833 }
1834 else
1835 fb->process = FALSE;
1836 fb->edit_baton = eb;
1837 if (fb->process && !fb->ignore_everything)
1838 {
1839 eb->changeset_live = TRUE;
1840 if (copyfrom_path)
1841 {
1842 dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
1843 if (SVN_IS_VALID_REVNUM(dst_rev))
1844 {
1845 svn_node_kind_t nodekind;
1846 /* Verify that the copyfrom source was exported to the destination
1847 * repository. */
1848 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1849 STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
1850 &nodekind, pool));
1851 if (nodekind == svn_node_none || nodekind != svn_node_file)
1852 dst_rev = SVN_INVALID_REVNUM;
1853 else
1854 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1855 svn_path_uri_encode(copyfrom_path, pool));
1856 }
1857 }
1858 else
1859 dst_rev = copyfrom_rev;
1860
1861 if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
1862 {
1863 /* Genuinely add a new file, referring to other revision/name if known. */
1864 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
1865 copyfrom_path, dst_rev,
1866 pool, &fb->wrapped_node_baton));
1867 }
1868 else
1869 {
1870 /* The file was renamed, need to copy previous contents because we
1871 * don't know which revnum to use for destination repository. */
1872 SVN_ERR(copy_file(copyfrom_path, copyfrom_rev, path, fb,
1873 pb->wrapped_node_baton, eb->from_session_prop, pool));
1874 }
1875 }
1876#else /* !VBOX */
1877 node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
1878
1879 if (copyfrom_path)
1880 copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
1881 svn_path_uri_encode(copyfrom_path, pool));
1882
1883 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
1884 copyfrom_path, copyfrom_rev,
1885 pool, &fb->wrapped_node_baton));
1886
1887 fb->edit_baton = eb;
1888#endif /* !VBOX */
1889 *file_baton = fb;
1890
1891 return SVN_NO_ERROR;
1892}
1893
1894static svn_error_t *
1895open_file(const char *path,
1896 void *parent_baton,
1897 svn_revnum_t base_revision,
1898 apr_pool_t *pool,
1899 void **file_baton)
1900{
1901 node_baton_t *pb = parent_baton;
1902 edit_baton_t *eb = pb->edit_baton;
1903#ifdef VBOX
1904 node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
1905 svn_boolean_t file_added_this_changeset = FALSE;
1906
1907 DX(fprintf(stderr, "open_file %s\n", path);)
1908 fb->ignore_everything_rec = pb->ignore_everything_rec;
1909 fb->ignore_everything = fb->ignore_everything_rec;
1910 if (!fb->ignore_everything)
1911 {
1912 svn_node_kind_t nodekind;
1913 /* Check whether the file was added in this changeset. If it was added
1914 * there, the export check for the previous revision would fail. */
1915 SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
1916 eb->current-1, &nodekind, pool));
1917 file_added_this_changeset = (nodekind != svn_node_file);
1918 if (!file_added_this_changeset)
1919 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1920 pb->prev_process_default,
1921 pb->prev_process_recursive,
1922 path, eb->current-1, &fb->prev_process,
1923 NULL, NULL, pool));
1924 SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
1925 pb->process_default, pb->process_recursive, path,
1926 eb->current, &fb->process, NULL, NULL, pool));
1927 if (file_added_this_changeset)
1928 fb->prev_process = fb->process;
1929 fb->prev_process_default = FALSE;
1930 fb->process_default = FALSE;
1931 DX(fprintf(stderr, " %s (prev %s)\n", fb->process ? "EXPORT" : "IGNORE", fb->prev_process ? "EXPORT" : "IGNORE");)
1932 if (fb->process && !pb->process)
1933 {
1934 /* Parent directory is not exported, but this file is. Warn user,
1935 * because this can lead to destination repository weirdness. */
1936 SVN_ERR(svn_cmdline_printf(pool,
1937 _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
1938 fb->process = FALSE;
1939 fb->ignore_everything = TRUE;
1940 }
1941 }
1942 else
1943 fb->process = FALSE;
1944 fb->edit_baton = eb;
1945 if (!fb->ignore_everything)
1946 {
1947 if (fb->process)
1948 {
1949 if (!file_added_this_changeset)
1950 {
1951 svn_node_kind_t nodekind;
1952 /* Verify that the previous source was exported to the destination
1953 * repository. */
1954 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1955 STRIP_LEADING_SLASH(path),
1956 SVN_IGNORED_REVNUM, &nodekind, pool));
1957 if (nodekind == svn_node_none || nodekind != svn_node_file)
1958 fb->prev_process = FALSE;
1959 }
1960
1961 if (fb->prev_process)
1962 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
1963 base_revision, pool,
1964 &fb->wrapped_node_baton));
1965 else
1966 {
1967 /* File appears due to changes to the process settings. */
1968 eb->changeset_live = TRUE;
1969
1970 SVN_ERR(copy_file(path, eb->current, path, fb, pb->wrapped_node_baton,
1971 eb->from_session_prop, pool));
1972 /* Suppress change_file_prop/apply_textdelta this file. Done already. */
1973 fb->ignore_everything = TRUE;
1974 }
1975 }
1976 else
1977 {
1978 if (!file_added_this_changeset)
1979 {
1980 svn_node_kind_t nodekind;
1981 /* Verify that the previous source was exported to the destination
1982 * repository. */
1983 SVN_ERR(svn_ra_check_path(eb->to_session_prop,
1984 STRIP_LEADING_SLASH(path),
1985 SVN_IGNORED_REVNUM, &nodekind, pool));
1986 if (nodekind == svn_node_none || nodekind != svn_node_file)
1987 fb->prev_process = FALSE;
1988 }
1989
1990 if (fb->prev_process)
1991 {
1992 /* File disappears due to changes to the process settings. */
1993 eb->changeset_live = TRUE;
1994 SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
1995 pb->wrapped_node_baton, pool));
1996 fb->ignore_everything = TRUE;
1997 }
1998 }
1999 }
2000#else /* !VBOX */
2001 node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
2002
2003 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
2004 base_revision, pool,
2005 &fb->wrapped_node_baton));
2006
2007 fb->edit_baton = eb;
2008#endif /* !VBOX */
2009 *file_baton = fb;
2010
2011 return SVN_NO_ERROR;
2012}
2013
2014static svn_error_t *
2015apply_textdelta(void *file_baton,
2016 const char *base_checksum,
2017 apr_pool_t *pool,
2018 svn_txdelta_window_handler_t *handler,
2019 void **handler_baton)
2020{
2021 node_baton_t *fb = file_baton;
2022 edit_baton_t *eb = fb->edit_baton;
2023
2024#ifdef VBOX
2025 DX(fprintf(stderr, "apply_textdelta\n");)
2026 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2027 if (fb->process && !fb->ignore_everything)
2028 {
2029 eb->changeset_live = TRUE;
2030 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2031 base_checksum, pool,
2032 handler, handler_baton);
2033 }
2034 else
2035 {
2036 /* Must provide a window handler, there's no way of telling our caller
2037 * to throw away its data as we're not interested. */
2038 *handler = svn_delta_noop_window_handler;
2039 *handler_baton = NULL;
2040 return SVN_NO_ERROR;
2041 }
2042#else /* !VBOX */
2043 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2044 base_checksum, pool,
2045 handler, handler_baton);
2046#endif /* VBOX */
2047}
2048
2049static svn_error_t *
2050close_file(void *file_baton,
2051 const char *text_checksum,
2052 apr_pool_t *pool)
2053{
2054 node_baton_t *fb = file_baton;
2055 edit_baton_t *eb = fb->edit_baton;
2056#ifdef VBOX
2057 DX(fprintf(stderr, "close_file\n");)
2058 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2059 if (!fb->process)
2060 return SVN_NO_ERROR;
2061#endif /* VBOX */
2062 return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
2063 text_checksum, pool);
2064}
2065
2066static svn_error_t *
2067absent_file(const char *path,
2068 void *file_baton,
2069 apr_pool_t *pool)
2070{
2071 node_baton_t *fb = file_baton;
2072 edit_baton_t *eb = fb->edit_baton;
2073#ifdef VBOX
2074 DX(fprintf(stderr, "absent_file\n");)
2075 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2076 if (!fb->process)
2077 return SVN_NO_ERROR;
2078#endif /* VBOX */
2079 return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
2080}
2081
2082static svn_error_t *
2083close_directory(void *dir_baton,
2084 apr_pool_t *pool)
2085{
2086 node_baton_t *db = dir_baton;
2087 edit_baton_t *eb = db->edit_baton;
2088#ifdef VBOX
2089 DX(fprintf(stderr, "close_directory\n");)
2090 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2091 if (!db->process)
2092 return SVN_NO_ERROR;
2093#endif /* VBOX */
2094 return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
2095}
2096
2097static svn_error_t *
2098absent_directory(const char *path,
2099 void *dir_baton,
2100 apr_pool_t *pool)
2101{
2102 node_baton_t *db = dir_baton;
2103 edit_baton_t *eb = db->edit_baton;
2104#ifdef VBOX
2105 DX(fprintf(stderr, "absent_directory\n");)
2106 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2107 if (!db->process)
2108 return SVN_NO_ERROR;
2109#endif /* VBOX */
2110 return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
2111 pool);
2112}
2113
2114static svn_error_t *
2115change_file_prop(void *file_baton,
2116 const char *name,
2117 const svn_string_t *value,
2118 apr_pool_t *pool)
2119{
2120 node_baton_t *fb = file_baton;
2121 edit_baton_t *eb = fb->edit_baton;
2122
2123#ifdef VBOX
2124 DX(fprintf(stderr, "change_file_prop %s\n", name);)
2125 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2126#endif /* VBOX */
2127 /* only regular properties can pass over libsvn_ra */
2128 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2129 return SVN_NO_ERROR;
2130#ifdef VBOX
2131 if (!strcmp(name, "cvs2svn:cvs-rev"))
2132 return SVN_NO_ERROR;
2133 if (eb->replace_license)
2134 {
2135 /* Throw away the normal license property and replace it by the value
2136 * of svn:sync-license, if present. */
2137 if (!strcmp(name, SVN_PROP_LICENSE))
2138 return SVN_NO_ERROR;
2139 if (!strcmp(name, SVNSYNC_PROP_LICENSE))
2140 name = SVN_PROP_LICENSE;
2141 }
2142 /* Never export any svn:sync-* properties. */
2143 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2144 return SVN_NO_ERROR;
2145 if (!fb->process || fb->ignore_everything)
2146 return SVN_NO_ERROR;
2147 eb->changeset_live = TRUE;
2148#endif /* VBOX */
2149
2150 return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
2151 name, value, pool);
2152}
2153
2154static svn_error_t *
2155change_dir_prop(void *dir_baton,
2156 const char *name,
2157 const svn_string_t *value,
2158 apr_pool_t *pool)
2159{
2160 node_baton_t *db = dir_baton;
2161 edit_baton_t *eb = db->edit_baton;
2162
2163#ifdef VBOX
2164 DX(fprintf(stderr, "change_dir_prop %s\n", name);)
2165 DX(fprintf(stderr, " %s (ignore_everything %d)\n", db->process ? "EXPORT" : "IGNORE", db->ignore_everything);)
2166#endif /* VBOX */
2167 /* only regular properties can pass over libsvn_ra */
2168 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2169 return SVN_NO_ERROR;
2170#ifdef VBOX
2171 if (!strcmp(name, "cvs2svn:cvs-rev"))
2172 return SVN_NO_ERROR;
2173 if (eb->replace_externals)
2174 {
2175 /* Throw away the normal externals and replace them by the value of
2176 * svn:sync-externals, if present. */
2177 if (!strcmp(name, SVN_PROP_EXTERNALS))
2178 return SVN_NO_ERROR;
2179 if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
2180 name = SVN_PROP_EXTERNALS;
2181 }
2182 /* Never export any svn:sync-* properties. */
2183 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2184 return SVN_NO_ERROR;
2185 if (!db->process || db->ignore_everything)
2186 return SVN_NO_ERROR;
2187 eb->changeset_live = TRUE;
2188#endif /* VBOX */
2189
2190 return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
2191 name, value, pool);
2192}
2193
2194static svn_error_t *
2195close_edit(void *edit_baton,
2196 apr_pool_t *pool)
2197{
2198 edit_baton_t *eb = edit_baton;
2199
2200#ifdef VBOX
2201 DX(fprintf(stderr, "close_edit\n");)
2202 /* Suppress empty commits. No need to record something in the
2203 * repository if the entire contents of a changeset is to be ignored. */
2204 if (eb->start_rev && !eb->changeset_live)
2205 {
2206 DX(fprintf(stderr, " discard empty commit\n");)
2207 SVN_ERR(eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool));
2208 SVN_ERR(svn_cmdline_printf(pool, _("Skipped revision %ld in source "
2209 "repository, empty commit.\n"),
2210 eb->current));
2211 return SVN_NO_ERROR;
2212 }
2213#endif /* VBOX */
2214
2215 /* If we haven't opened the root yet, that means we're transfering
2216 an empty revision, probably because we aren't allowed to see the
2217 contents for some reason. In any event, we need to open the root
2218 and close it again, before we can close out the edit, or the
2219 commit will fail. */
2220
2221 if (! eb->called_open_root)
2222 {
2223 void *baton;
2224#ifdef VBOX
2225 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2226 eb->current, pool,
2227 &baton));
2228#else /* !VBOX */
2229 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2230 eb->base_revision, pool,
2231 &baton));
2232#endif /* !VBOX */
2233 SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
2234 }
2235
2236 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
2237}
2238
2239/*** Editor factory function ***/
2240
2241/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
2242 * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
2243 * revision on which the driver of this returned editor will be basing
2244 * the commit. TO_URL is the URL of the root of the repository into
2245 * which the commit is being made.
2246 */
2247static svn_error_t *
2248get_sync_editor(const svn_delta_editor_t *wrapped_editor,
2249 void *wrapped_edit_baton,
2250 svn_revnum_t base_revision,
2251#ifdef VBOX
2252 svn_revnum_t start_rev,
2253 svn_revnum_t current,
2254 svn_ra_session_t *prop_session_from,
2255 svn_ra_session_t *prop_session_to,
2256 const char *default_process,
2257 svn_boolean_t replace_externals,
2258 svn_boolean_t replace_license,
2259#endif /* VBOX */
2260 const char *to_url,
2261 const svn_delta_editor_t **editor,
2262 void **edit_baton,
2263 apr_pool_t *pool)
2264{
2265 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
2266 edit_baton_t *eb = apr_palloc(pool, sizeof(*eb));
2267
2268 tree_editor->set_target_revision = set_target_revision;
2269 tree_editor->open_root = open_root;
2270 tree_editor->delete_entry = delete_entry;
2271 tree_editor->add_directory = add_directory;
2272 tree_editor->open_directory = open_directory;
2273 tree_editor->change_dir_prop = change_dir_prop;
2274 tree_editor->close_directory = close_directory;
2275 tree_editor->absent_directory = absent_directory;
2276 tree_editor->add_file = add_file;
2277 tree_editor->open_file = open_file;
2278 tree_editor->apply_textdelta = apply_textdelta;
2279 tree_editor->change_file_prop = change_file_prop;
2280 tree_editor->close_file = close_file;
2281 tree_editor->absent_file = absent_file;
2282 tree_editor->close_edit = close_edit;
2283
2284 eb->wrapped_editor = wrapped_editor;
2285 eb->wrapped_edit_baton = wrapped_edit_baton;
2286 eb->called_open_root = FALSE;
2287 eb->base_revision = base_revision;
2288#ifdef VBOX
2289 eb->changeset_live = FALSE;
2290 eb->start_rev = start_rev;
2291 eb->current = current;
2292 eb->default_process = default_process;
2293 eb->replace_externals = replace_externals;
2294 eb->replace_license = replace_license;
2295 eb->from_session_prop = prop_session_from;
2296 eb->to_session_prop = prop_session_to;
2297#endif /* VBOX */
2298 eb->to_url = to_url;
2299
2300 *editor = tree_editor;
2301 *edit_baton = eb;
2302
2303 return SVN_NO_ERROR;
2304}
2305
2306
2307
2308
2309/*** `svnsync sync' ***/
2310
2311/* Baton for syncronizing the destination repository while locked. */
2312typedef struct {
2313 apr_hash_t *config;
2314 svn_ra_callbacks2_t *callbacks;
2315 const char *to_url;
2316 svn_revnum_t committed_rev;
2317#ifdef VBOX
2318 svn_revnum_t from_rev;
2319#endif /* VBOX */
2320} sync_baton_t;
2321
2322
2323/* Implements `svn_commit_callback2_t' interface. */
2324static svn_error_t *
2325commit_callback(const svn_commit_info_t *commit_info,
2326 void *baton,
2327 apr_pool_t *pool)
2328{
2329 sync_baton_t *sb = baton;
2330
2331#ifdef VBOX
2332 if (sb->from_rev != commit_info->revision)
2333 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld (%ld in source repository).\n"),
2334 commit_info->revision, sb->from_rev));
2335 else
2336 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2337 commit_info->revision));
2338#else /* !VBOX */
2339 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2340 commit_info->revision));
2341#endif /* !VBOX */
2342
2343 sb->committed_rev = commit_info->revision;
2344
2345 return SVN_NO_ERROR;
2346}
2347
2348
2349/* Set *FROM_SESSION to an RA session associated with the source
2350 * repository of the syncronization, as determined by reading
2351 * svn:sync- properties from the destination repository (associated
2352 * with TO_SESSION). Set LAST_MERGED_REV to the value of the property
2353 * which records the most recently syncronized revision.
2354*** VBOX
2355 * Set START_REV_STR to the propery which records the starting revision.
2356*** VBOX
2357 *
2358 * CALLBACKS is a vtable of RA callbacks to provide when creating
2359 * *FROM_SESSION. CONFIG is a configuration hash.
2360 */
2361static svn_error_t *
2362open_source_session(svn_ra_session_t **from_session,
2363 svn_string_t **last_merged_rev,
2364#ifdef VBOX
2365 svn_revnum_t *start_rev,
2366#endif /* VBOX */
2367 svn_ra_session_t *to_session,
2368 svn_ra_callbacks2_t *callbacks,
2369 apr_hash_t *config,
2370 void *baton,
2371 apr_pool_t *pool)
2372{
2373#ifdef VBOX
2374 svn_string_t *start_rev_str;
2375#endif /* VBOX */
2376 svn_string_t *from_url, *from_uuid;
2377 const char *uuid;
2378
2379 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2380 &from_url, pool));
2381 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
2382 &from_uuid, pool));
2383 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
2384 last_merged_rev, pool));
2385#ifdef VBOX
2386 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_START_REV,
2387 &start_rev_str, pool));
2388#endif /* VBOX */
2389
2390#ifdef VBOX
2391 if (! from_url || ! from_uuid || ! *last_merged_rev || ! start_rev_str)
2392#else /* !VBOX */
2393 if (! from_url || ! from_uuid || ! *last_merged_rev)
2394#endif /* !VBOX */
2395 return svn_error_create
2396 (APR_EINVAL, NULL, _("Destination repository has not been initialized"));
2397
2398#ifdef VBOX
2399 *start_rev = SVN_STR_TO_REV(start_rev_str->data);
2400#endif /* VBOX */
2401
2402#ifdef VBOX
2403 SVN_ERR(svn_ra_open3(from_session, from_url->data, NULL, callbacks, baton,
2404 config, pool));
2405#else /* !VBOX */
2406 SVN_ERR(svn_ra_open2(from_session, from_url->data, callbacks, baton,
2407 config, pool));
2408#endif /* !VBOX */
2409
2410 SVN_ERR(check_if_session_is_at_repos_root(*from_session, from_url->data,
2411 pool));
2412
2413 /* Ok, now sanity check the UUID of the source repository, it
2414 wouldn't be a good thing to sync from a different repository. */
2415
2416#ifdef VBOX
2417 SVN_ERR(svn_ra_get_uuid2(*from_session, &uuid, pool));
2418#else /* !VBOX */
2419 SVN_ERR(svn_ra_get_uuid(*from_session, &uuid, pool));
2420#endif /* !VBOX */
2421
2422 if (strcmp(uuid, from_uuid->data) != 0)
2423 return svn_error_createf(APR_EINVAL, NULL,
2424 _("UUID of source repository (%s) does not "
2425 "match expected UUID (%s)"),
2426 uuid, from_uuid->data);
2427
2428 return SVN_NO_ERROR;
2429}
2430
2431
2432/* Syncronize the repository associated with RA session TO_SESSION,
2433 * using information found in baton B, while the repository is
2434 * locked. Implements `with_locked_func_t' interface.
2435 */
2436static svn_error_t *
2437do_synchronize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2438{
2439 svn_string_t *last_merged_rev;
2440 svn_revnum_t from_latest, current;
2441 svn_ra_session_t *from_session;
2442 sync_baton_t *baton = b;
2443 apr_pool_t *subpool;
2444 svn_string_t *currently_copying;
2445 svn_revnum_t to_latest, copying, last_merged;
2446#ifdef VBOX
2447 svn_revnum_t start_rev;
2448 svn_string_t *from_url;
2449 svn_string_t *default_process;
2450 svn_string_t *replace_externals_str;
2451 svn_boolean_t replace_externals;
2452 svn_string_t *replace_license_str;
2453 svn_boolean_t replace_license;
2454 svn_string_t *ignoreprop;
2455 svn_ra_session_t *from_session_prop;
2456 svn_ra_session_t *to_session_prop;
2457#endif /* VBOX */
2458
2459#ifdef VBOX
2460 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2461 to_session, baton->callbacks, baton->config,
2462 baton, pool));
2463 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2464 &from_url, pool));
2465 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS,
2466 &default_process, pool));
2467 if (!default_process)
2468 default_process = svn_string_create("export", pool);
2469 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_EXTERNALS,
2470 &replace_externals_str, pool));
2471 replace_externals = !!replace_externals_str;
2472 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_LICENSE,
2473 &replace_license_str, pool));
2474 replace_license = !!replace_license_str;
2475 SVN_ERR(svn_ra_open3(&from_session_prop, from_url->data, NULL,
2476 baton->callbacks, baton, baton->config, pool));
2477 SVN_ERR(svn_ra_open3(&to_session_prop, baton->to_url, NULL,
2478 baton->callbacks, baton, baton->config, pool));
2479#else /* !VBOX */
2480 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2481 baton->callbacks, baton->config, baton, pool));
2482#endif /* !VBOX */
2483
2484 /* Check to see if we have revprops that still need to be copied for
2485 a prior revision we didn't finish copying. But first, check for
2486 state sanity. Remember, mirroring is not an atomic action,
2487 because revision properties are copied separately from the
2488 revision's contents.
2489
2490 So, any time that currently-copying is not set, then
2491 last-merged-rev should be the HEAD revision of the destination
2492 repository. That is, if we didn't fall over in the middle of a
2493 previous syncronization, then our destination repository should
2494 have exactly as many revisions in it as we've syncronized.
2495
2496 Alternately, if currently-copying *is* set, it must
2497 be either last-merged-rev or last-merged-rev + 1, and the HEAD
2498 revision must be equal to either last-merged-rev or
2499 currently-copying. If this is not the case, somebody has meddled
2500 with the destination without using svnsync.
2501 */
2502
2503 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
2504 &currently_copying, pool));
2505
2506#ifndef VBOX
2507 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2508#endif /* !VBOX */
2509
2510 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
2511
2512#ifdef VBOX
2513 if (start_rev)
2514 {
2515 /* Fake the destination repository revnum to be what the complete sync
2516 * code expects. TODO: this probably breaks continuing after an abort.*/
2517 to_latest = last_merged;
2518 }
2519 else
2520 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2521#endif /* VBOX */
2522
2523 if (currently_copying)
2524 {
2525 copying = SVN_STR_TO_REV(currently_copying->data);
2526
2527 if ((copying < last_merged)
2528 || (copying > (last_merged + 1))
2529 || ((to_latest != last_merged) && (to_latest != copying)))
2530 {
2531 return svn_error_createf
2532 (APR_EINVAL, NULL,
2533 _("Revision being currently copied (%ld), last merged revision "
2534 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
2535 "committed to the destination without using svnsync?"),
2536 copying, last_merged, to_latest);
2537 }
2538 else if (copying == to_latest)
2539 {
2540 if (copying > last_merged)
2541 {
2542#ifdef VBOX
2543/* TODO fix use of from/to revision numbers. */
2544 SVN_ERR(copy_revprops(from_session, to_session,
2545 to_latest, to_latest, TRUE, pool));
2546#else /* !VBOX */
2547 SVN_ERR(copy_revprops(from_session, to_session,
2548 to_latest, TRUE, pool));
2549#endif /* !VBOX */
2550 last_merged = copying;
2551 last_merged_rev = svn_string_create
2552 (apr_psprintf(pool, "%ld", last_merged), pool);
2553 }
2554
2555 /* Now update last merged rev and drop currently changing.
2556 Note that the order here is significant, if we do them
2557 in the wrong order there are race conditions where we
2558 end up not being able to tell if there have been bogus
2559 (i.e. non-svnsync) commits to the dest repository. */
2560
2561 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2562 SVNSYNC_PROP_LAST_MERGED_REV,
2563 last_merged_rev, pool));
2564 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2565 SVNSYNC_PROP_CURRENTLY_COPYING,
2566 NULL, pool));
2567 }
2568 /* If copying > to_latest, then we just fall through to
2569 attempting to copy the revision again. */
2570 }
2571 else
2572 {
2573 if (to_latest != last_merged)
2574 {
2575 return svn_error_createf
2576 (APR_EINVAL, NULL,
2577 _("Destination HEAD (%ld) is not the last merged revision (%ld); "
2578 "have you committed to the destination without using svnsync?"),
2579 to_latest, last_merged);
2580 }
2581 }
2582
2583 /* Now check to see if there are any revisions to copy. */
2584
2585 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
2586
2587 if (from_latest < atol(last_merged_rev->data))
2588 return SVN_NO_ERROR;
2589
2590 subpool = svn_pool_create(pool);
2591
2592 /* Ok, so there are new revisions, iterate over them copying them
2593 into the destination repository. */
2594
2595 for (current = atol(last_merged_rev->data) + 1;
2596 current <= from_latest;
2597 ++current)
2598 {
2599 const svn_delta_editor_t *commit_editor;
2600 const svn_delta_editor_t *cancel_editor;
2601 const svn_delta_editor_t *sync_editor;
2602 void *commit_baton;
2603 void *cancel_baton;
2604 void *sync_baton;
2605#ifdef VBOX
2606 apr_hash_t *logrevprop;
2607#endif /* VBOX */
2608
2609 svn_pool_clear(subpool);
2610
2611 /* We set this property so that if we error out for some reason
2612 we can later determine where we were in the process of
2613 merging a revision. If we had committed the change, but we
2614 hadn't finished copying the revprops we need to know that, so
2615 we can go back and finish the job before we move on.
2616
2617 NOTE: We have to set this before we start the commit editor,
2618 because ra_svn doesn't let you change rev props during a
2619 commit. */
2620 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2621 SVNSYNC_PROP_CURRENTLY_COPYING,
2622 svn_string_createf(subpool, "%ld",
2623 current),
2624 subpool));
2625
2626 /* The actual copy is just a replay hooked up to a commit. */
2627
2628#ifdef VBOX
2629 logrevprop = apr_hash_make(pool);
2630 apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
2631 svn_string_create("", pool));
2632 SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor,
2633 &commit_baton,
2634 logrevprop,
2635 commit_callback, baton,
2636 NULL, FALSE, subpool));
2637#else /* !VBOX */
2638 SVN_ERR(svn_ra_get_commit_editor2(to_session, &commit_editor,
2639 &commit_baton,
2640 "", /* empty log */
2641 commit_callback, baton,
2642 NULL, FALSE, subpool));
2643#endif /* !VBOX */
2644
2645 /* There's one catch though, the diff shows us props we can't
2646 send over the RA interface, so we need an editor that's smart
2647 enough to filter those out for us. */
2648
2649#ifdef VBOX
2650 baton->from_rev = current;
2651 baton->committed_rev = SVN_INVALID_REVNUM;
2652 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2653 start_rev, current, from_session_prop,
2654 to_session_prop, default_process->data,
2655 replace_externals, replace_license,
2656 baton->to_url, &sync_editor, &sync_baton,
2657 subpool));
2658#else /* !VBOX */
2659 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2660 baton->to_url, &sync_editor, &sync_baton,
2661 subpool));
2662#endif /* !VBOX */
2663
2664 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
2665 sync_editor, sync_baton,
2666 &cancel_editor,
2667 &cancel_baton,
2668 subpool));
2669
2670#ifdef VBOX
2671 /* If svn:sync-ignore-changeset revprop exists in changeset, skip it. */
2672 SVN_ERR(svn_ra_rev_prop(from_session, current,
2673 SVNSYNC_PROP_IGNORE_CHANGESET,
2674 &ignoreprop, subpool));
2675 if (!ignoreprop)
2676 SVN_ERR(svn_ra_replay(from_session, current, start_rev, TRUE,
2677 cancel_editor, cancel_baton, subpool));
2678#else /* !VBOX */
2679 SVN_ERR(svn_ra_replay(from_session, current, 0, TRUE,
2680 cancel_editor, cancel_baton, subpool));
2681#endif /* !VBOX */
2682
2683 SVN_ERR(cancel_editor->close_edit(cancel_baton, subpool));
2684
2685#ifdef VBOX
2686 if (!start_rev)
2687 {
2688 /* Sanity check that we actually committed the revision we meant to. */
2689 if (baton->committed_rev != current)
2690 return svn_error_createf
2691 (APR_EINVAL, NULL,
2692 _("Commit created rev %ld but should have created %ld"),
2693 baton->committed_rev, current);
2694 }
2695#else /* !VBOX */
2696 /* Sanity check that we actually committed the revision we meant to. */
2697 if (baton->committed_rev != current)
2698 return svn_error_createf
2699 (APR_EINVAL, NULL,
2700 _("Commit created rev %ld but should have created %ld"),
2701 baton->committed_rev, current);
2702#endif /* !VBOX */
2703
2704 /* Ok, we're done with the data, now we just need to do the
2705 revprops and we're all set. */
2706
2707#ifdef VBOX
2708 if (SVN_IS_VALID_REVNUM(baton->committed_rev))
2709 {
2710 SVN_ERR(copy_revprops(from_session, to_session, current,
2711 baton->committed_rev, TRUE, subpool));
2712
2713 /* Add a revision cross-reference revprop. */
2714 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2715 apr_psprintf(subpool,
2716 SVNSYNC_PROP_REV__FMT,
2717 current),
2718 svn_string_create(apr_psprintf(subpool,
2719 "%ld",
2720 baton->committed_rev),
2721 subpool),
2722 subpool));
2723 }
2724 else
2725 {
2726 /* Add a revision cross-reference revprop for an empty commit,
2727 * referring to the previous commit (this avoids unnecessary copy_file
2728 * operation just because a source file was not modified when it
2729 * appears in the destination repository. */
2730 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, subpool));
2731 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2732 apr_psprintf(subpool,
2733 SVNSYNC_PROP_REV__FMT,
2734 current),
2735 svn_string_create(apr_psprintf(subpool,
2736 "%ld",
2737 to_latest),
2738 subpool),
2739 subpool));
2740 }
2741#else /* !VBOX */
2742 SVN_ERR(copy_revprops(from_session, to_session, current, TRUE, subpool));
2743#endif /* !VBOX */
2744
2745 /* Ok, we're done, bring the last-merged-rev property up to date. */
2746
2747 SVN_ERR(svn_ra_change_rev_prop
2748 (to_session,
2749 0,
2750 SVNSYNC_PROP_LAST_MERGED_REV,
2751 svn_string_create(apr_psprintf(subpool, "%ld", current),
2752 subpool),
2753 subpool));
2754
2755 /* And finally drop the currently copying prop, since we're done
2756 with this revision. */
2757
2758 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2759 SVNSYNC_PROP_CURRENTLY_COPYING,
2760 NULL, subpool));
2761 }
2762
2763 return SVN_NO_ERROR;
2764}
2765
2766
2767/* SUBCOMMAND: sync */
2768static svn_error_t *
2769synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2770{
2771 svn_ra_callbacks2_t callbacks = { 0 };
2772 svn_ra_session_t *to_session;
2773 opt_baton_t *opt_baton = b;
2774 apr_array_header_t *args;
2775 sync_baton_t baton;
2776 const char *to_url;
2777
2778 SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
2779
2780 to_url = svn_path_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
2781
2782 if (! svn_path_is_url(to_url))
2783 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2784 _("Path '%s' is not a URL"), to_url);
2785
2786 callbacks.open_tmp_file = open_tmp_file;
2787 callbacks.auth_baton = opt_baton->auth_baton;
2788
2789 baton.callbacks = &callbacks;
2790 baton.config = opt_baton->config;
2791 baton.to_url = to_url;
2792
2793#ifdef VBOX
2794 SVN_ERR(svn_ra_open3(&to_session, to_url, NULL,
2795 baton.callbacks, &baton, baton.config, pool));
2796#else /* !VBOX */
2797 SVN_ERR(svn_ra_open2(&to_session,
2798 to_url,
2799 baton.callbacks,
2800 &baton,
2801 baton.config,
2802 pool));
2803#endif /* !VBOX */
2804
2805 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
2806
2807 SVN_ERR(with_locked(to_session, do_synchronize, &baton, pool));
2808
2809 return SVN_NO_ERROR;
2810}
2811
2812
2813
2814
2815/*** `svnsync copy-revprops' ***/
2816
2817
2818/* Baton for copying revision properties to the destination repository
2819 * while locked.
2820 */
2821typedef struct {
2822 apr_hash_t *config;
2823 svn_ra_callbacks2_t *callbacks;
2824 const char *to_url;
2825 svn_revnum_t rev;
2826} copy_revprops_baton_t;
2827
2828
2829/* Copy revision properties to the repository associated with RA
2830 * session TO_SESSION, using information found in baton B, while the
2831 * repository is locked. Implements `with_locked_func_t' interface.
2832 */
2833static svn_error_t *
2834do_copy_revprops(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2835{
2836 copy_revprops_baton_t *baton = b;
2837 svn_ra_session_t *from_session;
2838 svn_string_t *last_merged_rev;
2839#ifdef VBOX
2840 svn_revnum_t start_rev;
2841#endif /* VBOX */
2842
2843#ifdef VBOX
2844 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2845 to_session, baton->callbacks, baton->config,
2846 baton, pool));
2847 if (start_rev)
2848 return svn_error_create
2849 (APR_EINVAL, NULL, _("Cannot copy revprops for repositories using "
2850 "the start-rev feature (unimplemented)"));
2851#else /* !VBOX */
2852 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2853 baton->callbacks, baton->config, baton, pool));
2854#endif /* !VBOX */
2855
2856 if (baton->rev > SVN_STR_TO_REV(last_merged_rev->data))
2857 return svn_error_create
2858 (APR_EINVAL, NULL, _("Cannot copy revprops for a revision that has not "
2859 "been synchronized yet"));
2860
2861#ifdef VBOX
2862 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, baton->rev, FALSE, pool));
2863#else /* !VBOX */
2864 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, FALSE, pool));
2865#endif /* !VBOX */
2866
2867 return SVN_NO_ERROR;
2868}
2869
2870
2871/* SUBCOMMAND: copy-revprops */
2872static svn_error_t *
2873copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2874{
2875 svn_ra_callbacks2_t callbacks = { 0 };
2876 svn_ra_session_t *to_session;
2877 opt_baton_t *opt_baton = b;
2878 apr_array_header_t *args;
2879 copy_revprops_baton_t baton;
2880 const char *to_url;
2881 svn_revnum_t revision = SVN_INVALID_REVNUM;
2882 char *digits_end = NULL;
2883
2884 SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
2885
2886 to_url = svn_path_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
2887 revision = strtol(APR_ARRAY_IDX(args, 1, const char *), &digits_end, 10);
2888
2889 if (! svn_path_is_url(to_url))
2890 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2891 _("Path '%s' is not a URL"), to_url);
2892 if ((! SVN_IS_VALID_REVNUM(revision)) || (! digits_end) || *digits_end)
2893 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2894 _("Invalid revision number"));
2895
2896 callbacks.open_tmp_file = open_tmp_file;
2897 callbacks.auth_baton = opt_baton->auth_baton;
2898
2899 baton.callbacks = &callbacks;
2900 baton.config = opt_baton->config;
2901 baton.to_url = to_url;
2902 baton.rev = revision;
2903
2904#ifdef VBOX
2905 SVN_ERR(svn_ra_open3(&to_session, to_url, NULL,
2906 baton.callbacks, &baton, baton.config, pool));
2907#else /* !VBOX */
2908 SVN_ERR(svn_ra_open2(&to_session,
2909 to_url,
2910 baton.callbacks,
2911 &baton,
2912 baton.config,
2913 pool));
2914#endif /* !VBOX */
2915
2916 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
2917
2918 SVN_ERR(with_locked(to_session, do_copy_revprops, &baton, pool));
2919
2920 return SVN_NO_ERROR;
2921}
2922
2923
2924
2925
2926/*** `svnsync help' ***/
2927
2928
2929/* SUBCOMMAND: help */
2930static svn_error_t *
2931help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2932{
2933 opt_baton_t *opt_baton = baton;
2934
2935 const char *header =
2936 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
2937 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
2938 "Type 'svnsync --version' to see the program version and RA modules.\n"
2939 "\n"
2940 "Available subcommands:\n");
2941
2942 const char *ra_desc_start
2943 = _("The following repository access (RA) modules are available:\n\n");
2944
2945 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
2946 pool);
2947
2948 SVN_ERR(svn_ra_print_modules(version_footer, pool));
2949
2950#ifdef VBOX
2951 SVN_ERR(svn_opt_print_help3(os, "svnsync",
2952 opt_baton ? opt_baton->version : FALSE,
2953 FALSE, version_footer->data, header,
2954 svnsync_cmd_table, svnsync_options, NULL,
2955 NULL, pool));
2956#else /* !VBOX */
2957 SVN_ERR(svn_opt_print_help(os, "svnsync",
2958 opt_baton ? opt_baton->version : FALSE,
2959 FALSE, version_footer->data, header,
2960 svnsync_cmd_table, svnsync_options, NULL,
2961 pool));
2962#endif /* !VBOX */
2963
2964 return SVN_NO_ERROR;
2965}
2966
2967
2968
2969
2970/*** Main ***/
2971
2972int
2973main(int argc, const char *argv[])
2974{
2975#ifdef VBOX
2976 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2977#else /* !VBOX */
2978 const svn_opt_subcommand_desc_t *subcommand = NULL;
2979#endif /* !VBOX */
2980 apr_array_header_t *received_opts;
2981 opt_baton_t opt_baton;
2982 svn_config_t *config;
2983 apr_status_t apr_err;
2984 apr_getopt_t *os;
2985 apr_pool_t *pool;
2986 svn_error_t *err;
2987 int opt_id, i;
2988
2989 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
2990 {
2991 return EXIT_FAILURE;
2992 }
2993
2994 err = check_lib_versions();
2995 if (err)
2996 {
2997 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
2998 return EXIT_FAILURE;
2999 }
3000
3001 pool = svn_pool_create(NULL);
3002
3003 err = svn_ra_initialize(pool);
3004 if (err)
3005 {
3006 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3007 return EXIT_FAILURE;
3008 }
3009
3010 memset(&opt_baton, 0, sizeof(opt_baton));
3011
3012 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
3013
3014 if (argc <= 1)
3015 {
3016 help_cmd(NULL, NULL, pool);
3017 svn_pool_destroy(pool);
3018 return EXIT_FAILURE;
3019 }
3020
3021 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
3022 if (err)
3023 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3024
3025 os->interleave = 1;
3026
3027 for (;;)
3028 {
3029 const char *opt_arg;
3030
3031 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
3032 if (APR_STATUS_IS_EOF(apr_err))
3033 break;
3034 else if (apr_err)
3035 {
3036 help_cmd(NULL, NULL, pool);
3037 svn_pool_destroy(pool);
3038 return EXIT_FAILURE;
3039 }
3040
3041 APR_ARRAY_PUSH(received_opts, int) = opt_id;
3042
3043 switch (opt_id)
3044 {
3045 case svnsync_opt_non_interactive:
3046 opt_baton.non_interactive = TRUE;
3047 break;
3048
3049 case svnsync_opt_no_auth_cache:
3050 opt_baton.no_auth_cache = TRUE;
3051 break;
3052
3053 case svnsync_opt_auth_username:
3054 opt_baton.auth_username = opt_arg;
3055 break;
3056
3057 case svnsync_opt_auth_password:
3058 opt_baton.auth_password = opt_arg;
3059 break;
3060
3061 case svnsync_opt_config_dir:
3062 opt_baton.config_dir = opt_arg;
3063 break;
3064
3065#ifdef VBOX
3066 case svnsync_opt_start_rev:
3067 opt_baton.start_rev = SVN_STR_TO_REV(opt_arg);
3068 break;
3069
3070 case svnsync_opt_default_process:
3071 opt_baton.default_process = opt_arg;
3072 break;
3073
3074 case svnsync_opt_replace_externals:
3075 opt_baton.replace_externals = TRUE;
3076 break;
3077
3078 case svnsync_opt_replace_license:
3079 opt_baton.replace_license = TRUE;
3080 break;
3081#endif /* VBOX */
3082
3083 case svnsync_opt_version:
3084 opt_baton.version = TRUE;
3085 break;
3086
3087 case '?':
3088 case 'h':
3089 opt_baton.help = TRUE;
3090 break;
3091
3092 default:
3093 {
3094 help_cmd(NULL, NULL, pool);
3095 svn_pool_destroy(pool);
3096 return EXIT_FAILURE;
3097 }
3098 }
3099 }
3100
3101 if (opt_baton.help)
3102#ifdef VBOX
3103 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
3104#else /* !VBOX */
3105 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table, "help");
3106#endif /* !VBOX */
3107
3108 if (subcommand == NULL)
3109 {
3110 if (os->ind >= os->argc)
3111 {
3112 if (opt_baton.version)
3113 {
3114 /* Use the "help" subcommand to handle the "--version" option. */
3115#ifdef VBOX
3116 static const svn_opt_subcommand_desc2_t pseudo_cmd =
3117#else /* !VBOX */
3118 static const svn_opt_subcommand_desc_t pseudo_cmd =
3119#endif /* !VBOX */
3120 { "--version", help_cmd, {0}, "",
3121 {svnsync_opt_version, /* must accept its own option */
3122 } };
3123
3124 subcommand = &pseudo_cmd;
3125 }
3126 else
3127 {
3128 help_cmd(NULL, NULL, pool);
3129 svn_pool_destroy(pool);
3130 return EXIT_FAILURE;
3131 }
3132 }
3133 else
3134 {
3135 const char *first_arg = os->argv[os->ind++];
3136#ifdef VBOX
3137 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
3138 first_arg);
3139#else /* !VBOX */
3140 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table,
3141 first_arg);
3142#endif /* !VBOX */
3143 if (subcommand == NULL)
3144 {
3145 help_cmd(NULL, NULL, pool);
3146 svn_pool_destroy(pool);
3147 return EXIT_FAILURE;
3148 }
3149 }
3150 }
3151
3152 for (i = 0; i < received_opts->nelts; ++i)
3153 {
3154 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3155
3156 if (opt_id == 'h' || opt_id == '?')
3157 continue;
3158
3159#ifdef VBOX
3160 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3161#else /* !VBOX */
3162 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
3163#endif /* !VBOX */
3164 {
3165 const char *optstr;
3166#ifdef VBOX
3167 const apr_getopt_option_t *badopt =
3168 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
3169 pool);
3170#else /* !VBOX */
3171 const apr_getopt_option_t *badopt =
3172 svn_opt_get_option_from_code(opt_id, svnsync_options);
3173#endif /* !VBOX */
3174 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3175 if (subcommand->name[0] == '-')
3176 help_cmd(NULL, NULL, pool);
3177 else
3178 svn_error_clear
3179 (svn_cmdline_fprintf
3180 (stderr, pool, _("subcommand '%s' doesn't accept option '%s'\n"
3181 "Type 'svnsync help %s' for usage.\n"),
3182 subcommand->name, optstr, subcommand->name));
3183 svn_pool_destroy(pool);
3184 return EXIT_FAILURE;
3185 }
3186 }
3187
3188 err = svn_config_get_config(&opt_baton.config, NULL, pool);
3189 if (err)
3190 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3191
3192 config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
3193 APR_HASH_KEY_STRING);
3194
3195 apr_signal(SIGINT, signal_handler);
3196
3197#ifdef SIGBREAK
3198 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
3199 apr_signal(SIGBREAK, signal_handler);
3200#endif
3201
3202#ifdef SIGHUP
3203 apr_signal(SIGHUP, signal_handler);
3204#endif
3205
3206#ifdef SIGTERM
3207 apr_signal(SIGTERM, signal_handler);
3208#endif
3209
3210#ifdef SIGPIPE
3211 /* Disable SIGPIPE generation for the platforms that have it. */
3212 apr_signal(SIGPIPE, SIG_IGN);
3213#endif
3214
3215#ifdef SIGXFSZ
3216 /* Disable SIGXFSZ generation for the platforms that have it,
3217 otherwise working with large files when compiled against an APR
3218 that doesn't have large file support will crash the program,
3219 which is uncool. */
3220 apr_signal(SIGXFSZ, SIG_IGN);
3221#endif
3222
3223#ifdef VBOX
3224 err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
3225 opt_baton.non_interactive,
3226 opt_baton.auth_username,
3227 opt_baton.auth_password,
3228 opt_baton.config_dir,
3229 opt_baton.no_auth_cache,
3230 1,
3231 config,
3232 check_cancel, NULL,
3233 pool);
3234 if (!err)
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#else /* !VBOX */
3246 err = svn_cmdline_setup_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 config,
3253 check_cancel, NULL,
3254 pool);
3255#endif /* !VBOX */
3256
3257 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
3258 if (err)
3259 {
3260 /* For argument-related problems, suggest using the 'help'
3261 subcommand. */
3262 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3263 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3264 {
3265 err = svn_error_quick_wrap(err,
3266 _("Try 'svnsync help' for more info"));
3267 }
3268 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3269 svn_error_clear(err);
3270
3271 return EXIT_FAILURE;
3272 }
3273
3274 svn_pool_destroy(pool);
3275
3276 return EXIT_SUCCESS;
3277}
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