VirtualBox

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

Last change on this file since 108190 was 108190, checked in by vboxsync, 3 days ago

svnsync-vbox: add a publicly visible revprop for xref between internal source repo and the public subset

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