VirtualBox

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

Last change on this file since 78327 was 76026, checked in by vboxsync, 6 years ago

svnsync-vbox: handle tricky cases better: deleting of files which are changed and renamed in a directory which also got renamed (only way is to ignore the error, found no way to detect) and also handle adding files which get renamed in the same changeset (i.e. newly exported and renamed)

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