VirtualBox

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

最後變更 在這個檔案從48252是 48252,由 vboxsync 提交於 11 年 前

svnsync-vbox/main.c: fix some bugs which showed up with the recent export problems

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

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