VirtualBox

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

Last change on this file since 29899 was 29684, checked in by vboxsync, 15 years ago

exported svnsync-vbox to OSE

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 109.5 KB
Line 
1/* $Id: main.c 29684 2010-05-20 11:30:05Z 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 (fb->prev_process)
1979 {
1980 /* File disappears due to changes to the process settings. */
1981 eb->changeset_live = TRUE;
1982 SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
1983 pb->wrapped_node_baton, pool));
1984 fb->ignore_everything = TRUE;
1985 }
1986 }
1987 }
1988#else /* !VBOX */
1989 node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
1990
1991 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
1992 base_revision, pool,
1993 &fb->wrapped_node_baton));
1994
1995 fb->edit_baton = eb;
1996#endif /* !VBOX */
1997 *file_baton = fb;
1998
1999 return SVN_NO_ERROR;
2000}
2001
2002static svn_error_t *
2003apply_textdelta(void *file_baton,
2004 const char *base_checksum,
2005 apr_pool_t *pool,
2006 svn_txdelta_window_handler_t *handler,
2007 void **handler_baton)
2008{
2009 node_baton_t *fb = file_baton;
2010 edit_baton_t *eb = fb->edit_baton;
2011
2012#ifdef VBOX
2013 DX(fprintf(stderr, "apply_textdelta\n");)
2014 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2015 if (fb->process && !fb->ignore_everything)
2016 {
2017 eb->changeset_live = TRUE;
2018 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2019 base_checksum, pool,
2020 handler, handler_baton);
2021 }
2022 else
2023 {
2024 /* Must provide a window handler, there's no way of telling our caller
2025 * to throw away its data as we're not interested. */
2026 *handler = svn_delta_noop_window_handler;
2027 *handler_baton = NULL;
2028 return SVN_NO_ERROR;
2029 }
2030#else /* !VBOX */
2031 return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
2032 base_checksum, pool,
2033 handler, handler_baton);
2034#endif /* VBOX */
2035}
2036
2037static svn_error_t *
2038close_file(void *file_baton,
2039 const char *text_checksum,
2040 apr_pool_t *pool)
2041{
2042 node_baton_t *fb = file_baton;
2043 edit_baton_t *eb = fb->edit_baton;
2044#ifdef VBOX
2045 DX(fprintf(stderr, "close_file\n");)
2046 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2047 if (!fb->process)
2048 return SVN_NO_ERROR;
2049#endif /* VBOX */
2050 return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
2051 text_checksum, pool);
2052}
2053
2054static svn_error_t *
2055absent_file(const char *path,
2056 void *file_baton,
2057 apr_pool_t *pool)
2058{
2059 node_baton_t *fb = file_baton;
2060 edit_baton_t *eb = fb->edit_baton;
2061#ifdef VBOX
2062 DX(fprintf(stderr, "absent_file\n");)
2063 DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
2064 if (!fb->process)
2065 return SVN_NO_ERROR;
2066#endif /* VBOX */
2067 return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
2068}
2069
2070static svn_error_t *
2071close_directory(void *dir_baton,
2072 apr_pool_t *pool)
2073{
2074 node_baton_t *db = dir_baton;
2075 edit_baton_t *eb = db->edit_baton;
2076#ifdef VBOX
2077 DX(fprintf(stderr, "close_directory\n");)
2078 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2079 if (!db->process)
2080 return SVN_NO_ERROR;
2081#endif /* VBOX */
2082 return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
2083}
2084
2085static svn_error_t *
2086absent_directory(const char *path,
2087 void *dir_baton,
2088 apr_pool_t *pool)
2089{
2090 node_baton_t *db = dir_baton;
2091 edit_baton_t *eb = db->edit_baton;
2092#ifdef VBOX
2093 DX(fprintf(stderr, "absent_directory\n");)
2094 DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
2095 if (!db->process)
2096 return SVN_NO_ERROR;
2097#endif /* VBOX */
2098 return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
2099 pool);
2100}
2101
2102static svn_error_t *
2103change_file_prop(void *file_baton,
2104 const char *name,
2105 const svn_string_t *value,
2106 apr_pool_t *pool)
2107{
2108 node_baton_t *fb = file_baton;
2109 edit_baton_t *eb = fb->edit_baton;
2110
2111#ifdef VBOX
2112 DX(fprintf(stderr, "change_file_prop %s\n", name);)
2113 DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
2114#endif /* VBOX */
2115 /* only regular properties can pass over libsvn_ra */
2116 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2117 return SVN_NO_ERROR;
2118#ifdef VBOX
2119 if (!strcmp(name, "cvs2svn:cvs-rev"))
2120 return SVN_NO_ERROR;
2121 if (eb->replace_license)
2122 {
2123 /* Throw away the normal license property and replace it by the value
2124 * of svn:sync-license, if present. */
2125 if (!strcmp(name, SVN_PROP_LICENSE))
2126 return SVN_NO_ERROR;
2127 if (!strcmp(name, SVNSYNC_PROP_LICENSE))
2128 name = SVN_PROP_LICENSE;
2129 }
2130 /* Never export any svn:sync-* properties. */
2131 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2132 return SVN_NO_ERROR;
2133 if (!fb->process || fb->ignore_everything)
2134 return SVN_NO_ERROR;
2135 eb->changeset_live = TRUE;
2136#endif /* VBOX */
2137
2138 return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
2139 name, value, pool);
2140}
2141
2142static svn_error_t *
2143change_dir_prop(void *dir_baton,
2144 const char *name,
2145 const svn_string_t *value,
2146 apr_pool_t *pool)
2147{
2148 node_baton_t *db = dir_baton;
2149 edit_baton_t *eb = db->edit_baton;
2150
2151#ifdef VBOX
2152 DX(fprintf(stderr, "change_dir_prop %s\n", name);)
2153 DX(fprintf(stderr, " %s (ignore_everything %d)\n", db->process ? "EXPORT" : "IGNORE", db->ignore_everything);)
2154#endif /* VBOX */
2155 /* only regular properties can pass over libsvn_ra */
2156 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
2157 return SVN_NO_ERROR;
2158#ifdef VBOX
2159 if (!strcmp(name, "cvs2svn:cvs-rev"))
2160 return SVN_NO_ERROR;
2161 if (eb->replace_externals)
2162 {
2163 /* Throw away the normal externals and replace them by the value of
2164 * svn:sync-externals, if present. */
2165 if (!strcmp(name, SVN_PROP_EXTERNALS))
2166 return SVN_NO_ERROR;
2167 if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
2168 name = SVN_PROP_EXTERNALS;
2169 }
2170 /* Never export any svn:sync-* properties. */
2171 if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
2172 return SVN_NO_ERROR;
2173 if (!db->process || db->ignore_everything)
2174 return SVN_NO_ERROR;
2175 eb->changeset_live = TRUE;
2176#endif /* VBOX */
2177
2178 return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
2179 name, value, pool);
2180}
2181
2182static svn_error_t *
2183close_edit(void *edit_baton,
2184 apr_pool_t *pool)
2185{
2186 edit_baton_t *eb = edit_baton;
2187
2188#ifdef VBOX
2189 DX(fprintf(stderr, "close_edit\n");)
2190 /* Suppress empty commits. No need to record something in the
2191 * repository if the entire contents of a changeset is to be ignored. */
2192 if (eb->start_rev && !eb->changeset_live)
2193 {
2194 DX(fprintf(stderr, " discard empty commit\n");)
2195 SVN_ERR(eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool));
2196 SVN_ERR(svn_cmdline_printf(pool, _("Skipped revision %ld in source "
2197 "repository, empty commit.\n"),
2198 eb->current));
2199 return SVN_NO_ERROR;
2200 }
2201#endif /* VBOX */
2202
2203 /* If we haven't opened the root yet, that means we're transfering
2204 an empty revision, probably because we aren't allowed to see the
2205 contents for some reason. In any event, we need to open the root
2206 and close it again, before we can close out the edit, or the
2207 commit will fail. */
2208
2209 if (! eb->called_open_root)
2210 {
2211 void *baton;
2212#ifdef VBOX
2213 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2214 eb->current, pool,
2215 &baton));
2216#else /* !VBOX */
2217 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
2218 eb->base_revision, pool,
2219 &baton));
2220#endif /* !VBOX */
2221 SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
2222 }
2223
2224 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
2225}
2226
2227/*** Editor factory function ***/
2228
2229/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
2230 * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
2231 * revision on which the driver of this returned editor will be basing
2232 * the commit. TO_URL is the URL of the root of the repository into
2233 * which the commit is being made.
2234 */
2235static svn_error_t *
2236get_sync_editor(const svn_delta_editor_t *wrapped_editor,
2237 void *wrapped_edit_baton,
2238 svn_revnum_t base_revision,
2239#ifdef VBOX
2240 svn_revnum_t start_rev,
2241 svn_revnum_t current,
2242 svn_ra_session_t *prop_session_from,
2243 svn_ra_session_t *prop_session_to,
2244 const char *default_process,
2245 svn_boolean_t replace_externals,
2246 svn_boolean_t replace_license,
2247#endif /* VBOX */
2248 const char *to_url,
2249 const svn_delta_editor_t **editor,
2250 void **edit_baton,
2251 apr_pool_t *pool)
2252{
2253 svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
2254 edit_baton_t *eb = apr_palloc(pool, sizeof(*eb));
2255
2256 tree_editor->set_target_revision = set_target_revision;
2257 tree_editor->open_root = open_root;
2258 tree_editor->delete_entry = delete_entry;
2259 tree_editor->add_directory = add_directory;
2260 tree_editor->open_directory = open_directory;
2261 tree_editor->change_dir_prop = change_dir_prop;
2262 tree_editor->close_directory = close_directory;
2263 tree_editor->absent_directory = absent_directory;
2264 tree_editor->add_file = add_file;
2265 tree_editor->open_file = open_file;
2266 tree_editor->apply_textdelta = apply_textdelta;
2267 tree_editor->change_file_prop = change_file_prop;
2268 tree_editor->close_file = close_file;
2269 tree_editor->absent_file = absent_file;
2270 tree_editor->close_edit = close_edit;
2271
2272 eb->wrapped_editor = wrapped_editor;
2273 eb->wrapped_edit_baton = wrapped_edit_baton;
2274 eb->called_open_root = FALSE;
2275 eb->base_revision = base_revision;
2276#ifdef VBOX
2277 eb->changeset_live = FALSE;
2278 eb->start_rev = start_rev;
2279 eb->current = current;
2280 eb->default_process = default_process;
2281 eb->replace_externals = replace_externals;
2282 eb->replace_license = replace_license;
2283 eb->from_session_prop = prop_session_from;
2284 eb->to_session_prop = prop_session_to;
2285#endif /* VBOX */
2286 eb->to_url = to_url;
2287
2288 *editor = tree_editor;
2289 *edit_baton = eb;
2290
2291 return SVN_NO_ERROR;
2292}
2293
2294
2295
2296
2297/*** `svnsync sync' ***/
2298
2299/* Baton for syncronizing the destination repository while locked. */
2300typedef struct {
2301 apr_hash_t *config;
2302 svn_ra_callbacks2_t *callbacks;
2303 const char *to_url;
2304 svn_revnum_t committed_rev;
2305#ifdef VBOX
2306 svn_revnum_t from_rev;
2307#endif /* VBOX */
2308} sync_baton_t;
2309
2310
2311/* Implements `svn_commit_callback2_t' interface. */
2312static svn_error_t *
2313commit_callback(const svn_commit_info_t *commit_info,
2314 void *baton,
2315 apr_pool_t *pool)
2316{
2317 sync_baton_t *sb = baton;
2318
2319#ifdef VBOX
2320 if (sb->from_rev != commit_info->revision)
2321 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld (%ld in source repository).\n"),
2322 commit_info->revision, sb->from_rev));
2323 else
2324 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2325 commit_info->revision));
2326#else /* !VBOX */
2327 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
2328 commit_info->revision));
2329#endif /* !VBOX */
2330
2331 sb->committed_rev = commit_info->revision;
2332
2333 return SVN_NO_ERROR;
2334}
2335
2336
2337/* Set *FROM_SESSION to an RA session associated with the source
2338 * repository of the syncronization, as determined by reading
2339 * svn:sync- properties from the destination repository (associated
2340 * with TO_SESSION). Set LAST_MERGED_REV to the value of the property
2341 * which records the most recently syncronized revision.
2342*** VBOX
2343 * Set START_REV_STR to the propery which records the starting revision.
2344*** VBOX
2345 *
2346 * CALLBACKS is a vtable of RA callbacks to provide when creating
2347 * *FROM_SESSION. CONFIG is a configuration hash.
2348 */
2349static svn_error_t *
2350open_source_session(svn_ra_session_t **from_session,
2351 svn_string_t **last_merged_rev,
2352#ifdef VBOX
2353 svn_revnum_t *start_rev,
2354#endif /* VBOX */
2355 svn_ra_session_t *to_session,
2356 svn_ra_callbacks2_t *callbacks,
2357 apr_hash_t *config,
2358 void *baton,
2359 apr_pool_t *pool)
2360{
2361#ifdef VBOX
2362 svn_string_t *start_rev_str;
2363#endif /* VBOX */
2364 svn_string_t *from_url, *from_uuid;
2365 const char *uuid;
2366
2367 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2368 &from_url, pool));
2369 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
2370 &from_uuid, pool));
2371 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
2372 last_merged_rev, pool));
2373#ifdef VBOX
2374 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_START_REV,
2375 &start_rev_str, pool));
2376#endif /* VBOX */
2377
2378#ifdef VBOX
2379 if (! from_url || ! from_uuid || ! *last_merged_rev || ! start_rev_str)
2380#else /* !VBOX */
2381 if (! from_url || ! from_uuid || ! *last_merged_rev)
2382#endif /* !VBOX */
2383 return svn_error_create
2384 (APR_EINVAL, NULL, _("Destination repository has not been initialized"));
2385
2386#ifdef VBOX
2387 *start_rev = SVN_STR_TO_REV(start_rev_str->data);
2388#endif /* VBOX */
2389
2390#ifdef VBOX
2391 SVN_ERR(svn_ra_open3(from_session, from_url->data, NULL, callbacks, baton,
2392 config, pool));
2393#else /* !VBOX */
2394 SVN_ERR(svn_ra_open2(from_session, from_url->data, callbacks, baton,
2395 config, pool));
2396#endif /* !VBOX */
2397
2398 SVN_ERR(check_if_session_is_at_repos_root(*from_session, from_url->data,
2399 pool));
2400
2401 /* Ok, now sanity check the UUID of the source repository, it
2402 wouldn't be a good thing to sync from a different repository. */
2403
2404#ifdef VBOX
2405 SVN_ERR(svn_ra_get_uuid2(*from_session, &uuid, pool));
2406#else /* !VBOX */
2407 SVN_ERR(svn_ra_get_uuid(*from_session, &uuid, pool));
2408#endif /* !VBOX */
2409
2410 if (strcmp(uuid, from_uuid->data) != 0)
2411 return svn_error_createf(APR_EINVAL, NULL,
2412 _("UUID of source repository (%s) does not "
2413 "match expected UUID (%s)"),
2414 uuid, from_uuid->data);
2415
2416 return SVN_NO_ERROR;
2417}
2418
2419
2420/* Syncronize the repository associated with RA session TO_SESSION,
2421 * using information found in baton B, while the repository is
2422 * locked. Implements `with_locked_func_t' interface.
2423 */
2424static svn_error_t *
2425do_synchronize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2426{
2427 svn_string_t *last_merged_rev;
2428 svn_revnum_t from_latest, current;
2429 svn_ra_session_t *from_session;
2430 sync_baton_t *baton = b;
2431 apr_pool_t *subpool;
2432 svn_string_t *currently_copying;
2433 svn_revnum_t to_latest, copying, last_merged;
2434#ifdef VBOX
2435 svn_revnum_t start_rev;
2436 svn_string_t *from_url;
2437 svn_string_t *default_process;
2438 svn_string_t *replace_externals_str;
2439 svn_boolean_t replace_externals;
2440 svn_string_t *replace_license_str;
2441 svn_boolean_t replace_license;
2442 svn_string_t *ignoreprop;
2443 svn_ra_session_t *from_session_prop;
2444 svn_ra_session_t *to_session_prop;
2445#endif /* VBOX */
2446
2447#ifdef VBOX
2448 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2449 to_session, baton->callbacks, baton->config,
2450 baton, pool));
2451 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
2452 &from_url, pool));
2453 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS,
2454 &default_process, pool));
2455 if (!default_process)
2456 default_process = svn_string_create("export", pool);
2457 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_EXTERNALS,
2458 &replace_externals_str, pool));
2459 replace_externals = !!replace_externals_str;
2460 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_LICENSE,
2461 &replace_license_str, pool));
2462 replace_license = !!replace_license_str;
2463 SVN_ERR(svn_ra_open3(&from_session_prop, from_url->data, NULL,
2464 baton->callbacks, baton, baton->config, pool));
2465 SVN_ERR(svn_ra_open3(&to_session_prop, baton->to_url, NULL,
2466 baton->callbacks, baton, baton->config, pool));
2467#else /* !VBOX */
2468 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2469 baton->callbacks, baton->config, baton, pool));
2470#endif /* !VBOX */
2471
2472 /* Check to see if we have revprops that still need to be copied for
2473 a prior revision we didn't finish copying. But first, check for
2474 state sanity. Remember, mirroring is not an atomic action,
2475 because revision properties are copied separately from the
2476 revision's contents.
2477
2478 So, any time that currently-copying is not set, then
2479 last-merged-rev should be the HEAD revision of the destination
2480 repository. That is, if we didn't fall over in the middle of a
2481 previous syncronization, then our destination repository should
2482 have exactly as many revisions in it as we've syncronized.
2483
2484 Alternately, if currently-copying *is* set, it must
2485 be either last-merged-rev or last-merged-rev + 1, and the HEAD
2486 revision must be equal to either last-merged-rev or
2487 currently-copying. If this is not the case, somebody has meddled
2488 with the destination without using svnsync.
2489 */
2490
2491 SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
2492 &currently_copying, pool));
2493
2494#ifndef VBOX
2495 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2496#endif /* !VBOX */
2497
2498 last_merged = SVN_STR_TO_REV(last_merged_rev->data);
2499
2500#ifdef VBOX
2501 if (start_rev)
2502 {
2503 /* Fake the destination repository revnum to be what the complete sync
2504 * code expects. TODO: this probably breaks continuing after an abort.*/
2505 to_latest = last_merged;
2506 }
2507 else
2508 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
2509#endif /* VBOX */
2510
2511 if (currently_copying)
2512 {
2513 copying = SVN_STR_TO_REV(currently_copying->data);
2514
2515 if ((copying < last_merged)
2516 || (copying > (last_merged + 1))
2517 || ((to_latest != last_merged) && (to_latest != copying)))
2518 {
2519 return svn_error_createf
2520 (APR_EINVAL, NULL,
2521 _("Revision being currently copied (%ld), last merged revision "
2522 "(%ld), and destination HEAD (%ld) are inconsistent; have you "
2523 "committed to the destination without using svnsync?"),
2524 copying, last_merged, to_latest);
2525 }
2526 else if (copying == to_latest)
2527 {
2528 if (copying > last_merged)
2529 {
2530#ifdef VBOX
2531/* TODO fix use of from/to revision numbers. */
2532 SVN_ERR(copy_revprops(from_session, to_session,
2533 to_latest, to_latest, TRUE, pool));
2534#else /* !VBOX */
2535 SVN_ERR(copy_revprops(from_session, to_session,
2536 to_latest, TRUE, pool));
2537#endif /* !VBOX */
2538 last_merged = copying;
2539 last_merged_rev = svn_string_create
2540 (apr_psprintf(pool, "%ld", last_merged), pool);
2541 }
2542
2543 /* Now update last merged rev and drop currently changing.
2544 Note that the order here is significant, if we do them
2545 in the wrong order there are race conditions where we
2546 end up not being able to tell if there have been bogus
2547 (i.e. non-svnsync) commits to the dest repository. */
2548
2549 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2550 SVNSYNC_PROP_LAST_MERGED_REV,
2551 last_merged_rev, pool));
2552 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2553 SVNSYNC_PROP_CURRENTLY_COPYING,
2554 NULL, pool));
2555 }
2556 /* If copying > to_latest, then we just fall through to
2557 attempting to copy the revision again. */
2558 }
2559 else
2560 {
2561 if (to_latest != last_merged)
2562 {
2563 return svn_error_createf
2564 (APR_EINVAL, NULL,
2565 _("Destination HEAD (%ld) is not the last merged revision (%ld); "
2566 "have you committed to the destination without using svnsync?"),
2567 to_latest, last_merged);
2568 }
2569 }
2570
2571 /* Now check to see if there are any revisions to copy. */
2572
2573 SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
2574
2575 if (from_latest < atol(last_merged_rev->data))
2576 return SVN_NO_ERROR;
2577
2578 subpool = svn_pool_create(pool);
2579
2580 /* Ok, so there are new revisions, iterate over them copying them
2581 into the destination repository. */
2582
2583 for (current = atol(last_merged_rev->data) + 1;
2584 current <= from_latest;
2585 ++current)
2586 {
2587 const svn_delta_editor_t *commit_editor;
2588 const svn_delta_editor_t *cancel_editor;
2589 const svn_delta_editor_t *sync_editor;
2590 void *commit_baton;
2591 void *cancel_baton;
2592 void *sync_baton;
2593#ifdef VBOX
2594 apr_hash_t *logrevprop;
2595#endif /* VBOX */
2596
2597 svn_pool_clear(subpool);
2598
2599 /* We set this property so that if we error out for some reason
2600 we can later determine where we were in the process of
2601 merging a revision. If we had committed the change, but we
2602 hadn't finished copying the revprops we need to know that, so
2603 we can go back and finish the job before we move on.
2604
2605 NOTE: We have to set this before we start the commit editor,
2606 because ra_svn doesn't let you change rev props during a
2607 commit. */
2608 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2609 SVNSYNC_PROP_CURRENTLY_COPYING,
2610 svn_string_createf(subpool, "%ld",
2611 current),
2612 subpool));
2613
2614 /* The actual copy is just a replay hooked up to a commit. */
2615
2616#ifdef VBOX
2617 logrevprop = apr_hash_make(pool);
2618 apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
2619 svn_string_create("", pool));
2620 SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor,
2621 &commit_baton,
2622 logrevprop,
2623 commit_callback, baton,
2624 NULL, FALSE, subpool));
2625#else /* !VBOX */
2626 SVN_ERR(svn_ra_get_commit_editor2(to_session, &commit_editor,
2627 &commit_baton,
2628 "", /* empty log */
2629 commit_callback, baton,
2630 NULL, FALSE, subpool));
2631#endif /* !VBOX */
2632
2633 /* There's one catch though, the diff shows us props we can't
2634 send over the RA interface, so we need an editor that's smart
2635 enough to filter those out for us. */
2636
2637#ifdef VBOX
2638 baton->from_rev = current;
2639 baton->committed_rev = SVN_INVALID_REVNUM;
2640 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2641 start_rev, current, from_session_prop,
2642 to_session_prop, default_process->data,
2643 replace_externals, replace_license,
2644 baton->to_url, &sync_editor, &sync_baton,
2645 subpool));
2646#else /* !VBOX */
2647 SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
2648 baton->to_url, &sync_editor, &sync_baton,
2649 subpool));
2650#endif /* !VBOX */
2651
2652 SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
2653 sync_editor, sync_baton,
2654 &cancel_editor,
2655 &cancel_baton,
2656 subpool));
2657
2658#ifdef VBOX
2659 /* If svn:sync-ignore-changeset revprop exists in changeset, skip it. */
2660 SVN_ERR(svn_ra_rev_prop(from_session, current,
2661 SVNSYNC_PROP_IGNORE_CHANGESET,
2662 &ignoreprop, subpool));
2663 if (!ignoreprop)
2664 SVN_ERR(svn_ra_replay(from_session, current, start_rev, TRUE,
2665 cancel_editor, cancel_baton, subpool));
2666#else /* !VBOX */
2667 SVN_ERR(svn_ra_replay(from_session, current, 0, TRUE,
2668 cancel_editor, cancel_baton, subpool));
2669#endif /* !VBOX */
2670
2671 SVN_ERR(cancel_editor->close_edit(cancel_baton, subpool));
2672
2673#ifdef VBOX
2674 if (!start_rev)
2675 {
2676 /* Sanity check that we actually committed the revision we meant to. */
2677 if (baton->committed_rev != current)
2678 return svn_error_createf
2679 (APR_EINVAL, NULL,
2680 _("Commit created rev %ld but should have created %ld"),
2681 baton->committed_rev, current);
2682 }
2683#else /* !VBOX */
2684 /* Sanity check that we actually committed the revision we meant to. */
2685 if (baton->committed_rev != current)
2686 return svn_error_createf
2687 (APR_EINVAL, NULL,
2688 _("Commit created rev %ld but should have created %ld"),
2689 baton->committed_rev, current);
2690#endif /* !VBOX */
2691
2692 /* Ok, we're done with the data, now we just need to do the
2693 revprops and we're all set. */
2694
2695#ifdef VBOX
2696 if (SVN_IS_VALID_REVNUM(baton->committed_rev))
2697 {
2698 SVN_ERR(copy_revprops(from_session, to_session, current,
2699 baton->committed_rev, TRUE, subpool));
2700
2701 /* Add a revision cross-reference revprop. */
2702 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2703 apr_psprintf(subpool,
2704 SVNSYNC_PROP_REV__FMT,
2705 current),
2706 svn_string_create(apr_psprintf(subpool,
2707 "%ld",
2708 baton->committed_rev),
2709 subpool),
2710 subpool));
2711 }
2712 else
2713 {
2714 /* Add a revision cross-reference revprop for an empty commit,
2715 * referring to the previous commit (this avoids unnecessary copy_file
2716 * operation just because a source file was not modified when it
2717 * appears in the destination repository. */
2718 SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, subpool));
2719 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2720 apr_psprintf(subpool,
2721 SVNSYNC_PROP_REV__FMT,
2722 current),
2723 svn_string_create(apr_psprintf(subpool,
2724 "%ld",
2725 to_latest),
2726 subpool),
2727 subpool));
2728 }
2729#else /* !VBOX */
2730 SVN_ERR(copy_revprops(from_session, to_session, current, TRUE, subpool));
2731#endif /* !VBOX */
2732
2733 /* Ok, we're done, bring the last-merged-rev property up to date. */
2734
2735 SVN_ERR(svn_ra_change_rev_prop
2736 (to_session,
2737 0,
2738 SVNSYNC_PROP_LAST_MERGED_REV,
2739 svn_string_create(apr_psprintf(subpool, "%ld", current),
2740 subpool),
2741 subpool));
2742
2743 /* And finally drop the currently copying prop, since we're done
2744 with this revision. */
2745
2746 SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
2747 SVNSYNC_PROP_CURRENTLY_COPYING,
2748 NULL, subpool));
2749 }
2750
2751 return SVN_NO_ERROR;
2752}
2753
2754
2755/* SUBCOMMAND: sync */
2756static svn_error_t *
2757synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2758{
2759 svn_ra_callbacks2_t callbacks = { 0 };
2760 svn_ra_session_t *to_session;
2761 opt_baton_t *opt_baton = b;
2762 apr_array_header_t *args;
2763 sync_baton_t baton;
2764 const char *to_url;
2765
2766 SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
2767
2768 to_url = svn_path_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
2769
2770 if (! svn_path_is_url(to_url))
2771 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2772 _("Path '%s' is not a URL"), to_url);
2773
2774 callbacks.open_tmp_file = open_tmp_file;
2775 callbacks.auth_baton = opt_baton->auth_baton;
2776
2777 baton.callbacks = &callbacks;
2778 baton.config = opt_baton->config;
2779 baton.to_url = to_url;
2780
2781#ifdef VBOX
2782 SVN_ERR(svn_ra_open3(&to_session, to_url, NULL,
2783 baton.callbacks, &baton, baton.config, pool));
2784#else /* !VBOX */
2785 SVN_ERR(svn_ra_open2(&to_session,
2786 to_url,
2787 baton.callbacks,
2788 &baton,
2789 baton.config,
2790 pool));
2791#endif /* !VBOX */
2792
2793 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
2794
2795 SVN_ERR(with_locked(to_session, do_synchronize, &baton, pool));
2796
2797 return SVN_NO_ERROR;
2798}
2799
2800
2801
2802
2803/*** `svnsync copy-revprops' ***/
2804
2805
2806/* Baton for copying revision properties to the destination repository
2807 * while locked.
2808 */
2809typedef struct {
2810 apr_hash_t *config;
2811 svn_ra_callbacks2_t *callbacks;
2812 const char *to_url;
2813 svn_revnum_t rev;
2814} copy_revprops_baton_t;
2815
2816
2817/* Copy revision properties to the repository associated with RA
2818 * session TO_SESSION, using information found in baton B, while the
2819 * repository is locked. Implements `with_locked_func_t' interface.
2820 */
2821static svn_error_t *
2822do_copy_revprops(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
2823{
2824 copy_revprops_baton_t *baton = b;
2825 svn_ra_session_t *from_session;
2826 svn_string_t *last_merged_rev;
2827#ifdef VBOX
2828 svn_revnum_t start_rev;
2829#endif /* VBOX */
2830
2831#ifdef VBOX
2832 SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
2833 to_session, baton->callbacks, baton->config,
2834 baton, pool));
2835 if (start_rev)
2836 return svn_error_create
2837 (APR_EINVAL, NULL, _("Cannot copy revprops for repositories using "
2838 "the start-rev feature (unimplemented)"));
2839#else /* !VBOX */
2840 SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
2841 baton->callbacks, baton->config, baton, pool));
2842#endif /* !VBOX */
2843
2844 if (baton->rev > SVN_STR_TO_REV(last_merged_rev->data))
2845 return svn_error_create
2846 (APR_EINVAL, NULL, _("Cannot copy revprops for a revision that has not "
2847 "been synchronized yet"));
2848
2849#ifdef VBOX
2850 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, baton->rev, FALSE, pool));
2851#else /* !VBOX */
2852 SVN_ERR(copy_revprops(from_session, to_session, baton->rev, FALSE, pool));
2853#endif /* !VBOX */
2854
2855 return SVN_NO_ERROR;
2856}
2857
2858
2859/* SUBCOMMAND: copy-revprops */
2860static svn_error_t *
2861copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
2862{
2863 svn_ra_callbacks2_t callbacks = { 0 };
2864 svn_ra_session_t *to_session;
2865 opt_baton_t *opt_baton = b;
2866 apr_array_header_t *args;
2867 copy_revprops_baton_t baton;
2868 const char *to_url;
2869 svn_revnum_t revision = SVN_INVALID_REVNUM;
2870 char *digits_end = NULL;
2871
2872 SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
2873
2874 to_url = svn_path_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
2875 revision = strtol(APR_ARRAY_IDX(args, 1, const char *), &digits_end, 10);
2876
2877 if (! svn_path_is_url(to_url))
2878 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2879 _("Path '%s' is not a URL"), to_url);
2880 if ((! SVN_IS_VALID_REVNUM(revision)) || (! digits_end) || *digits_end)
2881 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2882 _("Invalid revision number"));
2883
2884 callbacks.open_tmp_file = open_tmp_file;
2885 callbacks.auth_baton = opt_baton->auth_baton;
2886
2887 baton.callbacks = &callbacks;
2888 baton.config = opt_baton->config;
2889 baton.to_url = to_url;
2890 baton.rev = revision;
2891
2892#ifdef VBOX
2893 SVN_ERR(svn_ra_open3(&to_session, to_url, NULL,
2894 baton.callbacks, &baton, baton.config, pool));
2895#else /* !VBOX */
2896 SVN_ERR(svn_ra_open2(&to_session,
2897 to_url,
2898 baton.callbacks,
2899 &baton,
2900 baton.config,
2901 pool));
2902#endif /* !VBOX */
2903
2904 SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
2905
2906 SVN_ERR(with_locked(to_session, do_copy_revprops, &baton, pool));
2907
2908 return SVN_NO_ERROR;
2909}
2910
2911
2912
2913
2914/*** `svnsync help' ***/
2915
2916
2917/* SUBCOMMAND: help */
2918static svn_error_t *
2919help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2920{
2921 opt_baton_t *opt_baton = baton;
2922
2923 const char *header =
2924 _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
2925 "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
2926 "Type 'svnsync --version' to see the program version and RA modules.\n"
2927 "\n"
2928 "Available subcommands:\n");
2929
2930 const char *ra_desc_start
2931 = _("The following repository access (RA) modules are available:\n\n");
2932
2933 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
2934 pool);
2935
2936 SVN_ERR(svn_ra_print_modules(version_footer, pool));
2937
2938#ifdef VBOX
2939 SVN_ERR(svn_opt_print_help3(os, "svnsync",
2940 opt_baton ? opt_baton->version : FALSE,
2941 FALSE, version_footer->data, header,
2942 svnsync_cmd_table, svnsync_options, NULL,
2943 NULL, pool));
2944#else /* !VBOX */
2945 SVN_ERR(svn_opt_print_help(os, "svnsync",
2946 opt_baton ? opt_baton->version : FALSE,
2947 FALSE, version_footer->data, header,
2948 svnsync_cmd_table, svnsync_options, NULL,
2949 pool));
2950#endif /* !VBOX */
2951
2952 return SVN_NO_ERROR;
2953}
2954
2955
2956
2957
2958/*** Main ***/
2959
2960int
2961main(int argc, const char *argv[])
2962{
2963#ifdef VBOX
2964 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2965#else /* !VBOX */
2966 const svn_opt_subcommand_desc_t *subcommand = NULL;
2967#endif /* !VBOX */
2968 apr_array_header_t *received_opts;
2969 opt_baton_t opt_baton;
2970 svn_config_t *config;
2971 apr_status_t apr_err;
2972 apr_getopt_t *os;
2973 apr_pool_t *pool;
2974 svn_error_t *err;
2975 int opt_id, i;
2976
2977 if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
2978 {
2979 return EXIT_FAILURE;
2980 }
2981
2982 err = check_lib_versions();
2983 if (err)
2984 {
2985 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
2986 return EXIT_FAILURE;
2987 }
2988
2989 pool = svn_pool_create(NULL);
2990
2991 err = svn_ra_initialize(pool);
2992 if (err)
2993 {
2994 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
2995 return EXIT_FAILURE;
2996 }
2997
2998 memset(&opt_baton, 0, sizeof(opt_baton));
2999
3000 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
3001
3002 if (argc <= 1)
3003 {
3004 help_cmd(NULL, NULL, pool);
3005 svn_pool_destroy(pool);
3006 return EXIT_FAILURE;
3007 }
3008
3009 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
3010 if (err)
3011 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3012
3013 os->interleave = 1;
3014
3015 for (;;)
3016 {
3017 const char *opt_arg;
3018
3019 apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
3020 if (APR_STATUS_IS_EOF(apr_err))
3021 break;
3022 else if (apr_err)
3023 {
3024 help_cmd(NULL, NULL, pool);
3025 svn_pool_destroy(pool);
3026 return EXIT_FAILURE;
3027 }
3028
3029 APR_ARRAY_PUSH(received_opts, int) = opt_id;
3030
3031 switch (opt_id)
3032 {
3033 case svnsync_opt_non_interactive:
3034 opt_baton.non_interactive = TRUE;
3035 break;
3036
3037 case svnsync_opt_no_auth_cache:
3038 opt_baton.no_auth_cache = TRUE;
3039 break;
3040
3041 case svnsync_opt_auth_username:
3042 opt_baton.auth_username = opt_arg;
3043 break;
3044
3045 case svnsync_opt_auth_password:
3046 opt_baton.auth_password = opt_arg;
3047 break;
3048
3049 case svnsync_opt_config_dir:
3050 opt_baton.config_dir = opt_arg;
3051 break;
3052
3053#ifdef VBOX
3054 case svnsync_opt_start_rev:
3055 opt_baton.start_rev = SVN_STR_TO_REV(opt_arg);
3056 break;
3057
3058 case svnsync_opt_default_process:
3059 opt_baton.default_process = opt_arg;
3060 break;
3061
3062 case svnsync_opt_replace_externals:
3063 opt_baton.replace_externals = TRUE;
3064 break;
3065
3066 case svnsync_opt_replace_license:
3067 opt_baton.replace_license = TRUE;
3068 break;
3069#endif /* VBOX */
3070
3071 case svnsync_opt_version:
3072 opt_baton.version = TRUE;
3073 break;
3074
3075 case '?':
3076 case 'h':
3077 opt_baton.help = TRUE;
3078 break;
3079
3080 default:
3081 {
3082 help_cmd(NULL, NULL, pool);
3083 svn_pool_destroy(pool);
3084 return EXIT_FAILURE;
3085 }
3086 }
3087 }
3088
3089 if (opt_baton.help)
3090#ifdef VBOX
3091 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
3092#else /* !VBOX */
3093 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table, "help");
3094#endif /* !VBOX */
3095
3096 if (subcommand == NULL)
3097 {
3098 if (os->ind >= os->argc)
3099 {
3100 if (opt_baton.version)
3101 {
3102 /* Use the "help" subcommand to handle the "--version" option. */
3103#ifdef VBOX
3104 static const svn_opt_subcommand_desc2_t pseudo_cmd =
3105#else /* !VBOX */
3106 static const svn_opt_subcommand_desc_t pseudo_cmd =
3107#endif /* !VBOX */
3108 { "--version", help_cmd, {0}, "",
3109 {svnsync_opt_version, /* must accept its own option */
3110 } };
3111
3112 subcommand = &pseudo_cmd;
3113 }
3114 else
3115 {
3116 help_cmd(NULL, NULL, pool);
3117 svn_pool_destroy(pool);
3118 return EXIT_FAILURE;
3119 }
3120 }
3121 else
3122 {
3123 const char *first_arg = os->argv[os->ind++];
3124#ifdef VBOX
3125 subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
3126 first_arg);
3127#else /* !VBOX */
3128 subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table,
3129 first_arg);
3130#endif /* !VBOX */
3131 if (subcommand == NULL)
3132 {
3133 help_cmd(NULL, NULL, pool);
3134 svn_pool_destroy(pool);
3135 return EXIT_FAILURE;
3136 }
3137 }
3138 }
3139
3140 for (i = 0; i < received_opts->nelts; ++i)
3141 {
3142 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3143
3144 if (opt_id == 'h' || opt_id == '?')
3145 continue;
3146
3147#ifdef VBOX
3148 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3149#else /* !VBOX */
3150 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
3151#endif /* !VBOX */
3152 {
3153 const char *optstr;
3154#ifdef VBOX
3155 const apr_getopt_option_t *badopt =
3156 svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
3157 pool);
3158#else /* !VBOX */
3159 const apr_getopt_option_t *badopt =
3160 svn_opt_get_option_from_code(opt_id, svnsync_options);
3161#endif /* !VBOX */
3162 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3163 if (subcommand->name[0] == '-')
3164 help_cmd(NULL, NULL, pool);
3165 else
3166 svn_error_clear
3167 (svn_cmdline_fprintf
3168 (stderr, pool, _("subcommand '%s' doesn't accept option '%s'\n"
3169 "Type 'svnsync help %s' for usage.\n"),
3170 subcommand->name, optstr, subcommand->name));
3171 svn_pool_destroy(pool);
3172 return EXIT_FAILURE;
3173 }
3174 }
3175
3176 err = svn_config_get_config(&opt_baton.config, NULL, pool);
3177 if (err)
3178 return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
3179
3180 config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
3181 APR_HASH_KEY_STRING);
3182
3183 apr_signal(SIGINT, signal_handler);
3184
3185#ifdef SIGBREAK
3186 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
3187 apr_signal(SIGBREAK, signal_handler);
3188#endif
3189
3190#ifdef SIGHUP
3191 apr_signal(SIGHUP, signal_handler);
3192#endif
3193
3194#ifdef SIGTERM
3195 apr_signal(SIGTERM, signal_handler);
3196#endif
3197
3198#ifdef SIGPIPE
3199 /* Disable SIGPIPE generation for the platforms that have it. */
3200 apr_signal(SIGPIPE, SIG_IGN);
3201#endif
3202
3203#ifdef SIGXFSZ
3204 /* Disable SIGXFSZ generation for the platforms that have it,
3205 otherwise working with large files when compiled against an APR
3206 that doesn't have large file support will crash the program,
3207 which is uncool. */
3208 apr_signal(SIGXFSZ, SIG_IGN);
3209#endif
3210
3211#ifdef VBOX
3212 err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
3213 opt_baton.non_interactive,
3214 opt_baton.auth_username,
3215 opt_baton.auth_password,
3216 opt_baton.config_dir,
3217 opt_baton.no_auth_cache,
3218 1,
3219 config,
3220 check_cancel, NULL,
3221 pool);
3222 if (!err)
3223 err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
3224 opt_baton.non_interactive,
3225 opt_baton.auth_username,
3226 opt_baton.auth_password,
3227 opt_baton.config_dir,
3228 opt_baton.no_auth_cache,
3229 1,
3230 config,
3231 check_cancel, NULL,
3232 pool);
3233#else /* !VBOX */
3234 err = svn_cmdline_setup_auth_baton(&opt_baton.auth_baton,
3235 opt_baton.non_interactive,
3236 opt_baton.auth_username,
3237 opt_baton.auth_password,
3238 opt_baton.config_dir,
3239 opt_baton.no_auth_cache,
3240 config,
3241 check_cancel, NULL,
3242 pool);
3243#endif /* !VBOX */
3244
3245 err = (*subcommand->cmd_func)(os, &opt_baton, pool);
3246 if (err)
3247 {
3248 /* For argument-related problems, suggest using the 'help'
3249 subcommand. */
3250 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3251 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3252 {
3253 err = svn_error_quick_wrap(err,
3254 _("Try 'svnsync help' for more info"));
3255 }
3256 svn_handle_error2(err, stderr, FALSE, "svnsync: ");
3257 svn_error_clear(err);
3258
3259 return EXIT_FAILURE;
3260 }
3261
3262 svn_pool_destroy(pool);
3263
3264 return EXIT_SUCCESS;
3265}
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