VirtualBox

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

最後變更 在這個檔案從64529是 63568,由 vboxsync 提交於 8 年 前

scm: cleaning up todos

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

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