1 | /* |
---|
2 | * ==================================================================== |
---|
3 | * Copyright (c) 2005-2008 CollabNet. All rights reserved. |
---|
4 | * |
---|
5 | * This software is licensed as described in the file COPYING, which |
---|
6 | * you should have received as part of this distribution. The terms |
---|
7 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
8 | * If newer versions of this license are posted there, you may use a |
---|
9 | * newer version instead, at your option. |
---|
10 | * |
---|
11 | * This software consists of voluntary contributions made by many |
---|
12 | * individuals. For exact contribution history, see the revision |
---|
13 | * history and logs, available at http://subversion.tigris.org/. |
---|
14 | * ==================================================================== |
---|
15 | */ |
---|
16 | |
---|
17 | #include "svn_cmdline.h" |
---|
18 | #include "svn_config.h" |
---|
19 | #include "svn_pools.h" |
---|
20 | #include "svn_delta.h" |
---|
21 | #include "svn_path.h" |
---|
22 | #include "svn_props.h" |
---|
23 | #include "svn_auth.h" |
---|
24 | #include "svn_opt.h" |
---|
25 | #include "svn_ra.h" |
---|
26 | |
---|
27 | #include "private/svn_opt_private.h" |
---|
28 | |
---|
29 | #include "svn_private_config.h" |
---|
30 | |
---|
31 | #include <apr_network_io.h> |
---|
32 | #include <apr_signal.h> |
---|
33 | #include <apr_uuid.h> |
---|
34 | |
---|
35 | static svn_opt_subcommand_t initialize_cmd, |
---|
36 | synchronize_cmd, |
---|
37 | copy_revprops_cmd, |
---|
38 | info_cmd, |
---|
39 | help_cmd; |
---|
40 | |
---|
41 | enum { |
---|
42 | svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID, |
---|
43 | svnsync_opt_no_auth_cache, |
---|
44 | svnsync_opt_auth_username, |
---|
45 | svnsync_opt_auth_password, |
---|
46 | svnsync_opt_source_username, |
---|
47 | svnsync_opt_source_password, |
---|
48 | svnsync_opt_sync_username, |
---|
49 | svnsync_opt_sync_password, |
---|
50 | svnsync_opt_config_dir, |
---|
51 | svnsync_opt_version, |
---|
52 | svnsync_opt_trust_server_cert |
---|
53 | }; |
---|
54 | |
---|
55 | #define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \ |
---|
56 | svnsync_opt_no_auth_cache, \ |
---|
57 | svnsync_opt_auth_username, \ |
---|
58 | svnsync_opt_auth_password, \ |
---|
59 | svnsync_opt_trust_server_cert, \ |
---|
60 | svnsync_opt_source_username, \ |
---|
61 | svnsync_opt_source_password, \ |
---|
62 | svnsync_opt_sync_username, \ |
---|
63 | svnsync_opt_sync_password, \ |
---|
64 | svnsync_opt_config_dir |
---|
65 | |
---|
66 | static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] = |
---|
67 | { |
---|
68 | { "initialize", initialize_cmd, { "init" }, |
---|
69 | N_("usage: svnsync initialize DEST_URL SOURCE_URL\n" |
---|
70 | "\n" |
---|
71 | "Initialize a destination repository for synchronization from\n" |
---|
72 | "another repository.\n" |
---|
73 | "\n" |
---|
74 | "The destination URL must point to the root of a repository with\n" |
---|
75 | "no committed revisions. The destination repository must allow\n" |
---|
76 | "revision property changes.\n" |
---|
77 | "\n" |
---|
78 | "If the source URL is not the root of a repository, only the\n" |
---|
79 | "specified part of the repository will be synchronized.\n" |
---|
80 | "\n" |
---|
81 | "You should not commit to, or make revision property changes in,\n" |
---|
82 | "the destination repository by any method other than 'svnsync'.\n" |
---|
83 | "In other words, the destination repository should be a read-only\n" |
---|
84 | "mirror of the source repository.\n"), |
---|
85 | { SVNSYNC_OPTS_DEFAULT, 'q' } }, |
---|
86 | { "synchronize", synchronize_cmd, { "sync" }, |
---|
87 | N_("usage: svnsync synchronize DEST_URL\n" |
---|
88 | "\n" |
---|
89 | "Transfer all pending revisions to the destination from the source\n" |
---|
90 | "with which it was initialized.\n"), |
---|
91 | { SVNSYNC_OPTS_DEFAULT, 'q' } }, |
---|
92 | { "copy-revprops", copy_revprops_cmd, { 0 }, |
---|
93 | N_("usage: svnsync copy-revprops DEST_URL [REV[:REV2]]\n" |
---|
94 | "\n" |
---|
95 | "Copy the revision properties in a given range of revisions to the\n" |
---|
96 | "destination from the source with which it was initialized.\n" |
---|
97 | "\n" |
---|
98 | "If REV and REV2 are provided, copy properties for the revisions\n" |
---|
99 | "specified by that range, inclusively. If only REV is provided,\n" |
---|
100 | "copy properties for that revision alone. If REV is not provided,\n" |
---|
101 | "copy properties for all revisions previously transferred to the\n" |
---|
102 | "destination.\n" |
---|
103 | "\n" |
---|
104 | "REV and REV2 must be revisions which were previously transferred\n" |
---|
105 | "to the destination. You may use \"HEAD\" for either revision to\n" |
---|
106 | "mean \"the last revision transferred\".\n"), |
---|
107 | { SVNSYNC_OPTS_DEFAULT, 'q' } }, |
---|
108 | { "info", info_cmd, { 0 }, |
---|
109 | N_("usage: svnsync info DEST_URL\n" |
---|
110 | "\n" |
---|
111 | "Print information about the synchronization destination repository\n" |
---|
112 | "located at DEST_URL.\n"), |
---|
113 | { SVNSYNC_OPTS_DEFAULT } }, |
---|
114 | { "help", help_cmd, { "?", "h" }, |
---|
115 | N_("usage: svnsync help [SUBCOMMAND...]\n" |
---|
116 | "\n" |
---|
117 | "Describe the usage of this program or its subcommands.\n"), |
---|
118 | { 0 } }, |
---|
119 | { NULL, NULL, { 0 }, NULL, { 0 } } |
---|
120 | }; |
---|
121 | |
---|
122 | static const apr_getopt_option_t svnsync_options[] = |
---|
123 | { |
---|
124 | {"quiet", 'q', 0, |
---|
125 | N_("print as little as possible") }, |
---|
126 | {"non-interactive", svnsync_opt_non_interactive, 0, |
---|
127 | N_("do no interactive prompting") }, |
---|
128 | {"no-auth-cache", svnsync_opt_no_auth_cache, 0, |
---|
129 | N_("do not cache authentication tokens") }, |
---|
130 | {"username", svnsync_opt_auth_username, 1, |
---|
131 | N_("specify a username ARG (deprecated;\n" |
---|
132 | " " |
---|
133 | "see --source-username and --sync-username)") }, |
---|
134 | {"password", svnsync_opt_auth_password, 1, |
---|
135 | N_("specify a password ARG (deprecated;\n" |
---|
136 | " " |
---|
137 | "see --source-password and --sync-password)") }, |
---|
138 | {"trust-server-cert", svnsync_opt_trust_server_cert, 0, |
---|
139 | N_("accept unknown SSL server certificates without\n" |
---|
140 | " " |
---|
141 | "prompting (but only with '--non-interactive')") }, |
---|
142 | {"source-username", svnsync_opt_source_username, 1, |
---|
143 | N_("connect to source repository with username ARG") }, |
---|
144 | {"source-password", svnsync_opt_source_password, 1, |
---|
145 | N_("connect to source repository with password ARG") }, |
---|
146 | {"sync-username", svnsync_opt_sync_username, 1, |
---|
147 | N_("connect to sync repository with username ARG") }, |
---|
148 | {"sync-password", svnsync_opt_sync_password, 1, |
---|
149 | N_("connect to sync repository with password ARG") }, |
---|
150 | {"config-dir", svnsync_opt_config_dir, 1, |
---|
151 | N_("read user configuration files from directory ARG")}, |
---|
152 | {"version", svnsync_opt_version, 0, |
---|
153 | N_("show program version information")}, |
---|
154 | {"help", 'h', 0, |
---|
155 | N_("show help on a subcommand")}, |
---|
156 | {NULL, '?', 0, |
---|
157 | N_("show help on a subcommand")}, |
---|
158 | { 0, 0, 0, 0 } |
---|
159 | }; |
---|
160 | |
---|
161 | typedef struct { |
---|
162 | svn_boolean_t non_interactive; |
---|
163 | svn_boolean_t trust_server_cert; |
---|
164 | svn_boolean_t no_auth_cache; |
---|
165 | svn_auth_baton_t *source_auth_baton; |
---|
166 | svn_auth_baton_t *sync_auth_baton; |
---|
167 | const char *source_username; |
---|
168 | const char *source_password; |
---|
169 | const char *sync_username; |
---|
170 | const char *sync_password; |
---|
171 | const char *config_dir; |
---|
172 | apr_hash_t *config; |
---|
173 | svn_boolean_t quiet; |
---|
174 | svn_boolean_t version; |
---|
175 | svn_boolean_t help; |
---|
176 | } opt_baton_t; |
---|
177 | |
---|
178 | |
---|
179 | |
---|
180 | |
---|
181 | /*** Helper functions ***/ |
---|
182 | |
---|
183 | |
---|
184 | /* Global record of whether the user has requested cancellation. */ |
---|
185 | static volatile sig_atomic_t cancelled = FALSE; |
---|
186 | |
---|
187 | |
---|
188 | /* Callback function for apr_signal(). */ |
---|
189 | static void |
---|
190 | signal_handler(int signum) |
---|
191 | { |
---|
192 | apr_signal(signum, SIG_IGN); |
---|
193 | cancelled = TRUE; |
---|
194 | } |
---|
195 | |
---|
196 | |
---|
197 | /* Cancellation callback function. */ |
---|
198 | static svn_error_t * |
---|
199 | check_cancel(void *baton) |
---|
200 | { |
---|
201 | if (cancelled) |
---|
202 | return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); |
---|
203 | else |
---|
204 | return SVN_NO_ERROR; |
---|
205 | } |
---|
206 | |
---|
207 | |
---|
208 | /* Check that the version of libraries in use match what we expect. */ |
---|
209 | static svn_error_t * |
---|
210 | check_lib_versions(void) |
---|
211 | { |
---|
212 | static const svn_version_checklist_t checklist[] = |
---|
213 | { |
---|
214 | { "svn_subr", svn_subr_version }, |
---|
215 | { "svn_delta", svn_delta_version }, |
---|
216 | { "svn_ra", svn_ra_version }, |
---|
217 | { NULL, NULL } |
---|
218 | }; |
---|
219 | |
---|
220 | SVN_VERSION_DEFINE(my_version); |
---|
221 | |
---|
222 | return svn_ver_check_list(&my_version, checklist); |
---|
223 | } |
---|
224 | |
---|
225 | |
---|
226 | /* Acquire a lock (of sorts) on the repository associated with the |
---|
227 | * given RA SESSION. |
---|
228 | */ |
---|
229 | static svn_error_t * |
---|
230 | get_lock(svn_ra_session_t *session, apr_pool_t *pool) |
---|
231 | { |
---|
232 | char hostname_str[APRMAXHOSTLEN + 1] = { 0 }; |
---|
233 | svn_string_t *mylocktoken, *reposlocktoken; |
---|
234 | apr_status_t apr_err; |
---|
235 | apr_pool_t *subpool; |
---|
236 | int i; |
---|
237 | |
---|
238 | apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool); |
---|
239 | if (apr_err) |
---|
240 | return svn_error_wrap_apr(apr_err, _("Can't get local hostname")); |
---|
241 | |
---|
242 | mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str, |
---|
243 | svn_uuid_generate(pool)); |
---|
244 | |
---|
245 | subpool = svn_pool_create(pool); |
---|
246 | |
---|
247 | for (i = 0; i < 10; ++i) |
---|
248 | { |
---|
249 | svn_pool_clear(subpool); |
---|
250 | SVN_ERR(check_cancel(NULL)); |
---|
251 | |
---|
252 | SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken, |
---|
253 | subpool)); |
---|
254 | |
---|
255 | if (reposlocktoken) |
---|
256 | { |
---|
257 | /* Did we get it? If so, we're done, otherwise we sleep. */ |
---|
258 | if (strcmp(reposlocktoken->data, mylocktoken->data) == 0) |
---|
259 | return SVN_NO_ERROR; |
---|
260 | else |
---|
261 | { |
---|
262 | SVN_ERR(svn_cmdline_printf |
---|
263 | (pool, _("Failed to get lock on destination " |
---|
264 | "repos, currently held by '%s'\n"), |
---|
265 | reposlocktoken->data)); |
---|
266 | |
---|
267 | apr_sleep(apr_time_from_sec(1)); |
---|
268 | } |
---|
269 | } |
---|
270 | else |
---|
271 | { |
---|
272 | SVN_ERR(svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, |
---|
273 | mylocktoken, subpool)); |
---|
274 | } |
---|
275 | } |
---|
276 | |
---|
277 | return svn_error_createf(APR_EINVAL, NULL, |
---|
278 | "Couldn't get lock on destination repos " |
---|
279 | "after %d attempts\n", i); |
---|
280 | } |
---|
281 | |
---|
282 | |
---|
283 | /* Baton for the various subcommands to share. */ |
---|
284 | typedef struct { |
---|
285 | /* common to all subcommands */ |
---|
286 | apr_hash_t *config; |
---|
287 | svn_ra_callbacks2_t source_callbacks; |
---|
288 | svn_ra_callbacks2_t sync_callbacks; |
---|
289 | svn_boolean_t quiet; |
---|
290 | const char *to_url; |
---|
291 | |
---|
292 | /* initialize only */ |
---|
293 | const char *from_url; |
---|
294 | |
---|
295 | /* synchronize only */ |
---|
296 | svn_revnum_t committed_rev; |
---|
297 | |
---|
298 | /* copy-revprops only */ |
---|
299 | svn_revnum_t start_rev; |
---|
300 | svn_revnum_t end_rev; |
---|
301 | |
---|
302 | } subcommand_baton_t; |
---|
303 | |
---|
304 | typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session, |
---|
305 | subcommand_baton_t *baton, |
---|
306 | apr_pool_t *pool); |
---|
307 | |
---|
308 | |
---|
309 | /* Lock the repository associated with RA SESSION, then execute the |
---|
310 | * given FUNC/BATON pair while holding the lock. Finally, drop the |
---|
311 | * lock once it finishes. |
---|
312 | */ |
---|
313 | static svn_error_t * |
---|
314 | with_locked(svn_ra_session_t *session, |
---|
315 | with_locked_func_t func, |
---|
316 | subcommand_baton_t *baton, |
---|
317 | apr_pool_t *pool) |
---|
318 | { |
---|
319 | svn_error_t *err, *err2; |
---|
320 | |
---|
321 | SVN_ERR(get_lock(session, pool)); |
---|
322 | |
---|
323 | err = func(session, baton, pool); |
---|
324 | |
---|
325 | err2 = svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, NULL, pool); |
---|
326 | if (err2 && err) |
---|
327 | { |
---|
328 | svn_error_clear(err2); /* XXX what to do here? */ |
---|
329 | |
---|
330 | return err; |
---|
331 | } |
---|
332 | else if (err2) |
---|
333 | { |
---|
334 | return err2; |
---|
335 | } |
---|
336 | else |
---|
337 | { |
---|
338 | return err; |
---|
339 | } |
---|
340 | } |
---|
341 | |
---|
342 | |
---|
343 | /* Callback function for the RA session's open_tmp_file() |
---|
344 | * requirements. |
---|
345 | */ |
---|
346 | static svn_error_t * |
---|
347 | open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool) |
---|
348 | { |
---|
349 | return svn_io_open_unique_file3(fp, NULL, NULL, |
---|
350 | svn_io_file_del_on_pool_cleanup, |
---|
351 | pool, pool); |
---|
352 | } |
---|
353 | |
---|
354 | |
---|
355 | /* Return SVN_NO_ERROR iff URL identifies the root directory of the |
---|
356 | * repository associated with RA session SESS. |
---|
357 | */ |
---|
358 | static svn_error_t * |
---|
359 | check_if_session_is_at_repos_root(svn_ra_session_t *sess, |
---|
360 | const char *url, |
---|
361 | apr_pool_t *pool) |
---|
362 | { |
---|
363 | const char *sess_root; |
---|
364 | |
---|
365 | SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool)); |
---|
366 | |
---|
367 | if (strcmp(url, sess_root) == 0) |
---|
368 | return SVN_NO_ERROR; |
---|
369 | else |
---|
370 | return svn_error_createf |
---|
371 | (APR_EINVAL, NULL, |
---|
372 | _("Session is rooted at '%s' but the repos root is '%s'"), |
---|
373 | url, sess_root); |
---|
374 | } |
---|
375 | |
---|
376 | |
---|
377 | /* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from |
---|
378 | * revision REV of the repository associated with RA session SESSION. |
---|
379 | * |
---|
380 | * All allocations will be done in a subpool of POOL. |
---|
381 | */ |
---|
382 | static svn_error_t * |
---|
383 | remove_props_not_in_source(svn_ra_session_t *session, |
---|
384 | svn_revnum_t rev, |
---|
385 | apr_hash_t *source_props, |
---|
386 | apr_hash_t *target_props, |
---|
387 | apr_pool_t *pool) |
---|
388 | { |
---|
389 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
390 | apr_hash_index_t *hi; |
---|
391 | |
---|
392 | for (hi = apr_hash_first(pool, target_props); |
---|
393 | hi; |
---|
394 | hi = apr_hash_next(hi)) |
---|
395 | { |
---|
396 | const void *key; |
---|
397 | |
---|
398 | svn_pool_clear(subpool); |
---|
399 | |
---|
400 | apr_hash_this(hi, &key, NULL, NULL); |
---|
401 | |
---|
402 | /* Delete property if the key can't be found in SOURCE_PROPS. */ |
---|
403 | if (! apr_hash_get(source_props, key, APR_HASH_KEY_STRING)) |
---|
404 | SVN_ERR(svn_ra_change_rev_prop(session, rev, key, NULL, |
---|
405 | subpool)); |
---|
406 | } |
---|
407 | |
---|
408 | svn_pool_destroy(subpool); |
---|
409 | |
---|
410 | return SVN_NO_ERROR; |
---|
411 | } |
---|
412 | |
---|
413 | /* Filter callback function. |
---|
414 | * Takes a property name KEY, and is expected to return TRUE if the property |
---|
415 | * should be filtered out (ie. not be copied to the target list), or FALSE if |
---|
416 | * not. |
---|
417 | */ |
---|
418 | typedef svn_boolean_t (*filter_func_t)(const char *key); |
---|
419 | |
---|
420 | /* Make a new set of properties, by copying those properties in PROPS for which |
---|
421 | * the filter FILTER returns FALSE. |
---|
422 | * |
---|
423 | * The number of filtered properties will be stored in FILTERED_COUNT. |
---|
424 | * |
---|
425 | * The returned set of properties is allocated from POOL. |
---|
426 | */ |
---|
427 | static apr_hash_t * |
---|
428 | filter_props(int *filtered_count, apr_hash_t *props, |
---|
429 | filter_func_t filter, |
---|
430 | apr_pool_t *pool) |
---|
431 | { |
---|
432 | apr_hash_index_t *hi; |
---|
433 | apr_hash_t *filtered = apr_hash_make(pool); |
---|
434 | *filtered_count = 0; |
---|
435 | |
---|
436 | for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi)) |
---|
437 | { |
---|
438 | void *val; |
---|
439 | const void *key; |
---|
440 | apr_ssize_t len; |
---|
441 | |
---|
442 | apr_hash_this(hi, &key, &len, &val); |
---|
443 | |
---|
444 | /* Copy all properties: |
---|
445 | - not matching the exclude pattern if provided OR |
---|
446 | - matching the include pattern if provided */ |
---|
447 | if (!filter || filter(key) == FALSE) |
---|
448 | { |
---|
449 | apr_hash_set(filtered, key, APR_HASH_KEY_STRING, val); |
---|
450 | } |
---|
451 | else |
---|
452 | { |
---|
453 | *filtered_count += 1; |
---|
454 | } |
---|
455 | } |
---|
456 | |
---|
457 | return filtered; |
---|
458 | } |
---|
459 | |
---|
460 | |
---|
461 | /* Write the set of revision properties REV_PROPS to revision REV to the |
---|
462 | * repository associated with RA session SESSION. |
---|
463 | * |
---|
464 | * All allocations will be done in a subpool of POOL. |
---|
465 | */ |
---|
466 | static svn_error_t * |
---|
467 | write_revprops(int *filtered_count, |
---|
468 | svn_ra_session_t *session, |
---|
469 | svn_revnum_t rev, |
---|
470 | apr_hash_t *rev_props, |
---|
471 | apr_pool_t *pool) |
---|
472 | { |
---|
473 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
474 | apr_hash_index_t *hi; |
---|
475 | |
---|
476 | *filtered_count = 0; |
---|
477 | |
---|
478 | for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi)) |
---|
479 | { |
---|
480 | const void *key; |
---|
481 | void *val; |
---|
482 | |
---|
483 | svn_pool_clear(subpool); |
---|
484 | apr_hash_this(hi, &key, NULL, &val); |
---|
485 | |
---|
486 | if (strncmp(key, SVNSYNC_PROP_PREFIX, |
---|
487 | sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0) |
---|
488 | { |
---|
489 | SVN_ERR(svn_ra_change_rev_prop(session, rev, key, val, subpool)); |
---|
490 | } |
---|
491 | else |
---|
492 | { |
---|
493 | *filtered_count += 1; |
---|
494 | } |
---|
495 | } |
---|
496 | |
---|
497 | svn_pool_destroy(subpool); |
---|
498 | |
---|
499 | return SVN_NO_ERROR; |
---|
500 | } |
---|
501 | |
---|
502 | |
---|
503 | static svn_error_t * |
---|
504 | log_properties_copied(svn_boolean_t syncprops_found, |
---|
505 | svn_revnum_t rev, |
---|
506 | apr_pool_t *pool) |
---|
507 | { |
---|
508 | if (syncprops_found) |
---|
509 | SVN_ERR(svn_cmdline_printf(pool, |
---|
510 | _("Copied properties for revision %ld " |
---|
511 | "(%s* properties skipped).\n"), |
---|
512 | rev, SVNSYNC_PROP_PREFIX)); |
---|
513 | else |
---|
514 | SVN_ERR(svn_cmdline_printf(pool, |
---|
515 | _("Copied properties for revision %ld.\n"), |
---|
516 | rev)); |
---|
517 | |
---|
518 | return SVN_NO_ERROR; |
---|
519 | } |
---|
520 | |
---|
521 | /* Copy all the revision properties, except for those that have the |
---|
522 | * "svn:sync-" prefix, from revision REV of the repository associated |
---|
523 | * with RA session FROM_SESSION, to the repository associated with RA |
---|
524 | * session TO_SESSION. |
---|
525 | * |
---|
526 | * If SYNC is TRUE, then properties on the destination revision that |
---|
527 | * do not exist on the source revision will be removed. |
---|
528 | */ |
---|
529 | static svn_error_t * |
---|
530 | copy_revprops(svn_ra_session_t *from_session, |
---|
531 | svn_ra_session_t *to_session, |
---|
532 | svn_revnum_t rev, |
---|
533 | svn_boolean_t sync, |
---|
534 | svn_boolean_t quiet, |
---|
535 | apr_pool_t *pool) |
---|
536 | { |
---|
537 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
538 | apr_hash_t *existing_props, *rev_props; |
---|
539 | int filtered_count = 0; |
---|
540 | |
---|
541 | /* Get the list of revision properties on REV of TARGET. We're only interested |
---|
542 | in the property names, but we'll get the values 'for free'. */ |
---|
543 | if (sync) |
---|
544 | SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool)); |
---|
545 | |
---|
546 | /* Get the list of revision properties on REV of SOURCE. */ |
---|
547 | SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool)); |
---|
548 | |
---|
549 | /* Copy all but the svn:svnsync properties. */ |
---|
550 | SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool)); |
---|
551 | |
---|
552 | /* Delete those properties that were in TARGET but not in SOURCE */ |
---|
553 | if (sync) |
---|
554 | SVN_ERR(remove_props_not_in_source(to_session, rev, |
---|
555 | rev_props, existing_props, pool)); |
---|
556 | |
---|
557 | if (! quiet) |
---|
558 | SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool)); |
---|
559 | |
---|
560 | svn_pool_destroy(subpool); |
---|
561 | |
---|
562 | return SVN_NO_ERROR; |
---|
563 | } |
---|
564 | |
---|
565 | |
---|
566 | /* Return a subcommand baton allocated from POOL and populated with |
---|
567 | data from the provided parameters, which include the global |
---|
568 | OPT_BATON options structure and a handful of other options. Not |
---|
569 | all parameters are used in all subcommands -- see |
---|
570 | subcommand_baton_t's definition for details. */ |
---|
571 | static subcommand_baton_t * |
---|
572 | make_subcommand_baton(opt_baton_t *opt_baton, |
---|
573 | const char *to_url, |
---|
574 | const char *from_url, |
---|
575 | svn_revnum_t start_rev, |
---|
576 | svn_revnum_t end_rev, |
---|
577 | apr_pool_t *pool) |
---|
578 | { |
---|
579 | subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b)); |
---|
580 | b->config = opt_baton->config; |
---|
581 | b->source_callbacks.open_tmp_file = open_tmp_file; |
---|
582 | b->source_callbacks.auth_baton = opt_baton->source_auth_baton; |
---|
583 | b->sync_callbacks.open_tmp_file = open_tmp_file; |
---|
584 | b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton; |
---|
585 | b->quiet = opt_baton->quiet; |
---|
586 | b->to_url = to_url; |
---|
587 | b->from_url = from_url; |
---|
588 | b->start_rev = start_rev; |
---|
589 | b->end_rev = end_rev; |
---|
590 | return b; |
---|
591 | } |
---|
592 | |
---|
593 | |
---|
594 | /*** `svnsync init' ***/ |
---|
595 | |
---|
596 | /* Initialize the repository associated with RA session TO_SESSION, |
---|
597 | * using information found in baton B, while the repository is |
---|
598 | * locked. Implements `with_locked_func_t' interface. |
---|
599 | */ |
---|
600 | static svn_error_t * |
---|
601 | do_initialize(svn_ra_session_t *to_session, |
---|
602 | subcommand_baton_t *baton, |
---|
603 | apr_pool_t *pool) |
---|
604 | { |
---|
605 | svn_ra_session_t *from_session; |
---|
606 | svn_string_t *from_url; |
---|
607 | svn_revnum_t latest; |
---|
608 | const char *uuid, *root_url; |
---|
609 | |
---|
610 | /* First, sanity check to see that we're copying into a brand new repos. */ |
---|
611 | |
---|
612 | SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool)); |
---|
613 | |
---|
614 | if (latest != 0) |
---|
615 | return svn_error_create |
---|
616 | (APR_EINVAL, NULL, |
---|
617 | _("Cannot initialize a repository with content in it")); |
---|
618 | |
---|
619 | /* And check to see if anyone's run initialize on it before... We |
---|
620 | may want a --force option to override this check. */ |
---|
621 | |
---|
622 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, |
---|
623 | &from_url, pool)); |
---|
624 | |
---|
625 | if (from_url) |
---|
626 | return svn_error_createf |
---|
627 | (APR_EINVAL, NULL, |
---|
628 | _("Destination repository is already synchronizing from '%s'"), |
---|
629 | from_url->data); |
---|
630 | |
---|
631 | /* Now fill in our bookkeeping info in the dest repository. */ |
---|
632 | |
---|
633 | SVN_ERR(svn_ra_open3(&from_session, baton->from_url, NULL, |
---|
634 | &(baton->source_callbacks), baton, |
---|
635 | baton->config, pool)); |
---|
636 | SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool)); |
---|
637 | |
---|
638 | /* If we're doing a partial replay, we have to check first if the server |
---|
639 | supports this. */ |
---|
640 | if (svn_path_is_child(root_url, baton->from_url, pool)) |
---|
641 | { |
---|
642 | svn_boolean_t server_supports_partial_replay; |
---|
643 | svn_error_t *err = svn_ra_has_capability(from_session, |
---|
644 | &server_supports_partial_replay, |
---|
645 | SVN_RA_CAPABILITY_PARTIAL_REPLAY, |
---|
646 | pool); |
---|
647 | if (err && err->apr_err == SVN_ERR_UNKNOWN_CAPABILITY) |
---|
648 | { |
---|
649 | svn_error_clear(err); |
---|
650 | server_supports_partial_replay = FALSE; |
---|
651 | } |
---|
652 | |
---|
653 | if (!server_supports_partial_replay) |
---|
654 | return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, NULL, |
---|
655 | NULL); |
---|
656 | } |
---|
657 | |
---|
658 | SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, |
---|
659 | svn_string_create(baton->from_url, pool), |
---|
660 | pool)); |
---|
661 | |
---|
662 | SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool)); |
---|
663 | |
---|
664 | SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID, |
---|
665 | svn_string_create(uuid, pool), pool)); |
---|
666 | |
---|
667 | SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, |
---|
668 | svn_string_create("0", pool), pool)); |
---|
669 | |
---|
670 | /* Finally, copy all non-svnsync revprops from rev 0 of the source |
---|
671 | repos into the dest repos. */ |
---|
672 | |
---|
673 | SVN_ERR(copy_revprops(from_session, to_session, 0, FALSE, |
---|
674 | baton->quiet, pool)); |
---|
675 | |
---|
676 | /* TODO: It would be nice if we could set the dest repos UUID to be |
---|
677 | equal to the UUID of the source repos, at least optionally. That |
---|
678 | way people could check out/log/diff using a local fast mirror, |
---|
679 | but switch --relocate to the actual final repository in order to |
---|
680 | make changes... But at this time, the RA layer doesn't have a |
---|
681 | way to set a UUID. */ |
---|
682 | |
---|
683 | return SVN_NO_ERROR; |
---|
684 | } |
---|
685 | |
---|
686 | |
---|
687 | /* SUBCOMMAND: init */ |
---|
688 | static svn_error_t * |
---|
689 | initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) |
---|
690 | { |
---|
691 | const char *to_url, *from_url; |
---|
692 | svn_ra_session_t *to_session; |
---|
693 | opt_baton_t *opt_baton = b; |
---|
694 | apr_array_header_t *targets; |
---|
695 | subcommand_baton_t *baton; |
---|
696 | |
---|
697 | SVN_ERR(svn_opt__args_to_target_array(&targets, os, |
---|
698 | apr_array_make(pool, 0, |
---|
699 | sizeof(const char *)), |
---|
700 | pool)); |
---|
701 | if (targets->nelts < 2) |
---|
702 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
---|
703 | if (targets->nelts > 2) |
---|
704 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); |
---|
705 | |
---|
706 | to_url = APR_ARRAY_IDX(targets, 0, const char *); |
---|
707 | from_url = APR_ARRAY_IDX(targets, 1, const char *); |
---|
708 | |
---|
709 | if (! svn_path_is_url(to_url)) |
---|
710 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
711 | _("Path '%s' is not a URL"), to_url); |
---|
712 | if (! svn_path_is_url(from_url)) |
---|
713 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
714 | _("Path '%s' is not a URL"), from_url); |
---|
715 | |
---|
716 | baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool); |
---|
717 | SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL, |
---|
718 | &(baton->sync_callbacks), baton, baton->config, pool)); |
---|
719 | SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool)); |
---|
720 | SVN_ERR(with_locked(to_session, do_initialize, baton, pool)); |
---|
721 | |
---|
722 | return SVN_NO_ERROR; |
---|
723 | } |
---|
724 | |
---|
725 | |
---|
726 | |
---|
727 | /*** Synchronization Editor ***/ |
---|
728 | |
---|
729 | /* This editor has a couple of jobs. |
---|
730 | * |
---|
731 | * First, it needs to filter out the propchanges that can't be passed over |
---|
732 | * libsvn_ra. |
---|
733 | * |
---|
734 | * Second, it needs to adjust for the fact that we might not actually have |
---|
735 | * permission to see all of the data from the remote repository, which means |
---|
736 | * we could get revisions that are totally empty from our point of view. |
---|
737 | * |
---|
738 | * Third, it needs to adjust copyfrom paths, adding the root url for the |
---|
739 | * destination repository to the beginning of them. |
---|
740 | */ |
---|
741 | |
---|
742 | |
---|
743 | /* Edit baton */ |
---|
744 | typedef struct { |
---|
745 | const svn_delta_editor_t *wrapped_editor; |
---|
746 | void *wrapped_edit_baton; |
---|
747 | const char *to_url; /* URL we're copying into, for correct copyfrom URLs */ |
---|
748 | svn_boolean_t called_open_root; |
---|
749 | svn_boolean_t got_textdeltas; |
---|
750 | svn_revnum_t base_revision; |
---|
751 | svn_boolean_t quiet; |
---|
752 | svn_boolean_t strip_mergeinfo; /* Are we stripping svn:mergeinfo? */ |
---|
753 | svn_boolean_t migrate_svnmerge; /* Are we converting svnmerge.py data? */ |
---|
754 | svn_boolean_t mergeinfo_stripped; /* Did we strip svn:mergeinfo? */ |
---|
755 | svn_boolean_t svnmerge_migrated; /* Did we convert svnmerge.py data? */ |
---|
756 | svn_boolean_t svnmerge_blocked; /* Was there any blocked svnmerge data? */ |
---|
757 | } edit_baton_t; |
---|
758 | |
---|
759 | |
---|
760 | /* A dual-purpose baton for files and directories. */ |
---|
761 | typedef struct { |
---|
762 | void *edit_baton; |
---|
763 | void *wrapped_node_baton; |
---|
764 | } node_baton_t; |
---|
765 | |
---|
766 | |
---|
767 | /*** Editor vtable functions ***/ |
---|
768 | |
---|
769 | static svn_error_t * |
---|
770 | set_target_revision(void *edit_baton, |
---|
771 | svn_revnum_t target_revision, |
---|
772 | apr_pool_t *pool) |
---|
773 | { |
---|
774 | edit_baton_t *eb = edit_baton; |
---|
775 | return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, |
---|
776 | target_revision, pool); |
---|
777 | } |
---|
778 | |
---|
779 | static svn_error_t * |
---|
780 | open_root(void *edit_baton, |
---|
781 | svn_revnum_t base_revision, |
---|
782 | apr_pool_t *pool, |
---|
783 | void **root_baton) |
---|
784 | { |
---|
785 | edit_baton_t *eb = edit_baton; |
---|
786 | node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton)); |
---|
787 | |
---|
788 | SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, |
---|
789 | base_revision, pool, |
---|
790 | &dir_baton->wrapped_node_baton)); |
---|
791 | |
---|
792 | eb->called_open_root = TRUE; |
---|
793 | dir_baton->edit_baton = edit_baton; |
---|
794 | *root_baton = dir_baton; |
---|
795 | |
---|
796 | return SVN_NO_ERROR; |
---|
797 | } |
---|
798 | |
---|
799 | static svn_error_t * |
---|
800 | delete_entry(const char *path, |
---|
801 | svn_revnum_t base_revision, |
---|
802 | void *parent_baton, |
---|
803 | apr_pool_t *pool) |
---|
804 | { |
---|
805 | node_baton_t *pb = parent_baton; |
---|
806 | edit_baton_t *eb = pb->edit_baton; |
---|
807 | |
---|
808 | return eb->wrapped_editor->delete_entry(path, base_revision, |
---|
809 | pb->wrapped_node_baton, pool); |
---|
810 | } |
---|
811 | |
---|
812 | static svn_error_t * |
---|
813 | add_directory(const char *path, |
---|
814 | void *parent_baton, |
---|
815 | const char *copyfrom_path, |
---|
816 | svn_revnum_t copyfrom_rev, |
---|
817 | apr_pool_t *pool, |
---|
818 | void **child_baton) |
---|
819 | { |
---|
820 | node_baton_t *pb = parent_baton; |
---|
821 | edit_baton_t *eb = pb->edit_baton; |
---|
822 | node_baton_t *b = apr_palloc(pool, sizeof(*b)); |
---|
823 | |
---|
824 | if (copyfrom_path) |
---|
825 | copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url, |
---|
826 | svn_path_uri_encode(copyfrom_path, pool)); |
---|
827 | |
---|
828 | SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton, |
---|
829 | copyfrom_path, |
---|
830 | copyfrom_rev, pool, |
---|
831 | &b->wrapped_node_baton)); |
---|
832 | |
---|
833 | b->edit_baton = eb; |
---|
834 | *child_baton = b; |
---|
835 | |
---|
836 | return SVN_NO_ERROR; |
---|
837 | } |
---|
838 | |
---|
839 | static svn_error_t * |
---|
840 | open_directory(const char *path, |
---|
841 | void *parent_baton, |
---|
842 | svn_revnum_t base_revision, |
---|
843 | apr_pool_t *pool, |
---|
844 | void **child_baton) |
---|
845 | { |
---|
846 | node_baton_t *pb = parent_baton; |
---|
847 | edit_baton_t *eb = pb->edit_baton; |
---|
848 | node_baton_t *db = apr_palloc(pool, sizeof(*db)); |
---|
849 | |
---|
850 | SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton, |
---|
851 | base_revision, pool, |
---|
852 | &db->wrapped_node_baton)); |
---|
853 | |
---|
854 | db->edit_baton = eb; |
---|
855 | *child_baton = db; |
---|
856 | |
---|
857 | return SVN_NO_ERROR; |
---|
858 | } |
---|
859 | |
---|
860 | static svn_error_t * |
---|
861 | add_file(const char *path, |
---|
862 | void *parent_baton, |
---|
863 | const char *copyfrom_path, |
---|
864 | svn_revnum_t copyfrom_rev, |
---|
865 | apr_pool_t *pool, |
---|
866 | void **file_baton) |
---|
867 | { |
---|
868 | node_baton_t *pb = parent_baton; |
---|
869 | edit_baton_t *eb = pb->edit_baton; |
---|
870 | node_baton_t *fb = apr_palloc(pool, sizeof(*fb)); |
---|
871 | |
---|
872 | if (copyfrom_path) |
---|
873 | copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url, |
---|
874 | svn_path_uri_encode(copyfrom_path, pool)); |
---|
875 | |
---|
876 | SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton, |
---|
877 | copyfrom_path, copyfrom_rev, |
---|
878 | pool, &fb->wrapped_node_baton)); |
---|
879 | |
---|
880 | fb->edit_baton = eb; |
---|
881 | *file_baton = fb; |
---|
882 | |
---|
883 | return SVN_NO_ERROR; |
---|
884 | } |
---|
885 | |
---|
886 | static svn_error_t * |
---|
887 | open_file(const char *path, |
---|
888 | void *parent_baton, |
---|
889 | svn_revnum_t base_revision, |
---|
890 | apr_pool_t *pool, |
---|
891 | void **file_baton) |
---|
892 | { |
---|
893 | node_baton_t *pb = parent_baton; |
---|
894 | edit_baton_t *eb = pb->edit_baton; |
---|
895 | node_baton_t *fb = apr_palloc(pool, sizeof(*fb)); |
---|
896 | |
---|
897 | SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton, |
---|
898 | base_revision, pool, |
---|
899 | &fb->wrapped_node_baton)); |
---|
900 | |
---|
901 | fb->edit_baton = eb; |
---|
902 | *file_baton = fb; |
---|
903 | |
---|
904 | return SVN_NO_ERROR; |
---|
905 | } |
---|
906 | |
---|
907 | static svn_error_t * |
---|
908 | apply_textdelta(void *file_baton, |
---|
909 | const char *base_checksum, |
---|
910 | apr_pool_t *pool, |
---|
911 | svn_txdelta_window_handler_t *handler, |
---|
912 | void **handler_baton) |
---|
913 | { |
---|
914 | node_baton_t *fb = file_baton; |
---|
915 | edit_baton_t *eb = fb->edit_baton; |
---|
916 | |
---|
917 | if (! eb->quiet) |
---|
918 | { |
---|
919 | if (! eb->got_textdeltas) |
---|
920 | SVN_ERR(svn_cmdline_printf(pool, _("Transmitting file data "))); |
---|
921 | SVN_ERR(svn_cmdline_printf(pool, ".")); |
---|
922 | SVN_ERR(svn_cmdline_fflush(stdout)); |
---|
923 | } |
---|
924 | |
---|
925 | eb->got_textdeltas = TRUE; |
---|
926 | return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton, |
---|
927 | base_checksum, pool, |
---|
928 | handler, handler_baton); |
---|
929 | } |
---|
930 | |
---|
931 | static svn_error_t * |
---|
932 | close_file(void *file_baton, |
---|
933 | const char *text_checksum, |
---|
934 | apr_pool_t *pool) |
---|
935 | { |
---|
936 | node_baton_t *fb = file_baton; |
---|
937 | edit_baton_t *eb = fb->edit_baton; |
---|
938 | return eb->wrapped_editor->close_file(fb->wrapped_node_baton, |
---|
939 | text_checksum, pool); |
---|
940 | } |
---|
941 | |
---|
942 | static svn_error_t * |
---|
943 | absent_file(const char *path, |
---|
944 | void *file_baton, |
---|
945 | apr_pool_t *pool) |
---|
946 | { |
---|
947 | node_baton_t *fb = file_baton; |
---|
948 | edit_baton_t *eb = fb->edit_baton; |
---|
949 | return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool); |
---|
950 | } |
---|
951 | |
---|
952 | static svn_error_t * |
---|
953 | close_directory(void *dir_baton, |
---|
954 | apr_pool_t *pool) |
---|
955 | { |
---|
956 | node_baton_t *db = dir_baton; |
---|
957 | edit_baton_t *eb = db->edit_baton; |
---|
958 | return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool); |
---|
959 | } |
---|
960 | |
---|
961 | static svn_error_t * |
---|
962 | absent_directory(const char *path, |
---|
963 | void *dir_baton, |
---|
964 | apr_pool_t *pool) |
---|
965 | { |
---|
966 | node_baton_t *db = dir_baton; |
---|
967 | edit_baton_t *eb = db->edit_baton; |
---|
968 | return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton, |
---|
969 | pool); |
---|
970 | } |
---|
971 | |
---|
972 | static svn_error_t * |
---|
973 | change_file_prop(void *file_baton, |
---|
974 | const char *name, |
---|
975 | const svn_string_t *value, |
---|
976 | apr_pool_t *pool) |
---|
977 | { |
---|
978 | node_baton_t *fb = file_baton; |
---|
979 | edit_baton_t *eb = fb->edit_baton; |
---|
980 | |
---|
981 | /* only regular properties can pass over libsvn_ra */ |
---|
982 | if (svn_property_kind(NULL, name) != svn_prop_regular_kind) |
---|
983 | return SVN_NO_ERROR; |
---|
984 | |
---|
985 | /* Maybe drop svn:mergeinfo. */ |
---|
986 | if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0)) |
---|
987 | { |
---|
988 | eb->mergeinfo_stripped = TRUE; |
---|
989 | return SVN_NO_ERROR; |
---|
990 | } |
---|
991 | |
---|
992 | /* Maybe drop (errantly set, as this is a file) svnmerge.py properties. */ |
---|
993 | if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0)) |
---|
994 | { |
---|
995 | eb->svnmerge_migrated = TRUE; |
---|
996 | return SVN_NO_ERROR; |
---|
997 | } |
---|
998 | |
---|
999 | /* Remember if we see any svnmerge-blocked properties. (They really |
---|
1000 | shouldn't be here, as this is a file, but whatever...) */ |
---|
1001 | if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0)) |
---|
1002 | { |
---|
1003 | eb->svnmerge_blocked = TRUE; |
---|
1004 | } |
---|
1005 | |
---|
1006 | return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton, |
---|
1007 | name, value, pool); |
---|
1008 | } |
---|
1009 | |
---|
1010 | static svn_error_t * |
---|
1011 | change_dir_prop(void *dir_baton, |
---|
1012 | const char *name, |
---|
1013 | const svn_string_t *value, |
---|
1014 | apr_pool_t *pool) |
---|
1015 | { |
---|
1016 | node_baton_t *db = dir_baton; |
---|
1017 | edit_baton_t *eb = db->edit_baton; |
---|
1018 | svn_string_t *real_value = (svn_string_t *)value; |
---|
1019 | |
---|
1020 | /* Only regular properties can pass over libsvn_ra */ |
---|
1021 | if (svn_property_kind(NULL, name) != svn_prop_regular_kind) |
---|
1022 | return SVN_NO_ERROR; |
---|
1023 | |
---|
1024 | /* Maybe drop svn:mergeinfo. */ |
---|
1025 | if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0)) |
---|
1026 | { |
---|
1027 | eb->mergeinfo_stripped = TRUE; |
---|
1028 | return SVN_NO_ERROR; |
---|
1029 | } |
---|
1030 | |
---|
1031 | /* Maybe convert svnmerge-integrated data into svn:mergeinfo. (We |
---|
1032 | ignore svnmerge-blocked for now.) */ |
---|
1033 | /* ### FIXME: Consult the mirror repository's HEAD prop values and |
---|
1034 | ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */ |
---|
1035 | if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0)) |
---|
1036 | { |
---|
1037 | if (value) |
---|
1038 | { |
---|
1039 | /* svnmerge-integrated differs from svn:mergeinfo in a pair |
---|
1040 | of ways. First, it can use tabs, newlines, or spaces to |
---|
1041 | delimit source information. Secondly, the source paths |
---|
1042 | are relative URLs, whereas svn:mergeinfo uses relative |
---|
1043 | paths (not URI-encoded). */ |
---|
1044 | svn_error_t *err; |
---|
1045 | svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create("", pool); |
---|
1046 | svn_mergeinfo_t mergeinfo; |
---|
1047 | int i; |
---|
1048 | apr_array_header_t *sources = |
---|
1049 | svn_cstring_split(value->data, " \t\n", TRUE, pool); |
---|
1050 | |
---|
1051 | for (i = 0; i < sources->nelts; i++) |
---|
1052 | { |
---|
1053 | const char *rel_path; |
---|
1054 | apr_array_header_t *path_revs = |
---|
1055 | svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *), |
---|
1056 | ":", TRUE, pool); |
---|
1057 | |
---|
1058 | /* ### TODO: Warn? */ |
---|
1059 | if (path_revs->nelts != 2) |
---|
1060 | continue; |
---|
1061 | |
---|
1062 | /* Append this source's mergeinfo data. */ |
---|
1063 | rel_path = APR_ARRAY_IDX(path_revs, 0, const char *); |
---|
1064 | rel_path = svn_path_uri_decode(rel_path, pool); |
---|
1065 | svn_stringbuf_appendcstr(mergeinfo_buf, rel_path); |
---|
1066 | svn_stringbuf_appendcstr(mergeinfo_buf, ":"); |
---|
1067 | svn_stringbuf_appendcstr(mergeinfo_buf, |
---|
1068 | APR_ARRAY_IDX(path_revs, 1, |
---|
1069 | const char *)); |
---|
1070 | svn_stringbuf_appendcstr(mergeinfo_buf, "\n"); |
---|
1071 | } |
---|
1072 | |
---|
1073 | /* Try to parse the mergeinfo string we've created, just to |
---|
1074 | check for bogosity. If all goes well, we'll unparse it |
---|
1075 | again and use that as our property value. */ |
---|
1076 | err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool); |
---|
1077 | if (err) |
---|
1078 | { |
---|
1079 | svn_error_clear(err); |
---|
1080 | return SVN_NO_ERROR; |
---|
1081 | } |
---|
1082 | SVN_ERR(svn_mergeinfo_to_string(&real_value, mergeinfo, pool)); |
---|
1083 | } |
---|
1084 | name = SVN_PROP_MERGEINFO; |
---|
1085 | eb->svnmerge_migrated = TRUE; |
---|
1086 | } |
---|
1087 | |
---|
1088 | /* Remember if we see any svnmerge-blocked properties. */ |
---|
1089 | if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0)) |
---|
1090 | { |
---|
1091 | eb->svnmerge_blocked = TRUE; |
---|
1092 | } |
---|
1093 | |
---|
1094 | return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton, |
---|
1095 | name, real_value, pool); |
---|
1096 | } |
---|
1097 | |
---|
1098 | static svn_error_t * |
---|
1099 | close_edit(void *edit_baton, |
---|
1100 | apr_pool_t *pool) |
---|
1101 | { |
---|
1102 | edit_baton_t *eb = edit_baton; |
---|
1103 | |
---|
1104 | /* If we haven't opened the root yet, that means we're transfering |
---|
1105 | an empty revision, probably because we aren't allowed to see the |
---|
1106 | contents for some reason. In any event, we need to open the root |
---|
1107 | and close it again, before we can close out the edit, or the |
---|
1108 | commit will fail. */ |
---|
1109 | |
---|
1110 | if (! eb->called_open_root) |
---|
1111 | { |
---|
1112 | void *baton; |
---|
1113 | SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, |
---|
1114 | eb->base_revision, pool, |
---|
1115 | &baton)); |
---|
1116 | SVN_ERR(eb->wrapped_editor->close_directory(baton, pool)); |
---|
1117 | } |
---|
1118 | |
---|
1119 | if (! eb->quiet) |
---|
1120 | { |
---|
1121 | if (eb->got_textdeltas) |
---|
1122 | SVN_ERR(svn_cmdline_printf(pool, "\n")); |
---|
1123 | if (eb->mergeinfo_stripped) |
---|
1124 | SVN_ERR(svn_cmdline_printf(pool, |
---|
1125 | "NOTE: Dropped Subversion mergeinfo " |
---|
1126 | "from this revision.\n")); |
---|
1127 | if (eb->svnmerge_migrated) |
---|
1128 | SVN_ERR(svn_cmdline_printf(pool, |
---|
1129 | "NOTE: Migrated 'svnmerge-integrated' in " |
---|
1130 | "this revision.\n")); |
---|
1131 | if (eb->svnmerge_blocked) |
---|
1132 | SVN_ERR(svn_cmdline_printf(pool, |
---|
1133 | "NOTE: Saw 'svnmerge-blocked' in this " |
---|
1134 | "revision (but didn't migrate it).\n")); |
---|
1135 | } |
---|
1136 | |
---|
1137 | return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); |
---|
1138 | } |
---|
1139 | |
---|
1140 | /*** Editor factory function ***/ |
---|
1141 | |
---|
1142 | /* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair |
---|
1143 | * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the |
---|
1144 | * revision on which the driver of this returned editor will be basing |
---|
1145 | * the commit. TO_URL is the URL of the root of the repository into |
---|
1146 | * which the commit is being made. |
---|
1147 | */ |
---|
1148 | static svn_error_t * |
---|
1149 | get_sync_editor(const svn_delta_editor_t *wrapped_editor, |
---|
1150 | void *wrapped_edit_baton, |
---|
1151 | svn_revnum_t base_revision, |
---|
1152 | const char *to_url, |
---|
1153 | svn_boolean_t quiet, |
---|
1154 | const svn_delta_editor_t **editor, |
---|
1155 | void **edit_baton, |
---|
1156 | apr_pool_t *pool) |
---|
1157 | { |
---|
1158 | svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool); |
---|
1159 | edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb)); |
---|
1160 | |
---|
1161 | tree_editor->set_target_revision = set_target_revision; |
---|
1162 | tree_editor->open_root = open_root; |
---|
1163 | tree_editor->delete_entry = delete_entry; |
---|
1164 | tree_editor->add_directory = add_directory; |
---|
1165 | tree_editor->open_directory = open_directory; |
---|
1166 | tree_editor->change_dir_prop = change_dir_prop; |
---|
1167 | tree_editor->close_directory = close_directory; |
---|
1168 | tree_editor->absent_directory = absent_directory; |
---|
1169 | tree_editor->add_file = add_file; |
---|
1170 | tree_editor->open_file = open_file; |
---|
1171 | tree_editor->apply_textdelta = apply_textdelta; |
---|
1172 | tree_editor->change_file_prop = change_file_prop; |
---|
1173 | tree_editor->close_file = close_file; |
---|
1174 | tree_editor->absent_file = absent_file; |
---|
1175 | tree_editor->close_edit = close_edit; |
---|
1176 | |
---|
1177 | eb->wrapped_editor = wrapped_editor; |
---|
1178 | eb->wrapped_edit_baton = wrapped_edit_baton; |
---|
1179 | eb->base_revision = base_revision; |
---|
1180 | eb->to_url = to_url; |
---|
1181 | eb->quiet = quiet; |
---|
1182 | |
---|
1183 | if (getenv("SVNSYNC_UNSUPPORTED_STRIP_MERGEINFO")) |
---|
1184 | { |
---|
1185 | eb->strip_mergeinfo = TRUE; |
---|
1186 | } |
---|
1187 | if (getenv("SVNSYNC_UNSUPPORTED_MIGRATE_SVNMERGE")) |
---|
1188 | { |
---|
1189 | /* Current we can't merge property values. That's only possible |
---|
1190 | if all the properties to be merged were always modified in |
---|
1191 | exactly the same revisions, or if we allow ourselves to |
---|
1192 | lookup the current state of properties in the sync |
---|
1193 | destination. So for now, migrating svnmerge.py data implies |
---|
1194 | stripping pre-existing svn:mergeinfo. */ |
---|
1195 | /* ### FIXME: Do a real migration by consulting the mirror |
---|
1196 | ### repository's HEAD propvalues and merging svn:mergeinfo, |
---|
1197 | ### svnmerge-integrated, and svnmerge-blocked together. */ |
---|
1198 | eb->migrate_svnmerge = TRUE; |
---|
1199 | eb->strip_mergeinfo = TRUE; |
---|
1200 | } |
---|
1201 | |
---|
1202 | *editor = tree_editor; |
---|
1203 | *edit_baton = eb; |
---|
1204 | |
---|
1205 | return SVN_NO_ERROR; |
---|
1206 | } |
---|
1207 | |
---|
1208 | |
---|
1209 | |
---|
1210 | /*** `svnsync sync' ***/ |
---|
1211 | |
---|
1212 | /* Implements `svn_commit_callback2_t' interface. */ |
---|
1213 | static svn_error_t * |
---|
1214 | commit_callback(const svn_commit_info_t *commit_info, |
---|
1215 | void *baton, |
---|
1216 | apr_pool_t *pool) |
---|
1217 | { |
---|
1218 | subcommand_baton_t *sb = baton; |
---|
1219 | |
---|
1220 | if (! sb->quiet) |
---|
1221 | { |
---|
1222 | SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"), |
---|
1223 | commit_info->revision)); |
---|
1224 | } |
---|
1225 | |
---|
1226 | sb->committed_rev = commit_info->revision; |
---|
1227 | |
---|
1228 | return SVN_NO_ERROR; |
---|
1229 | } |
---|
1230 | |
---|
1231 | |
---|
1232 | /* Set *FROM_SESSION to an RA session associated with the source |
---|
1233 | * repository of the synchronization, as determined by reading |
---|
1234 | * svn:sync- properties from the destination repository (associated |
---|
1235 | * with TO_SESSION). Set LAST_MERGED_REV to the value of the property |
---|
1236 | * which records the most recently synchronized revision. |
---|
1237 | * |
---|
1238 | * CALLBACKS is a vtable of RA callbacks to provide when creating |
---|
1239 | * *FROM_SESSION. CONFIG is a configuration hash. |
---|
1240 | */ |
---|
1241 | static svn_error_t * |
---|
1242 | open_source_session(svn_ra_session_t **from_session, |
---|
1243 | svn_string_t **last_merged_rev, |
---|
1244 | svn_ra_session_t *to_session, |
---|
1245 | svn_ra_callbacks2_t *callbacks, |
---|
1246 | apr_hash_t *config, |
---|
1247 | void *baton, |
---|
1248 | apr_pool_t *pool) |
---|
1249 | { |
---|
1250 | svn_string_t *from_url, *from_uuid; |
---|
1251 | |
---|
1252 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, |
---|
1253 | &from_url, pool)); |
---|
1254 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID, |
---|
1255 | &from_uuid, pool)); |
---|
1256 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, |
---|
1257 | last_merged_rev, pool)); |
---|
1258 | |
---|
1259 | if (! from_url || ! from_uuid || ! *last_merged_rev) |
---|
1260 | return svn_error_create |
---|
1261 | (APR_EINVAL, NULL, |
---|
1262 | _("Destination repository has not been initialized")); |
---|
1263 | |
---|
1264 | /* Open the session to copy the revision data. */ |
---|
1265 | SVN_ERR(svn_ra_open3(from_session, from_url->data, from_uuid->data, |
---|
1266 | callbacks, baton, config, pool)); |
---|
1267 | |
---|
1268 | return SVN_NO_ERROR; |
---|
1269 | } |
---|
1270 | |
---|
1271 | /* Replay baton, used during sychnronization. */ |
---|
1272 | typedef struct { |
---|
1273 | svn_ra_session_t *from_session; |
---|
1274 | svn_ra_session_t *to_session; |
---|
1275 | subcommand_baton_t *sb; |
---|
1276 | svn_boolean_t has_commit_revprops_capability; |
---|
1277 | } replay_baton_t; |
---|
1278 | |
---|
1279 | /* Return a replay baton allocated from POOL and populated with |
---|
1280 | data from the provided parameters. */ |
---|
1281 | static replay_baton_t * |
---|
1282 | make_replay_baton(svn_ra_session_t *from_session, |
---|
1283 | svn_ra_session_t *to_session, |
---|
1284 | subcommand_baton_t *sb, apr_pool_t *pool) |
---|
1285 | { |
---|
1286 | replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb)); |
---|
1287 | rb->from_session = from_session; |
---|
1288 | rb->to_session = to_session; |
---|
1289 | rb->sb = sb; |
---|
1290 | return rb; |
---|
1291 | } |
---|
1292 | |
---|
1293 | /* Filter out svn:date and svn:author properties. */ |
---|
1294 | static svn_boolean_t |
---|
1295 | filter_exclude_date_author_sync(const char *key) |
---|
1296 | { |
---|
1297 | if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0) |
---|
1298 | return TRUE; |
---|
1299 | else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0) |
---|
1300 | return TRUE; |
---|
1301 | else if (strncmp(key, SVNSYNC_PROP_PREFIX, |
---|
1302 | sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0) |
---|
1303 | return TRUE; |
---|
1304 | |
---|
1305 | return FALSE; |
---|
1306 | } |
---|
1307 | |
---|
1308 | /* Filter out all properties except svn:date and svn:author */ |
---|
1309 | static svn_boolean_t |
---|
1310 | filter_include_date_author_sync(const char *key) |
---|
1311 | { |
---|
1312 | return ! filter_exclude_date_author_sync(key); |
---|
1313 | } |
---|
1314 | |
---|
1315 | |
---|
1316 | /* Only exclude svn:log .*/ |
---|
1317 | static svn_boolean_t |
---|
1318 | filter_exclude_log(const char *key) |
---|
1319 | { |
---|
1320 | if (strcmp(key, SVN_PROP_REVISION_LOG) == 0) |
---|
1321 | return TRUE; |
---|
1322 | else |
---|
1323 | return FALSE; |
---|
1324 | } |
---|
1325 | |
---|
1326 | /* Only include svn:log. */ |
---|
1327 | static svn_boolean_t |
---|
1328 | filter_include_log(const char *key) |
---|
1329 | { |
---|
1330 | return ! filter_exclude_log(key); |
---|
1331 | } |
---|
1332 | |
---|
1333 | |
---|
1334 | /* Callback function for svn_ra_replay_range, invoked when starting to parse |
---|
1335 | * a replay report. |
---|
1336 | */ |
---|
1337 | static svn_error_t * |
---|
1338 | replay_rev_started(svn_revnum_t revision, |
---|
1339 | void *replay_baton, |
---|
1340 | const svn_delta_editor_t **editor, |
---|
1341 | void **edit_baton, |
---|
1342 | apr_hash_t *rev_props, |
---|
1343 | apr_pool_t *pool) |
---|
1344 | { |
---|
1345 | const svn_delta_editor_t *commit_editor; |
---|
1346 | const svn_delta_editor_t *cancel_editor; |
---|
1347 | const svn_delta_editor_t *sync_editor; |
---|
1348 | void *commit_baton; |
---|
1349 | void *cancel_baton; |
---|
1350 | void *sync_baton; |
---|
1351 | replay_baton_t *rb = replay_baton; |
---|
1352 | apr_hash_t *filtered; |
---|
1353 | int filtered_count; |
---|
1354 | |
---|
1355 | /* We set this property so that if we error out for some reason |
---|
1356 | we can later determine where we were in the process of |
---|
1357 | merging a revision. If we had committed the change, but we |
---|
1358 | hadn't finished copying the revprops we need to know that, so |
---|
1359 | we can go back and finish the job before we move on. |
---|
1360 | |
---|
1361 | NOTE: We have to set this before we start the commit editor, |
---|
1362 | because ra_svn doesn't let you change rev props during a |
---|
1363 | commit. */ |
---|
1364 | SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0, |
---|
1365 | SVNSYNC_PROP_CURRENTLY_COPYING, |
---|
1366 | svn_string_createf(pool, "%ld", |
---|
1367 | revision), |
---|
1368 | pool)); |
---|
1369 | |
---|
1370 | /* The actual copy is just a replay hooked up to a commit. Include |
---|
1371 | all the revision properties from the source repositories, except |
---|
1372 | 'svn:author' and 'svn:date', those are not guaranteed to get |
---|
1373 | through the editor anyway. |
---|
1374 | If we're syncing to an non-commit-revprops capable server, filter |
---|
1375 | out all revprops except svn:log and add them later in |
---|
1376 | revplay_rev_finished. */ |
---|
1377 | filtered = filter_props(&filtered_count, rev_props, |
---|
1378 | (rb->has_commit_revprops_capability |
---|
1379 | ? filter_exclude_date_author_sync |
---|
1380 | : filter_include_log), |
---|
1381 | pool); |
---|
1382 | |
---|
1383 | /* svn_ra_get_commit_editor3 requires the log message to be |
---|
1384 | set. It's possible that we didn't receive 'svn:log' here, so we |
---|
1385 | have to set it to at least the empty string. If there's a svn:log |
---|
1386 | property on this revision, we will write the actual value in the |
---|
1387 | replay_rev_finished callback. */ |
---|
1388 | if (! apr_hash_get(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING)) |
---|
1389 | apr_hash_set(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, |
---|
1390 | svn_string_create("", pool)); |
---|
1391 | |
---|
1392 | SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor, |
---|
1393 | &commit_baton, |
---|
1394 | filtered, |
---|
1395 | commit_callback, rb->sb, |
---|
1396 | NULL, FALSE, pool)); |
---|
1397 | |
---|
1398 | /* There's one catch though, the diff shows us props we can't send |
---|
1399 | over the RA interface, so we need an editor that's smart enough |
---|
1400 | to filter those out for us. */ |
---|
1401 | SVN_ERR(get_sync_editor(commit_editor, commit_baton, revision - 1, |
---|
1402 | rb->sb->to_url, rb->sb->quiet, |
---|
1403 | &sync_editor, &sync_baton, pool)); |
---|
1404 | |
---|
1405 | SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL, |
---|
1406 | sync_editor, sync_baton, |
---|
1407 | &cancel_editor, |
---|
1408 | &cancel_baton, |
---|
1409 | pool)); |
---|
1410 | *editor = cancel_editor; |
---|
1411 | *edit_baton = cancel_baton; |
---|
1412 | |
---|
1413 | return SVN_NO_ERROR; |
---|
1414 | } |
---|
1415 | |
---|
1416 | /* Callback function for svn_ra_replay_range, invoked when finishing parsing |
---|
1417 | * a replay report. |
---|
1418 | */ |
---|
1419 | static svn_error_t * |
---|
1420 | replay_rev_finished(svn_revnum_t revision, |
---|
1421 | void *replay_baton, |
---|
1422 | const svn_delta_editor_t *editor, |
---|
1423 | void *edit_baton, |
---|
1424 | apr_hash_t *rev_props, |
---|
1425 | apr_pool_t *pool) |
---|
1426 | { |
---|
1427 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1428 | replay_baton_t *rb = replay_baton; |
---|
1429 | apr_hash_t *filtered, *existing_props; |
---|
1430 | int filtered_count; |
---|
1431 | |
---|
1432 | SVN_ERR(editor->close_edit(edit_baton, pool)); |
---|
1433 | |
---|
1434 | /* Sanity check that we actually committed the revision we meant to. */ |
---|
1435 | if (rb->sb->committed_rev != revision) |
---|
1436 | return svn_error_createf |
---|
1437 | (APR_EINVAL, NULL, |
---|
1438 | _("Commit created rev %ld but should have created %ld"), |
---|
1439 | rb->sb->committed_rev, revision); |
---|
1440 | |
---|
1441 | SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props, |
---|
1442 | subpool)); |
---|
1443 | |
---|
1444 | |
---|
1445 | /* Ok, we're done with the data, now we just need to copy the remaining |
---|
1446 | 'svn:date' and 'svn:author' revprops and we're all set. |
---|
1447 | If the server doesn't support revprops-in-a-commit, we still have to |
---|
1448 | set all revision properties except svn:log. */ |
---|
1449 | filtered = filter_props(&filtered_count, rev_props, |
---|
1450 | (rb->has_commit_revprops_capability |
---|
1451 | ? filter_include_date_author_sync |
---|
1452 | : filter_exclude_log), |
---|
1453 | subpool); |
---|
1454 | SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered, |
---|
1455 | subpool)); |
---|
1456 | |
---|
1457 | /* Remove all extra properties in TARGET. */ |
---|
1458 | SVN_ERR(remove_props_not_in_source(rb->to_session, revision, |
---|
1459 | rev_props, existing_props, subpool)); |
---|
1460 | |
---|
1461 | svn_pool_clear(subpool); |
---|
1462 | |
---|
1463 | /* Ok, we're done, bring the last-merged-rev property up to date. */ |
---|
1464 | SVN_ERR(svn_ra_change_rev_prop |
---|
1465 | (rb->to_session, |
---|
1466 | 0, |
---|
1467 | SVNSYNC_PROP_LAST_MERGED_REV, |
---|
1468 | svn_string_create(apr_psprintf(pool, "%ld", revision), |
---|
1469 | subpool), |
---|
1470 | subpool)); |
---|
1471 | |
---|
1472 | /* And finally drop the currently copying prop, since we're done |
---|
1473 | with this revision. */ |
---|
1474 | SVN_ERR(svn_ra_change_rev_prop(rb->to_session, 0, |
---|
1475 | SVNSYNC_PROP_CURRENTLY_COPYING, |
---|
1476 | NULL, subpool)); |
---|
1477 | |
---|
1478 | /* Notify the user that we copied revision properties. */ |
---|
1479 | if (! rb->sb->quiet) |
---|
1480 | SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool)); |
---|
1481 | |
---|
1482 | svn_pool_destroy(subpool); |
---|
1483 | |
---|
1484 | return SVN_NO_ERROR; |
---|
1485 | } |
---|
1486 | |
---|
1487 | /* Synchronize the repository associated with RA session TO_SESSION, |
---|
1488 | * using information found in baton B, while the repository is |
---|
1489 | * locked. Implements `with_locked_func_t' interface. |
---|
1490 | */ |
---|
1491 | static svn_error_t * |
---|
1492 | do_synchronize(svn_ra_session_t *to_session, |
---|
1493 | subcommand_baton_t *baton, apr_pool_t *pool) |
---|
1494 | { |
---|
1495 | svn_string_t *last_merged_rev; |
---|
1496 | svn_revnum_t from_latest; |
---|
1497 | svn_ra_session_t *from_session; |
---|
1498 | svn_string_t *currently_copying; |
---|
1499 | svn_revnum_t to_latest, copying, last_merged; |
---|
1500 | svn_revnum_t start_revision, end_revision; |
---|
1501 | replay_baton_t *rb; |
---|
1502 | |
---|
1503 | SVN_ERR(open_source_session(&from_session, |
---|
1504 | &last_merged_rev, to_session, |
---|
1505 | &(baton->source_callbacks), baton->config, |
---|
1506 | baton, pool)); |
---|
1507 | |
---|
1508 | /* Check to see if we have revprops that still need to be copied for |
---|
1509 | a prior revision we didn't finish copying. But first, check for |
---|
1510 | state sanity. Remember, mirroring is not an atomic action, |
---|
1511 | because revision properties are copied separately from the |
---|
1512 | revision's contents. |
---|
1513 | |
---|
1514 | So, any time that currently-copying is not set, then |
---|
1515 | last-merged-rev should be the HEAD revision of the destination |
---|
1516 | repository. That is, if we didn't fall over in the middle of a |
---|
1517 | previous synchronization, then our destination repository should |
---|
1518 | have exactly as many revisions in it as we've synchronized. |
---|
1519 | |
---|
1520 | Alternately, if currently-copying *is* set, it must |
---|
1521 | be either last-merged-rev or last-merged-rev + 1, and the HEAD |
---|
1522 | revision must be equal to either last-merged-rev or |
---|
1523 | currently-copying. If this is not the case, somebody has meddled |
---|
1524 | with the destination without using svnsync. |
---|
1525 | */ |
---|
1526 | |
---|
1527 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING, |
---|
1528 | ¤tly_copying, pool)); |
---|
1529 | |
---|
1530 | SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool)); |
---|
1531 | |
---|
1532 | last_merged = SVN_STR_TO_REV(last_merged_rev->data); |
---|
1533 | |
---|
1534 | if (currently_copying) |
---|
1535 | { |
---|
1536 | copying = SVN_STR_TO_REV(currently_copying->data); |
---|
1537 | |
---|
1538 | if ((copying < last_merged) |
---|
1539 | || (copying > (last_merged + 1)) |
---|
1540 | || ((to_latest != last_merged) && (to_latest != copying))) |
---|
1541 | { |
---|
1542 | return svn_error_createf |
---|
1543 | (APR_EINVAL, NULL, |
---|
1544 | _("Revision being currently copied (%ld), last merged revision " |
---|
1545 | "(%ld), and destination HEAD (%ld) are inconsistent; have you " |
---|
1546 | "committed to the destination without using svnsync?"), |
---|
1547 | copying, last_merged, to_latest); |
---|
1548 | } |
---|
1549 | else if (copying == to_latest) |
---|
1550 | { |
---|
1551 | if (copying > last_merged) |
---|
1552 | { |
---|
1553 | SVN_ERR(copy_revprops(from_session, to_session, |
---|
1554 | to_latest, TRUE, baton->quiet, |
---|
1555 | pool)); |
---|
1556 | last_merged = copying; |
---|
1557 | last_merged_rev = svn_string_create |
---|
1558 | (apr_psprintf(pool, "%ld", last_merged), pool); |
---|
1559 | } |
---|
1560 | |
---|
1561 | /* Now update last merged rev and drop currently changing. |
---|
1562 | Note that the order here is significant, if we do them |
---|
1563 | in the wrong order there are race conditions where we |
---|
1564 | end up not being able to tell if there have been bogus |
---|
1565 | (i.e. non-svnsync) commits to the dest repository. */ |
---|
1566 | |
---|
1567 | SVN_ERR(svn_ra_change_rev_prop(to_session, 0, |
---|
1568 | SVNSYNC_PROP_LAST_MERGED_REV, |
---|
1569 | last_merged_rev, pool)); |
---|
1570 | SVN_ERR(svn_ra_change_rev_prop(to_session, 0, |
---|
1571 | SVNSYNC_PROP_CURRENTLY_COPYING, |
---|
1572 | NULL, pool)); |
---|
1573 | } |
---|
1574 | /* If copying > to_latest, then we just fall through to |
---|
1575 | attempting to copy the revision again. */ |
---|
1576 | } |
---|
1577 | else |
---|
1578 | { |
---|
1579 | if (to_latest != last_merged) |
---|
1580 | return svn_error_createf(APR_EINVAL, NULL, |
---|
1581 | _("Destination HEAD (%ld) is not the last " |
---|
1582 | "merged revision (%ld); have you " |
---|
1583 | "committed to the destination without " |
---|
1584 | "using svnsync?"), |
---|
1585 | to_latest, last_merged); |
---|
1586 | } |
---|
1587 | |
---|
1588 | /* Now check to see if there are any revisions to copy. */ |
---|
1589 | SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); |
---|
1590 | |
---|
1591 | if (from_latest < atol(last_merged_rev->data)) |
---|
1592 | return SVN_NO_ERROR; |
---|
1593 | |
---|
1594 | /* Ok, so there are new revisions, iterate over them copying them |
---|
1595 | into the destination repository. */ |
---|
1596 | rb = make_replay_baton(from_session, to_session, baton, pool); |
---|
1597 | |
---|
1598 | /* For compatibility with older svnserve versions, check first if we |
---|
1599 | support adding revprops to the commit. */ |
---|
1600 | SVN_ERR(svn_ra_has_capability(rb->to_session, |
---|
1601 | &rb->has_commit_revprops_capability, |
---|
1602 | SVN_RA_CAPABILITY_COMMIT_REVPROPS, |
---|
1603 | pool)); |
---|
1604 | |
---|
1605 | start_revision = atol(last_merged_rev->data) + 1; |
---|
1606 | end_revision = from_latest; |
---|
1607 | |
---|
1608 | SVN_ERR(check_cancel(NULL)); |
---|
1609 | |
---|
1610 | SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision, |
---|
1611 | 0, TRUE, replay_rev_started, |
---|
1612 | replay_rev_finished, rb, pool)); |
---|
1613 | |
---|
1614 | return SVN_NO_ERROR; |
---|
1615 | } |
---|
1616 | |
---|
1617 | |
---|
1618 | /* SUBCOMMAND: sync */ |
---|
1619 | static svn_error_t * |
---|
1620 | synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) |
---|
1621 | { |
---|
1622 | svn_ra_session_t *to_session; |
---|
1623 | opt_baton_t *opt_baton = b; |
---|
1624 | apr_array_header_t *targets; |
---|
1625 | subcommand_baton_t *baton; |
---|
1626 | const char *to_url; |
---|
1627 | |
---|
1628 | SVN_ERR(svn_opt__args_to_target_array(&targets, os, |
---|
1629 | apr_array_make(pool, 0, |
---|
1630 | sizeof(const char *)), |
---|
1631 | pool)); |
---|
1632 | if (targets->nelts < 1) |
---|
1633 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
---|
1634 | if (targets->nelts > 1) |
---|
1635 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); |
---|
1636 | |
---|
1637 | to_url = APR_ARRAY_IDX(targets, 0, const char *); |
---|
1638 | |
---|
1639 | if (! svn_path_is_url(to_url)) |
---|
1640 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1641 | _("Path '%s' is not a URL"), to_url); |
---|
1642 | |
---|
1643 | baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool); |
---|
1644 | SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL, |
---|
1645 | &(baton->sync_callbacks), baton, baton->config, pool)); |
---|
1646 | SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool)); |
---|
1647 | SVN_ERR(with_locked(to_session, do_synchronize, baton, pool)); |
---|
1648 | |
---|
1649 | return SVN_NO_ERROR; |
---|
1650 | } |
---|
1651 | |
---|
1652 | |
---|
1653 | |
---|
1654 | /*** `svnsync copy-revprops' ***/ |
---|
1655 | |
---|
1656 | /* Copy revision properties to the repository associated with RA |
---|
1657 | * session TO_SESSION, using information found in baton B, while the |
---|
1658 | * repository is locked. Implements `with_locked_func_t' interface. |
---|
1659 | */ |
---|
1660 | static svn_error_t * |
---|
1661 | do_copy_revprops(svn_ra_session_t *to_session, |
---|
1662 | subcommand_baton_t *baton, apr_pool_t *pool) |
---|
1663 | { |
---|
1664 | svn_ra_session_t *from_session; |
---|
1665 | svn_string_t *last_merged_rev; |
---|
1666 | svn_revnum_t i; |
---|
1667 | svn_revnum_t step = 1; |
---|
1668 | |
---|
1669 | SVN_ERR(open_source_session(&from_session, &last_merged_rev, |
---|
1670 | to_session, |
---|
1671 | &(baton->source_callbacks), baton->config, |
---|
1672 | baton, pool)); |
---|
1673 | |
---|
1674 | /* An invalid revision means "last-synced" */ |
---|
1675 | if (! SVN_IS_VALID_REVNUM(baton->start_rev)) |
---|
1676 | baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data); |
---|
1677 | if (! SVN_IS_VALID_REVNUM(baton->end_rev)) |
---|
1678 | baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data); |
---|
1679 | |
---|
1680 | /* Make sure we have revisions within the valid range. */ |
---|
1681 | if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data)) |
---|
1682 | return svn_error_createf |
---|
1683 | (APR_EINVAL, NULL, |
---|
1684 | _("Cannot copy revprops for a revision (%ld) that has not " |
---|
1685 | "been synchronized yet"), baton->start_rev); |
---|
1686 | if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data)) |
---|
1687 | return svn_error_createf |
---|
1688 | (APR_EINVAL, NULL, |
---|
1689 | _("Cannot copy revprops for a revision (%ld) that has not " |
---|
1690 | "been synchronized yet"), baton->end_rev); |
---|
1691 | |
---|
1692 | /* Now, copy all the requested revisions, in the requested order. */ |
---|
1693 | step = (baton->start_rev > baton->end_rev) ? -1 : 1; |
---|
1694 | for (i = baton->start_rev; i != baton->end_rev + step; i = i + step) |
---|
1695 | { |
---|
1696 | SVN_ERR(check_cancel(NULL)); |
---|
1697 | SVN_ERR(copy_revprops(from_session, to_session, i, FALSE, |
---|
1698 | baton->quiet, pool)); |
---|
1699 | } |
---|
1700 | |
---|
1701 | return SVN_NO_ERROR; |
---|
1702 | } |
---|
1703 | |
---|
1704 | |
---|
1705 | /* SUBCOMMAND: copy-revprops */ |
---|
1706 | static svn_error_t * |
---|
1707 | copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool) |
---|
1708 | { |
---|
1709 | svn_ra_session_t *to_session; |
---|
1710 | opt_baton_t *opt_baton = b; |
---|
1711 | apr_array_header_t *targets; |
---|
1712 | subcommand_baton_t *baton; |
---|
1713 | const char *to_url; |
---|
1714 | svn_opt_revision_t start_revision, end_revision; |
---|
1715 | svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM; |
---|
1716 | |
---|
1717 | /* There should be either one or two arguments left to parse. */ |
---|
1718 | if (os->argc - os->ind > 2) |
---|
1719 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); |
---|
1720 | if (os->argc - os->ind < 1) |
---|
1721 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
---|
1722 | |
---|
1723 | /* If there are two args, the last one is a revision range. We'll |
---|
1724 | effectively pop it from the end of the list. Why? Because |
---|
1725 | svn_opt__args_to_target_array() does waaaaay too many useful |
---|
1726 | things for us not to use it. */ |
---|
1727 | if (os->argc - os->ind == 2) |
---|
1728 | { |
---|
1729 | const char *rev_str = os->argv[--(os->argc)]; |
---|
1730 | |
---|
1731 | start_revision.kind = svn_opt_revision_unspecified; |
---|
1732 | end_revision.kind = svn_opt_revision_unspecified; |
---|
1733 | if ((svn_opt_parse_revision(&start_revision, &end_revision, |
---|
1734 | rev_str, pool) != 0) |
---|
1735 | || ((start_revision.kind != svn_opt_revision_number) |
---|
1736 | && (start_revision.kind != svn_opt_revision_head)) |
---|
1737 | || ((end_revision.kind != svn_opt_revision_number) |
---|
1738 | && (end_revision.kind != svn_opt_revision_head) |
---|
1739 | && (end_revision.kind != svn_opt_revision_unspecified))) |
---|
1740 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1741 | _("'%s' is not a valid revision range"), |
---|
1742 | rev_str); |
---|
1743 | |
---|
1744 | /* Get the start revision, which must be either HEAD or a number |
---|
1745 | (which is required to be a valid one). */ |
---|
1746 | if (start_revision.kind == svn_opt_revision_head) |
---|
1747 | { |
---|
1748 | start_rev = SVN_INVALID_REVNUM; |
---|
1749 | } |
---|
1750 | else |
---|
1751 | { |
---|
1752 | start_rev = start_revision.value.number; |
---|
1753 | if (! SVN_IS_VALID_REVNUM(start_rev)) |
---|
1754 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1755 | _("Invalid revision number (%ld)"), |
---|
1756 | start_rev); |
---|
1757 | } |
---|
1758 | |
---|
1759 | /* Get the end revision, which must be unspecified (meaning, |
---|
1760 | "same as the start_rev"), HEAD, or a number (which is |
---|
1761 | required to be a valid one). */ |
---|
1762 | if (end_revision.kind == svn_opt_revision_unspecified) |
---|
1763 | { |
---|
1764 | end_rev = start_rev; |
---|
1765 | } |
---|
1766 | else if (end_revision.kind == svn_opt_revision_head) |
---|
1767 | { |
---|
1768 | end_rev = SVN_INVALID_REVNUM; |
---|
1769 | } |
---|
1770 | else |
---|
1771 | { |
---|
1772 | end_rev = end_revision.value.number; |
---|
1773 | if (! SVN_IS_VALID_REVNUM(end_rev)) |
---|
1774 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1775 | _("Invalid revision number (%ld)"), |
---|
1776 | end_rev); |
---|
1777 | } |
---|
1778 | } |
---|
1779 | |
---|
1780 | SVN_ERR(svn_opt__args_to_target_array(&targets, os, |
---|
1781 | apr_array_make(pool, 1, |
---|
1782 | sizeof(const char *)), |
---|
1783 | pool)); |
---|
1784 | if (targets->nelts != 1) |
---|
1785 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
---|
1786 | |
---|
1787 | to_url = APR_ARRAY_IDX(targets, 0, const char *); |
---|
1788 | |
---|
1789 | if (! svn_path_is_url(to_url)) |
---|
1790 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1791 | _("Path '%s' is not a URL"), to_url); |
---|
1792 | |
---|
1793 | baton = make_subcommand_baton(opt_baton, to_url, NULL, |
---|
1794 | start_rev, end_rev, pool); |
---|
1795 | SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL, |
---|
1796 | &(baton->sync_callbacks), baton, baton->config, pool)); |
---|
1797 | SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool)); |
---|
1798 | SVN_ERR(with_locked(to_session, do_copy_revprops, baton, pool)); |
---|
1799 | |
---|
1800 | return SVN_NO_ERROR; |
---|
1801 | } |
---|
1802 | |
---|
1803 | |
---|
1804 | |
---|
1805 | /*** `svnsync info' ***/ |
---|
1806 | |
---|
1807 | |
---|
1808 | /* SUBCOMMAND: info */ |
---|
1809 | static svn_error_t * |
---|
1810 | info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool) |
---|
1811 | { |
---|
1812 | svn_ra_session_t *to_session; |
---|
1813 | opt_baton_t *opt_baton = b; |
---|
1814 | apr_array_header_t *targets; |
---|
1815 | subcommand_baton_t *baton; |
---|
1816 | const char *to_url; |
---|
1817 | svn_string_t *from_url, *from_uuid, *last_merged_rev; |
---|
1818 | |
---|
1819 | SVN_ERR(svn_opt__args_to_target_array(&targets, os, |
---|
1820 | apr_array_make(pool, 0, |
---|
1821 | sizeof(const char *)), |
---|
1822 | pool)); |
---|
1823 | if (targets->nelts < 1) |
---|
1824 | return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
---|
1825 | if (targets->nelts > 1) |
---|
1826 | return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); |
---|
1827 | |
---|
1828 | /* Get the mirror repository URL, and verify that it is URL-ish. */ |
---|
1829 | to_url = APR_ARRAY_IDX(targets, 0, const char *); |
---|
1830 | if (! svn_path_is_url(to_url)) |
---|
1831 | return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
1832 | _("Path '%s' is not a URL"), to_url); |
---|
1833 | |
---|
1834 | /* Open an RA session to the mirror repository URL. */ |
---|
1835 | baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool); |
---|
1836 | SVN_ERR(svn_ra_open3(&to_session, baton->to_url, NULL, |
---|
1837 | &(baton->sync_callbacks), baton, baton->config, pool)); |
---|
1838 | SVN_ERR(check_if_session_is_at_repos_root(to_session, baton->to_url, pool)); |
---|
1839 | |
---|
1840 | /* Verify that the repos has been initialized for synchronization. */ |
---|
1841 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL, |
---|
1842 | &from_url, pool)); |
---|
1843 | if (! from_url) |
---|
1844 | return svn_error_createf |
---|
1845 | (SVN_ERR_BAD_URL, NULL, |
---|
1846 | _("Repository '%s' is not initialized for synchronization"), to_url); |
---|
1847 | |
---|
1848 | /* Fetch more of the magic properties, which are the source of our info. */ |
---|
1849 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID, |
---|
1850 | &from_uuid, pool)); |
---|
1851 | SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, |
---|
1852 | &last_merged_rev, pool)); |
---|
1853 | |
---|
1854 | /* Print the info. */ |
---|
1855 | SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data)); |
---|
1856 | if (from_uuid) |
---|
1857 | SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"), |
---|
1858 | from_uuid->data)); |
---|
1859 | if (last_merged_rev) |
---|
1860 | SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"), |
---|
1861 | last_merged_rev->data)); |
---|
1862 | return SVN_NO_ERROR; |
---|
1863 | } |
---|
1864 | |
---|
1865 | |
---|
1866 | |
---|
1867 | /*** `svnsync help' ***/ |
---|
1868 | |
---|
1869 | |
---|
1870 | /* SUBCOMMAND: help */ |
---|
1871 | static svn_error_t * |
---|
1872 | help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool) |
---|
1873 | { |
---|
1874 | opt_baton_t *opt_baton = baton; |
---|
1875 | |
---|
1876 | const char *header = |
---|
1877 | _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n" |
---|
1878 | "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n" |
---|
1879 | "Type 'svnsync --version' to see the program version and RA modules.\n" |
---|
1880 | "\n" |
---|
1881 | "Available subcommands:\n"); |
---|
1882 | |
---|
1883 | const char *ra_desc_start |
---|
1884 | = _("The following repository access (RA) modules are available:\n\n"); |
---|
1885 | |
---|
1886 | svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start, |
---|
1887 | pool); |
---|
1888 | |
---|
1889 | SVN_ERR(svn_ra_print_modules(version_footer, pool)); |
---|
1890 | |
---|
1891 | SVN_ERR(svn_opt_print_help3(os, "svnsync", |
---|
1892 | opt_baton ? opt_baton->version : FALSE, |
---|
1893 | FALSE, version_footer->data, header, |
---|
1894 | svnsync_cmd_table, svnsync_options, NULL, |
---|
1895 | NULL, pool)); |
---|
1896 | |
---|
1897 | return SVN_NO_ERROR; |
---|
1898 | } |
---|
1899 | |
---|
1900 | |
---|
1901 | |
---|
1902 | /*** Main ***/ |
---|
1903 | |
---|
1904 | int |
---|
1905 | main(int argc, const char *argv[]) |
---|
1906 | { |
---|
1907 | const svn_opt_subcommand_desc2_t *subcommand = NULL; |
---|
1908 | apr_array_header_t *received_opts; |
---|
1909 | opt_baton_t opt_baton; |
---|
1910 | svn_config_t *config; |
---|
1911 | apr_status_t apr_err; |
---|
1912 | apr_getopt_t *os; |
---|
1913 | apr_pool_t *pool; |
---|
1914 | svn_error_t *err; |
---|
1915 | int opt_id, i; |
---|
1916 | const char *username = NULL, *source_username = NULL, *sync_username = NULL; |
---|
1917 | const char *password = NULL, *source_password = NULL, *sync_password = NULL; |
---|
1918 | |
---|
1919 | if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS) |
---|
1920 | { |
---|
1921 | return EXIT_FAILURE; |
---|
1922 | } |
---|
1923 | |
---|
1924 | err = check_lib_versions(); |
---|
1925 | if (err) |
---|
1926 | return svn_cmdline_handle_exit_error(err, NULL, "svnsync: "); |
---|
1927 | |
---|
1928 | pool = svn_pool_create(NULL); |
---|
1929 | |
---|
1930 | err = svn_ra_initialize(pool); |
---|
1931 | if (err) |
---|
1932 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
1933 | |
---|
1934 | memset(&opt_baton, 0, sizeof(opt_baton)); |
---|
1935 | |
---|
1936 | received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); |
---|
1937 | |
---|
1938 | if (argc <= 1) |
---|
1939 | { |
---|
1940 | help_cmd(NULL, NULL, pool); |
---|
1941 | svn_pool_destroy(pool); |
---|
1942 | return EXIT_FAILURE; |
---|
1943 | } |
---|
1944 | |
---|
1945 | err = svn_cmdline__getopt_init(&os, argc, argv, pool); |
---|
1946 | if (err) |
---|
1947 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
1948 | |
---|
1949 | os->interleave = 1; |
---|
1950 | |
---|
1951 | for (;;) |
---|
1952 | { |
---|
1953 | const char *opt_arg; |
---|
1954 | |
---|
1955 | apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg); |
---|
1956 | if (APR_STATUS_IS_EOF(apr_err)) |
---|
1957 | break; |
---|
1958 | else if (apr_err) |
---|
1959 | { |
---|
1960 | help_cmd(NULL, NULL, pool); |
---|
1961 | svn_pool_destroy(pool); |
---|
1962 | return EXIT_FAILURE; |
---|
1963 | } |
---|
1964 | |
---|
1965 | APR_ARRAY_PUSH(received_opts, int) = opt_id; |
---|
1966 | |
---|
1967 | switch (opt_id) |
---|
1968 | { |
---|
1969 | case svnsync_opt_non_interactive: |
---|
1970 | opt_baton.non_interactive = TRUE; |
---|
1971 | break; |
---|
1972 | |
---|
1973 | case svnsync_opt_trust_server_cert: |
---|
1974 | opt_baton.trust_server_cert = TRUE; |
---|
1975 | break; |
---|
1976 | |
---|
1977 | case svnsync_opt_no_auth_cache: |
---|
1978 | opt_baton.no_auth_cache = TRUE; |
---|
1979 | break; |
---|
1980 | |
---|
1981 | case svnsync_opt_auth_username: |
---|
1982 | username = opt_arg; |
---|
1983 | break; |
---|
1984 | |
---|
1985 | case svnsync_opt_auth_password: |
---|
1986 | password = opt_arg; |
---|
1987 | break; |
---|
1988 | |
---|
1989 | case svnsync_opt_source_username: |
---|
1990 | source_username = opt_arg; |
---|
1991 | break; |
---|
1992 | |
---|
1993 | case svnsync_opt_source_password: |
---|
1994 | source_password = opt_arg; |
---|
1995 | break; |
---|
1996 | |
---|
1997 | case svnsync_opt_sync_username: |
---|
1998 | sync_username = opt_arg; |
---|
1999 | break; |
---|
2000 | |
---|
2001 | case svnsync_opt_sync_password: |
---|
2002 | sync_password = opt_arg; |
---|
2003 | break; |
---|
2004 | |
---|
2005 | case svnsync_opt_config_dir: |
---|
2006 | opt_baton.config_dir = opt_arg; |
---|
2007 | break; |
---|
2008 | |
---|
2009 | case svnsync_opt_version: |
---|
2010 | opt_baton.version = TRUE; |
---|
2011 | break; |
---|
2012 | |
---|
2013 | case 'q': |
---|
2014 | opt_baton.quiet = TRUE; |
---|
2015 | break; |
---|
2016 | |
---|
2017 | case '?': |
---|
2018 | case 'h': |
---|
2019 | opt_baton.help = TRUE; |
---|
2020 | break; |
---|
2021 | |
---|
2022 | default: |
---|
2023 | { |
---|
2024 | help_cmd(NULL, NULL, pool); |
---|
2025 | svn_pool_destroy(pool); |
---|
2026 | return EXIT_FAILURE; |
---|
2027 | } |
---|
2028 | } |
---|
2029 | } |
---|
2030 | |
---|
2031 | if (opt_baton.help) |
---|
2032 | subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help"); |
---|
2033 | |
---|
2034 | /* Disallow the mixing --username/password with their --source- and |
---|
2035 | --sync- variants. Treat "--username FOO" as "--source-username |
---|
2036 | FOO --sync-username FOO"; ditto for "--password FOO". */ |
---|
2037 | if ((username || password) |
---|
2038 | && (source_username || sync_username |
---|
2039 | || source_password || sync_password)) |
---|
2040 | { |
---|
2041 | err = svn_error_create |
---|
2042 | (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
2043 | _("Cannot use --username or --password with any of " |
---|
2044 | "--source-username, --source-password, --sync-username, " |
---|
2045 | "or --sync-password.\n")); |
---|
2046 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
2047 | } |
---|
2048 | if (username) |
---|
2049 | { |
---|
2050 | source_username = username; |
---|
2051 | sync_username = username; |
---|
2052 | } |
---|
2053 | if (password) |
---|
2054 | { |
---|
2055 | source_password = password; |
---|
2056 | sync_password = password; |
---|
2057 | } |
---|
2058 | opt_baton.source_username = source_username; |
---|
2059 | opt_baton.source_password = source_password; |
---|
2060 | opt_baton.sync_username = sync_username; |
---|
2061 | opt_baton.sync_password = sync_password; |
---|
2062 | |
---|
2063 | /* --trust-server-cert can only be used with --non-interactive */ |
---|
2064 | if (opt_baton.trust_server_cert && !opt_baton.non_interactive) |
---|
2065 | { |
---|
2066 | err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
2067 | _("--trust-server-cert requires " |
---|
2068 | "--non-interactive")); |
---|
2069 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
2070 | } |
---|
2071 | |
---|
2072 | err = svn_config_ensure(opt_baton.config_dir, pool); |
---|
2073 | if (err) |
---|
2074 | return svn_cmdline_handle_exit_error(err, pool, "synsync: "); |
---|
2075 | |
---|
2076 | if (subcommand == NULL) |
---|
2077 | { |
---|
2078 | if (os->ind >= os->argc) |
---|
2079 | { |
---|
2080 | if (opt_baton.version) |
---|
2081 | { |
---|
2082 | /* Use the "help" subcommand to handle "--version". */ |
---|
2083 | static const svn_opt_subcommand_desc2_t pseudo_cmd = |
---|
2084 | { "--version", help_cmd, {0}, "", |
---|
2085 | {svnsync_opt_version, /* must accept its own option */ |
---|
2086 | } }; |
---|
2087 | |
---|
2088 | subcommand = &pseudo_cmd; |
---|
2089 | } |
---|
2090 | else |
---|
2091 | { |
---|
2092 | help_cmd(NULL, NULL, pool); |
---|
2093 | svn_pool_destroy(pool); |
---|
2094 | return EXIT_FAILURE; |
---|
2095 | } |
---|
2096 | } |
---|
2097 | else |
---|
2098 | { |
---|
2099 | const char *first_arg = os->argv[os->ind++]; |
---|
2100 | subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, |
---|
2101 | first_arg); |
---|
2102 | if (subcommand == NULL) |
---|
2103 | { |
---|
2104 | help_cmd(NULL, NULL, pool); |
---|
2105 | svn_pool_destroy(pool); |
---|
2106 | return EXIT_FAILURE; |
---|
2107 | } |
---|
2108 | } |
---|
2109 | } |
---|
2110 | |
---|
2111 | for (i = 0; i < received_opts->nelts; ++i) |
---|
2112 | { |
---|
2113 | opt_id = APR_ARRAY_IDX(received_opts, i, int); |
---|
2114 | |
---|
2115 | if (opt_id == 'h' || opt_id == '?') |
---|
2116 | continue; |
---|
2117 | |
---|
2118 | if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) |
---|
2119 | { |
---|
2120 | const char *optstr; |
---|
2121 | const apr_getopt_option_t *badopt = |
---|
2122 | svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand, |
---|
2123 | pool); |
---|
2124 | svn_opt_format_option(&optstr, badopt, FALSE, pool); |
---|
2125 | if (subcommand->name[0] == '-') |
---|
2126 | { |
---|
2127 | help_cmd(NULL, NULL, pool); |
---|
2128 | } |
---|
2129 | else |
---|
2130 | { |
---|
2131 | err = svn_error_createf |
---|
2132 | (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
---|
2133 | _("Subcommand '%s' doesn't accept option '%s'\n" |
---|
2134 | "Type 'svnsync help %s' for usage.\n"), |
---|
2135 | subcommand->name, optstr, subcommand->name); |
---|
2136 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
2137 | } |
---|
2138 | } |
---|
2139 | } |
---|
2140 | |
---|
2141 | err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool); |
---|
2142 | if (err) |
---|
2143 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
2144 | |
---|
2145 | config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG, |
---|
2146 | APR_HASH_KEY_STRING); |
---|
2147 | |
---|
2148 | apr_signal(SIGINT, signal_handler); |
---|
2149 | |
---|
2150 | #ifdef SIGBREAK |
---|
2151 | /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ |
---|
2152 | apr_signal(SIGBREAK, signal_handler); |
---|
2153 | #endif |
---|
2154 | |
---|
2155 | #ifdef SIGHUP |
---|
2156 | apr_signal(SIGHUP, signal_handler); |
---|
2157 | #endif |
---|
2158 | |
---|
2159 | #ifdef SIGTERM |
---|
2160 | apr_signal(SIGTERM, signal_handler); |
---|
2161 | #endif |
---|
2162 | |
---|
2163 | #ifdef SIGPIPE |
---|
2164 | /* Disable SIGPIPE generation for the platforms that have it. */ |
---|
2165 | apr_signal(SIGPIPE, SIG_IGN); |
---|
2166 | #endif |
---|
2167 | |
---|
2168 | #ifdef SIGXFSZ |
---|
2169 | /* Disable SIGXFSZ generation for the platforms that have it, |
---|
2170 | otherwise working with large files when compiled against an APR |
---|
2171 | that doesn't have large file support will crash the program, |
---|
2172 | which is uncool. */ |
---|
2173 | apr_signal(SIGXFSZ, SIG_IGN); |
---|
2174 | #endif |
---|
2175 | |
---|
2176 | err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton, |
---|
2177 | opt_baton.non_interactive, |
---|
2178 | opt_baton.source_username, |
---|
2179 | opt_baton.source_password, |
---|
2180 | opt_baton.config_dir, |
---|
2181 | opt_baton.no_auth_cache, |
---|
2182 | opt_baton.trust_server_cert, |
---|
2183 | config, |
---|
2184 | check_cancel, NULL, |
---|
2185 | pool); |
---|
2186 | if (! err) |
---|
2187 | err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton, |
---|
2188 | opt_baton.non_interactive, |
---|
2189 | opt_baton.sync_username, |
---|
2190 | opt_baton.sync_password, |
---|
2191 | opt_baton.config_dir, |
---|
2192 | opt_baton.no_auth_cache, |
---|
2193 | opt_baton.trust_server_cert, |
---|
2194 | config, |
---|
2195 | check_cancel, NULL, |
---|
2196 | pool); |
---|
2197 | if (! err) |
---|
2198 | err = (*subcommand->cmd_func)(os, &opt_baton, pool); |
---|
2199 | if (err) |
---|
2200 | { |
---|
2201 | /* For argument-related problems, suggest using the 'help' |
---|
2202 | subcommand. */ |
---|
2203 | if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS |
---|
2204 | || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) |
---|
2205 | { |
---|
2206 | err = svn_error_quick_wrap(err, |
---|
2207 | _("Try 'svnsync help' for more info")); |
---|
2208 | } |
---|
2209 | |
---|
2210 | return svn_cmdline_handle_exit_error(err, pool, "svnsync: "); |
---|
2211 | } |
---|
2212 | |
---|
2213 | svn_pool_destroy(pool); |
---|
2214 | |
---|
2215 | return EXIT_SUCCESS; |
---|
2216 | } |
---|