1 /*
2 * svn-mergeinfo-normalizer.c: MI normalization tool main file.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
30 #include <string.h>
31 #include <assert.h>
32
33 #include <apr_strings.h>
34 #include <apr_tables.h>
35 #include <apr_general.h>
36 #include <apr_signal.h>
37
38 #include "svn_cmdline.h"
39 #include "svn_pools.h"
40 #include "svn_wc.h"
41 #include "svn_client.h"
42 #include "svn_config.h"
43 #include "svn_string.h"
44 #include "svn_dirent_uri.h"
45 #include "svn_path.h"
46 #include "svn_delta.h"
47 #include "svn_diff.h"
48 #include "svn_error.h"
49 #include "svn_io.h"
50 #include "svn_opt.h"
51 #include "svn_utf.h"
52 #include "svn_auth.h"
53 #include "svn_hash.h"
54 #include "svn_version.h"
55 #include "mergeinfo-normalizer.h"
56
57 #include "private/svn_opt_private.h"
58 #include "private/svn_cmdline_private.h"
59 #include "private/svn_subr_private.h"
60
61 #include "svn_private_config.h"
62
63
64 /*** Option Processing ***/
65
66 /* Add an identifier here for long options that don't have a short
67 option. Options that have both long and short options should just
68 use the short option letter as identifier. */
69 typedef enum svn_min__longopt_t {
70 opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
71 opt_auth_password_from_stdin,
72 opt_auth_username,
73 opt_config_dir,
74 opt_config_options,
75 opt_dry_run,
76 opt_no_auth_cache,
77 opt_targets,
78 opt_depth,
79 opt_version,
80 opt_non_interactive,
81 opt_force_interactive,
82 opt_trust_server_cert,
83 opt_trust_server_cert_unknown_ca,
84 opt_trust_server_cert_cn_mismatch,
85 opt_trust_server_cert_expired,
86 opt_trust_server_cert_not_yet_valid,
87 opt_trust_server_cert_other_failure,
88 opt_allow_mixed_revisions,
89 opt_remove_obsoletes,
90 opt_remove_redundant,
91 opt_combine_ranges,
92 opt_remove_redundant_misaligned
93 } svn_cl__longopt_t;
94
95
96 /* Option codes and descriptions for the command line client.
97 *
98 * The entire list must be terminated with an entry of nulls.
99 */
100 const apr_getopt_option_t svn_min__options[] =
101 {
102 {"help", 'h', 0, N_("show help on a subcommand")},
103 {NULL, '?', 0, N_("show help on a subcommand")},
104 {"quiet", 'q', 0, N_("print nothing, or only summary information")},
105 {"version", opt_version, 0, N_("show program version information")},
106 {"file", 'F', 1, N_("read list of branches to remove from file ARG.\n"
107 " "
108 "Each branch given on a separate line with no\n"
109 " "
110 "extra whitespace.")},
111 {"verbose", 'v', 0, N_("print extra information")},
112 {"username", opt_auth_username, 1, N_("specify a username ARG")},
113 {"password", opt_auth_password, 1,
114 N_("specify a password ARG (caution: on many operating\n"
115 " "
116 "systems, other users will be able to see this)")},
117 {"password-from-stdin",
118 opt_auth_password_from_stdin, 0,
119 N_("read password from stdin")},
120 {"targets", opt_targets, 1,
121 N_("pass contents of file ARG as additional args")},
122 {"depth", opt_depth, 1,
123 N_("limit operation by depth ARG ('empty', 'files',\n"
124 " "
125 "'immediates', or 'infinity')")},
126 {"no-auth-cache", opt_no_auth_cache, 0,
127 N_("do not cache authentication tokens")},
128 {"trust-server-cert", opt_trust_server_cert, 0,
129 N_("deprecated; same as --trust-unknown-ca")},
130 {"trust-unknown-ca", opt_trust_server_cert_unknown_ca, 0,
131 N_("with --non-interactive, accept SSL server\n"
132 " "
133 "certificates from unknown certificate authorities")},
134 {"trust-cn-mismatch", opt_trust_server_cert_cn_mismatch, 0,
135 N_("with --non-interactive, accept SSL server\n"
136 " "
137 "certificates even if the server hostname does not\n"
138 " "
139 "match the certificate's common name attribute")},
140 {"trust-expired", opt_trust_server_cert_expired, 0,
141 N_("with --non-interactive, accept expired SSL server\n"
142 " "
143 "certificates")},
144 {"trust-not-yet-valid", opt_trust_server_cert_not_yet_valid, 0,
145 N_("with --non-interactive, accept SSL server\n"
146 " "
147 "certificates from the future")},
148 {"trust-other-failure", opt_trust_server_cert_other_failure, 0,
149 N_("with --non-interactive, accept SSL server\n"
150 " "
151 "certificates with failures other than the above")},
152 {"non-interactive", opt_non_interactive, 0,
153 N_("do no interactive prompting (default is to prompt\n"
154 " "
155 "only if standard input is a terminal device)")},
156 {"force-interactive", opt_force_interactive, 0,
157 N_("do interactive prompting even if standard input\n"
158 " "
159 "is not a terminal device")},
160 {"dry-run", opt_dry_run, 0,
161 N_("try operation but make no changes")},
162 {"config-dir", opt_config_dir, 1,
163 N_("read user configuration files from directory ARG")},
164 {"config-option", opt_config_options, 1,
165 N_("set user configuration option in the format:\n"
166 " "
167 " FILE:SECTION:OPTION=[VALUE]\n"
168 " "
169 "For example:\n"
170 " "
171 " servers:global:http-library=serf")},
172 {"allow-mixed-revisions", opt_allow_mixed_revisions, 0,
173 N_("Allow operation on mixed-revision working copy.\n"
174 " "
175 "Use of this option is not recommended!\n"
176 " "
177 "Please run 'svn update' instead.")},
178
179 {"remove-obsoletes", opt_remove_obsoletes, 0,
180 N_("Remove mergeinfo for deleted branches.")},
181 {"remove-redundant", opt_remove_redundant, 0,
182 N_("Remove mergeinfo on sub-nodes if it is\n"
183 " "
184 "redundant with the parent mergeinfo.")},
185 {"remove-redundant-misaligned", opt_remove_redundant_misaligned, 0,
186 N_("Remove mergeinfo of a misaligned branch if it\n"
187 " "
188 "is already covered by a correctly aligned one.\n")},
189 {"combine-ranges", opt_combine_ranges, 0,
190 N_("Try to combine adjacent revision ranges\n"
191 " "
192 "to reduce the size of the mergeinfo.")},
193
194 {0, 0, 0, 0},
195 };
196
197
198
199 /*** Command dispatch. ***/
200
201 /* Our array of available subcommands.
202 *
203 * The entire list must be terminated with an entry of nulls.
204 *
205 * In most of the help text "PATH" is used where a working copy path is
206 * required, "URL" where a repository URL is required and "TARGET" when
207 * either a path or a url can be used. Hmm, should this be part of the
208 * help text?
209 */
210
211 /* Options that apply to all commands. (While not every command may
212 currently require authentication or be interactive, allowing every
213 command to take these arguments allows scripts to just pass them
214 willy-nilly to every invocation of 'svn') . */
215 const int svn_min__global_options[] =
216 { opt_auth_username, opt_auth_password, opt_auth_password_from_stdin,
217 opt_no_auth_cache, opt_non_interactive, opt_force_interactive,
218 opt_trust_server_cert, opt_trust_server_cert_unknown_ca,
219 opt_trust_server_cert_cn_mismatch, opt_trust_server_cert_expired,
220 opt_trust_server_cert_not_yet_valid, opt_trust_server_cert_other_failure,
221 opt_config_dir, opt_config_options, 0
222 };
223
224 const svn_opt_subcommand_desc3_t svn_min__cmd_table[] =
225 {
226 { "help", svn_min__help, {"?", "h"}, {N_(
227 "Describe the usage of this program or its subcommands.\n"
228 "usage: help [SUBCOMMAND...]\n"
229 )},
230 {0} },
231
232 /* This command is also invoked if we see option "--help", "-h" or "-?". */
233
234 { "analyze", svn_min__analyze, { "analyse" }, {N_(
235 "Generate a report of which part of the sub-tree mergeinfo can be\n"
236 "removed and which part can't.\n"
237 "usage: analyze [WCPATH...]\n"
238 "\n"), N_(
239 " If neither --remove-obsoletes, --remove-redundant nor --combine-ranges\n"
240 " option is given, all three will be used implicitly.\n"
241 "\n"), N_(
242 " In verbose mode, the command will behave just like 'normalize --dry-run'\n"
243 " but will show an additional summary of all deleted branches that were\n"
244 " encountered plus the revision of their latest deletion (if available).\n"
245 "\n"), N_(
246 " In non-verbose mode, the per-node output does not give the parent path,\n"
247 " no successful elisions and branch removals nor the list of remaining\n"
248 " branches.\n"
249 )},
250 {opt_targets, opt_depth, 'v',
251 opt_remove_obsoletes, opt_remove_redundant,
252 opt_remove_redundant_misaligned, opt_combine_ranges} },
253
254 { "normalize", svn_min__normalize, { 0 }, {N_(
255 "Normalize / reduce the mergeinfo throughout the working copy sub-tree.\n"
256 "usage: normalize [WCPATH...]\n"
257 "\n"), N_(
258 " If neither --remove-obsoletes, --remove-redundant, --combine-ranges\n"
259 " nor --remove-redundant-misaligned option is given, --remove-redundant\n"
260 " will be used implicitly.\n"
261 "\n"), N_(
262 " In non-verbose mode, only general progress as well as a summary before\n"
263 " and after the normalization process will be shown. Note that sub-node\n"
264 " mergeinfo which could be removed entirely does not contribute to the\n"
265 " number of removed branch lines. Similarly, the number of revision\n"
266 " ranges combined only refers to the mergeinfo lines still present after\n"
267 " the normalization process. To get total numbers, compare the initial\n"
268 " with the final mergeinfo statistics.\n"
269 "\n"), N_(
270 " The detailed operation log in verbose mode replaces the progress display.\n"
271 " For each node with mergeinfo, the nearest parent node with mergeinfo is\n"
272 " given - if there is one and the result of trying to remove the mergeinfo\n"
273 " is shown for each branch. The various outputs are:\n"
274 "\n"), N_(
275 " elide redundant branch - Revision ranges are the same as in the parent.\n"
276 " Mergeinfo for this branch can be elided.\n"
277 " elide branch - Not an exact match with the parent but the\n"
278 " differences could be eliminated by ...\n"
279 " revisions implied in parent\n"
280 " ... ignoring these revisions because they are\n"
281 " part of the parent's copy history.\n"
282 " revisions moved to parent\n"
283 " ... adding these revisions to the parent node\n"
284 " because they only affect the current sub-tree.\n"
285 " revisions implied in sub-tree\n"
286 " ... ignoring these revisions because they are\n"
287 " part of the sub-tree's copy history.\n"
288 " revisions inoperative in sub-node\n"
289 " ... removing these revisions from the sub-tree\n"
290 " mergeinfo because they did not change it.\n"
291 " remove deleted branch - The branch no longer exists in the repository.\n"
292 " We will remove its mergeinfo line.\n"
293 " elide misaligned branch- All revisions merged from that misaligned\n"
294 " branch have also been merged from the likely\n"
295 " correctly aligned branch.\n"
296 " CANNOT elide branch - Mergeinfo differs from parent's significantly\n"
297 " and can't be elided because ...\n"
298 " revisions not movable to parent\n"
299 " ... these revisions affect the parent tree\n"
300 " outside the current sub-tree but are only\n"
301 " listed as merged in the current sub-tree.\n"
302 " revisions missing in sub-node\n"
303 " ... these revisions affect current sub-tree\n"
304 " but are only listed as merged for the parent.\n"
305 " keep POTENTIAL branch - The path does not exist @HEAD but may appear\n"
306 " in the future as the result of catch-up merges\n"
307 " from other branches.\n"
308 " has SURVIVING COPIES: - The path does not exist @HEAD but copies of it\n"
309 " or its sub-nodes do. This mergeinfo may be\n"
310 " relevant to them and will be kept.\n"
311 " NON-RECURSIVE RANGE(S) found\n"
312 " - Those revisions had been merged into a sparse\n"
313 " working copy resulting in incomplete merges.\n"
314 " The sub-tree mergeinfo cannot be elided.\n"
315 " MISSING in parent - The branch for the parent node exists in the\n"
316 " repository but is not in its mergeinfo.\n"
317 " The sub-tree mergeinfo will not be elided.\n"
318 " CANNOT elide MISALIGNED branch\n"
319 " The misaligned branch cannot be elide because\n"
320 " the revisions listed ...\n"
321 " revisions not merged from likely correctly aligned branch\n"
322 " ... here have not also been merged from the\n"
323 " likely correctly aligned branch.\n"
324 " MISALIGNED branch - There is no such branch for the parent node.\n"
325 " The sub-tree mergeinfo cannot be elided.\n"
326 " REVERSE RANGE(S) found - The mergeinfo contains illegal reverse ranges.\n"
327 " The sub-tree mergeinfo cannot be elided.\n"
328 "\n"), N_(
329 " If all branches have been removed from a nodes' mergeinfo, the whole\n"
330 " svn:mergeinfo property will be removed. Otherwise, only obsolete\n"
331 " branches will be removed. In verbose mode, a list of branches that\n"
332 " could not be removed will be shown per node.\n"
333 )},
334 {opt_targets, opt_depth, opt_dry_run, 'q', 'v',
335 opt_remove_obsoletes, opt_remove_redundant,
336 opt_remove_redundant_misaligned, opt_combine_ranges} },
337
338 { "remove-branches", svn_min__remove_branches, { 0 }, {N_(
339 "Read a list of branch names from the given file and remove all\n"
340 "mergeinfo referring to these branches from the given targets.\n"
341 "usage: remove-branches [WCPATH...] --file FILE\n"
342 "\n"), N_(
343 " The command will behave just like 'normalize --remove-obsoletes' but\n"
344 " will never actually contact the repository. Instead, it assumes any\n"
345 " path given in FILE is a deleted branch.\n"
346 "\n"), N_(
347 " Compared to a simple 'normalize --remove-obsoletes' run, this command\n"
348 " allows for selective removal of obsolete branches. It may therefore be\n"
349 " better suited for large deployments with complex branch structures.\n"
350 " You may also use this to remove mergeinfo that refers to still existing\n"
351 " branches.\n"
352 )},
353 {opt_targets, opt_depth, opt_dry_run, 'q', 'v', 'F'} },
354
355 { NULL, NULL, {0}, {NULL}, {0} }
356 };
357
358
359 /* Version compatibility check */
360 static svn_error_t *
check_lib_versions(void)361 check_lib_versions(void)
362 {
363 static const svn_version_checklist_t checklist[] =
364 {
365 { "svn_subr", svn_subr_version },
366 { "svn_client", svn_client_version },
367 { "svn_wc", svn_wc_version },
368 { "svn_ra", svn_ra_version },
369 { "svn_delta", svn_delta_version },
370 { "svn_diff", svn_diff_version },
371 { NULL, NULL }
372 };
373 SVN_VERSION_DEFINE(my_version);
374
375 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
376 }
377
378
379 /* A flag to see if we've been cancelled by the client or not. */
380 static volatile sig_atomic_t cancelled = FALSE;
381
382 /* A signal handler to support cancellation. */
383 static void
signal_handler(int signum)384 signal_handler(int signum)
385 {
386 apr_signal(signum, SIG_IGN);
387 cancelled = TRUE;
388 }
389
390 /* Our cancellation callback. */
391 svn_error_t *
svn_min__check_cancel(void * baton)392 svn_min__check_cancel(void *baton)
393 {
394 /* Cancel baton should be always NULL in command line client. */
395 SVN_ERR_ASSERT(baton == NULL);
396 if (cancelled)
397 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
398 else
399 return SVN_NO_ERROR;
400 }
401
402
403 /*** Main. ***/
404
405 /*
406 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
407 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
408 * return SVN_NO_ERROR.
409 */
410 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)411 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
412 {
413 svn_error_t *err;
414 int opt_id;
415 apr_getopt_t *os;
416 svn_min__opt_state_t opt_state = { 0 };
417 svn_client_ctx_t *ctx;
418 apr_array_header_t *received_opts;
419 int i;
420 const svn_opt_subcommand_desc3_t *subcommand = NULL;
421 svn_min__cmd_baton_t command_baton = { 0 };
422 svn_auth_baton_t *ab;
423 svn_config_t *cfg_config;
424 svn_boolean_t interactive_conflicts = FALSE;
425 svn_boolean_t force_interactive = FALSE;
426 apr_hash_t *cfg_hash;
427 svn_boolean_t read_pass_from_stdin = FALSE;
428
429 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
430
431 /* Check library versions */
432 SVN_ERR(check_lib_versions());
433
434 #if defined(WIN32) || defined(__CYGWIN__)
435 /* Set the working copy administrative directory name. */
436 if (getenv("SVN_ASP_DOT_NET_HACK"))
437 {
438 SVN_ERR(svn_wc_set_adm_dir("_svn", pool));
439 }
440 #endif
441
442 /* Initialize the RA library. */
443 SVN_ERR(svn_ra_initialize(pool));
444
445 /* Begin processing arguments. */
446 opt_state.depth = svn_depth_unknown;
447
448 /* No args? Show usage. */
449 if (argc <= 1)
450 {
451 SVN_ERR(svn_min__help(NULL, NULL, pool));
452 *exit_code = EXIT_FAILURE;
453 return SVN_NO_ERROR;
454 }
455
456 /* Else, parse options. */
457 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
458
459 os->interleave = 1;
460 while (1)
461 {
462 const char *opt_arg;
463 const char *utf8_opt_arg;
464
465 /* Parse the next option. */
466 apr_status_t apr_err = apr_getopt_long(os, svn_min__options, &opt_id,
467 &opt_arg);
468 if (APR_STATUS_IS_EOF(apr_err))
469 break;
470 else if (apr_err)
471 {
472 SVN_ERR(svn_min__help(NULL, NULL, pool));
473 *exit_code = EXIT_FAILURE;
474 return SVN_NO_ERROR;
475 }
476
477 /* Stash the option code in an array before parsing it. */
478 APR_ARRAY_PUSH(received_opts, int) = opt_id;
479
480 switch (opt_id) {
481 case 'h':
482 case '?':
483 opt_state.help = TRUE;
484 break;
485 case 'q':
486 opt_state.quiet = TRUE;
487 break;
488 case 'v':
489 opt_state.verbose = TRUE;
490 break;
491 case 'F':
492 /* We read the raw file content here. */
493 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
494 SVN_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
495 utf8_opt_arg, pool));
496 break;
497 case opt_targets:
498 {
499 svn_stringbuf_t *buffer, *buffer_utf8;
500
501 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
502 SVN_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
503 SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
504 opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
505 TRUE, pool);
506 }
507 break;
508 case opt_depth:
509 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
510 if (err)
511 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
512 _("Error converting depth "
513 "from locale to UTF-8"));
514 opt_state.depth = svn_depth_from_word(utf8_opt_arg);
515 if (opt_state.depth == svn_depth_unknown
516 || opt_state.depth == svn_depth_exclude)
517 {
518 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
519 _("'%s' is not a valid depth; try "
520 "'empty', 'files', 'immediates', "
521 "or 'infinity'"),
522 utf8_opt_arg);
523 }
524 break;
525 case opt_version:
526 opt_state.version = TRUE;
527 break;
528 case opt_dry_run:
529 opt_state.dry_run = TRUE;
530 break;
531 case opt_auth_username:
532 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
533 opt_arg, pool));
534 break;
535 case opt_auth_password:
536 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
537 opt_arg, pool));
538 break;
539 case opt_auth_password_from_stdin:
540 read_pass_from_stdin = TRUE;
541 break;
542 case opt_no_auth_cache:
543 opt_state.no_auth_cache = TRUE;
544 break;
545 case opt_non_interactive:
546 opt_state.non_interactive = TRUE;
547 break;
548 case opt_force_interactive:
549 force_interactive = TRUE;
550 break;
551 case opt_trust_server_cert: /* backwards compat to 1.8 */
552 case opt_trust_server_cert_unknown_ca:
553 opt_state.trust_server_cert_unknown_ca = TRUE;
554 break;
555 case opt_trust_server_cert_cn_mismatch:
556 opt_state.trust_server_cert_cn_mismatch = TRUE;
557 break;
558 case opt_trust_server_cert_expired:
559 opt_state.trust_server_cert_expired = TRUE;
560 break;
561 case opt_trust_server_cert_not_yet_valid:
562 opt_state.trust_server_cert_not_yet_valid = TRUE;
563 break;
564 case opt_trust_server_cert_other_failure:
565 opt_state.trust_server_cert_other_failure = TRUE;
566 break;
567 case opt_config_dir:
568 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
569 SVN_ERR(svn_dirent_internal_style_safe(&opt_state.config_dir, NULL,
570 utf8_opt_arg, pool, pool));
571 break;
572 case opt_config_options:
573 if (!opt_state.config_options)
574 opt_state.config_options =
575 apr_array_make(pool, 1,
576 sizeof(svn_cmdline__config_argument_t*));
577
578 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
579 SVN_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
580 utf8_opt_arg,
581 "svn-mi-normalizer: ",
582 pool));
583 break;
584 case opt_allow_mixed_revisions:
585 opt_state.allow_mixed_rev = TRUE;
586 break;
587
588 case opt_remove_obsoletes:
589 opt_state.remove_obsoletes = TRUE;
590 break;
591 case opt_remove_redundant:
592 opt_state.remove_redundants = TRUE;
593 break;
594 case opt_combine_ranges:
595 opt_state.combine_ranges = TRUE;
596 break;
597 case opt_remove_redundant_misaligned:
598 opt_state.remove_redundant_misaligned = TRUE;
599 break;
600
601 default:
602 /* Hmmm. Perhaps this would be a good place to squirrel away
603 opts that commands like svn diff might need. Hmmm indeed. */
604 break;
605 }
606 }
607
608 /* The --non-interactive and --force-interactive options are mutually
609 * exclusive. */
610 if (opt_state.non_interactive && force_interactive)
611 {
612 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
613 _("--non-interactive and --force-interactive "
614 "are mutually exclusive"));
615 }
616 else
617 opt_state.non_interactive = !svn_cmdline__be_interactive(
618 opt_state.non_interactive,
619 force_interactive);
620
621 /* --password-from-stdin can only be used with --non-interactive */
622 if (read_pass_from_stdin && !opt_state.non_interactive)
623 {
624 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
625 _("--password-from-stdin requires "
626 "--non-interactive"));
627 }
628
629 /* ### This really belongs in libsvn_client. The trouble is,
630 there's no one place there to run it from, no
631 svn_client_init(). We'd have to add it to all the public
632 functions that a client might call. It's unmaintainable to do
633 initialization from within libsvn_client itself, but it seems
634 burdensome to demand that all clients call svn_client_init()
635 before calling any other libsvn_client function... On the other
636 hand, the alternative is effectively to demand that they call
637 svn_config_ensure() instead, so maybe we should have a generic
638 init function anyway. Thoughts? */
639 SVN_ERR(svn_config_ensure(opt_state.config_dir, pool));
640
641 /* If the user asked for help, then the rest of the arguments are
642 the names of subcommands to get help on (if any), or else they're
643 just typos/mistakes. Whatever the case, the subcommand to
644 actually run is svn_cl__help(). */
645 if (opt_state.help)
646 subcommand = svn_opt_get_canonical_subcommand3(svn_min__cmd_table, "help");
647
648 /* If we're not running the `help' subcommand, then look for a
649 subcommand in the first argument. */
650 if (subcommand == NULL)
651 {
652 if (os->ind >= os->argc)
653 {
654 if (opt_state.version)
655 {
656 /* Use the "help" subcommand to handle the "--version" option. */
657 static const svn_opt_subcommand_desc3_t pseudo_cmd =
658 { "--version", svn_min__help, {0}, {""},
659 {opt_version, /* must accept its own option */
660 'q', /* brief output */
661 'v', /* verbose output */
662 opt_config_dir /* all commands accept this */
663 } };
664
665 subcommand = &pseudo_cmd;
666 }
667 else
668 {
669 svn_error_clear
670 (svn_cmdline_fprintf(stderr, pool,
671 _("Subcommand argument required\n")));
672 svn_error_clear(svn_min__help(NULL, NULL, pool));
673 *exit_code = EXIT_FAILURE;
674 return SVN_NO_ERROR;
675 }
676 }
677 else
678 {
679 const char *first_arg;
680
681 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
682 pool));
683 subcommand = svn_opt_get_canonical_subcommand3(svn_min__cmd_table,
684 first_arg);
685 if (subcommand == NULL)
686 {
687 svn_error_clear
688 (svn_cmdline_fprintf(stderr, pool,
689 _("Unknown subcommand: '%s'\n"),
690 first_arg));
691 svn_error_clear(svn_min__help(NULL, NULL, pool));
692
693 *exit_code = EXIT_FAILURE;
694 return SVN_NO_ERROR;
695 }
696 }
697 }
698
699 /* Check that the subcommand wasn't passed any inappropriate options. */
700 for (i = 0; i < received_opts->nelts; i++)
701 {
702 opt_id = APR_ARRAY_IDX(received_opts, i, int);
703
704 /* All commands implicitly accept --help, so just skip over this
705 when we see it. Note that we don't want to include this option
706 in their "accepted options" list because it would be awfully
707 redundant to display it in every commands' help text. */
708 if (opt_id == 'h' || opt_id == '?')
709 continue;
710
711 if (! svn_opt_subcommand_takes_option4(subcommand, opt_id,
712 svn_min__global_options))
713 {
714 const char *optstr;
715 const apr_getopt_option_t *badopt =
716 svn_opt_get_option_from_code3(opt_id, svn_min__options,
717 subcommand, pool);
718 svn_opt_format_option(&optstr, badopt, FALSE, pool);
719 if (subcommand->name[0] == '-')
720 svn_error_clear(svn_min__help(NULL, NULL, pool));
721 else
722 svn_error_clear
723 (svn_cmdline_fprintf
724 (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
725 "Type 'svn-mergeinfo-normalizer help %s' for usage.\n"),
726 subcommand->name, optstr, subcommand->name));
727 *exit_code = EXIT_FAILURE;
728 return SVN_NO_ERROR;
729 }
730 }
731
732 /* --trust-* options can only be used with --non-interactive */
733 if (!opt_state.non_interactive)
734 {
735 if (opt_state.trust_server_cert_unknown_ca)
736 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
737 _("--trust-unknown-ca requires "
738 "--non-interactive"));
739 if (opt_state.trust_server_cert_cn_mismatch)
740 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
741 _("--trust-cn-mismatch requires "
742 "--non-interactive"));
743 if (opt_state.trust_server_cert_expired)
744 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
745 _("--trust-expired requires "
746 "--non-interactive"));
747 if (opt_state.trust_server_cert_not_yet_valid)
748 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
749 _("--trust-not-yet-valid requires "
750 "--non-interactive"));
751 if (opt_state.trust_server_cert_other_failure)
752 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
753 _("--trust-other-failure requires "
754 "--non-interactive"));
755 }
756
757 err = svn_config_get_config(&cfg_hash, opt_state.config_dir, pool);
758 if (err)
759 {
760 /* Fallback to default config if the config directory isn't readable
761 or is not a directory. */
762 if (APR_STATUS_IS_EACCES(err->apr_err)
763 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
764 {
765 svn_handle_warning2(stderr, err, "svn: ");
766 svn_error_clear(err);
767
768 SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
769 }
770 else
771 return err;
772 }
773
774 /* Update the options in the config */
775 if (opt_state.config_options)
776 {
777 svn_error_clear(
778 svn_cmdline__apply_config_options(cfg_hash,
779 opt_state.config_options,
780 "svn: ", "--config-option"));
781 }
782
783 cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
784 #if !defined(SVN_CL_NO_EXCLUSIVE_LOCK)
785 {
786 const char *exclusive_clients_option;
787 apr_array_header_t *exclusive_clients;
788
789 svn_config_get(cfg_config, &exclusive_clients_option,
790 SVN_CONFIG_SECTION_WORKING_COPY,
791 SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS,
792 NULL);
793 exclusive_clients = svn_cstring_split(exclusive_clients_option,
794 " ,", TRUE, pool);
795 for (i = 0; i < exclusive_clients->nelts; ++i)
796 {
797 const char *exclusive_client = APR_ARRAY_IDX(exclusive_clients, i,
798 const char *);
799
800 /* This blocks other clients from accessing the wc.db so it must
801 be explicitly enabled.*/
802 if (!strcmp(exclusive_client, "svn"))
803 svn_config_set(cfg_config,
804 SVN_CONFIG_SECTION_WORKING_COPY,
805 SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
806 "true");
807 }
808 }
809 #endif
810
811 /* Get password from stdin if necessary */
812 if (read_pass_from_stdin)
813 {
814 SVN_ERR(svn_cmdline__stdin_readline(&opt_state.auth_password, pool, pool));
815 }
816
817 /* Create a client context object. */
818 command_baton.opt_state = &opt_state;
819 SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
820 command_baton.ctx = ctx;
821
822 /* Set up our cancellation support. */
823 ctx->cancel_func = svn_min__check_cancel;
824 apr_signal(SIGINT, signal_handler);
825 #ifdef SIGBREAK
826 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
827 apr_signal(SIGBREAK, signal_handler);
828 #endif
829 #ifdef SIGHUP
830 apr_signal(SIGHUP, signal_handler);
831 #endif
832 #ifdef SIGTERM
833 apr_signal(SIGTERM, signal_handler);
834 #endif
835
836 #ifdef SIGPIPE
837 /* Disable SIGPIPE generation for the platforms that have it. */
838 apr_signal(SIGPIPE, SIG_IGN);
839 #endif
840
841 #ifdef SIGXFSZ
842 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
843 * working with large files when compiled against an APR that doesn't have
844 * large file support will crash the program, which is uncool. */
845 apr_signal(SIGXFSZ, SIG_IGN);
846 #endif
847
848 /* Set up Authentication stuff. */
849 SVN_ERR(svn_cmdline_create_auth_baton2(
850 &ab,
851 opt_state.non_interactive,
852 opt_state.auth_username,
853 opt_state.auth_password,
854 opt_state.config_dir,
855 opt_state.no_auth_cache,
856 opt_state.trust_server_cert_unknown_ca,
857 opt_state.trust_server_cert_cn_mismatch,
858 opt_state.trust_server_cert_expired,
859 opt_state.trust_server_cert_not_yet_valid,
860 opt_state.trust_server_cert_other_failure,
861 cfg_config,
862 ctx->cancel_func,
863 ctx->cancel_baton,
864 pool));
865
866 ctx->auth_baton = ab;
867
868 /* Check whether interactive conflict resolution is disabled by
869 * the configuration file. If no --accept option was specified
870 * we postpone all conflicts in this case. */
871 SVN_ERR(svn_config_get_bool(cfg_config, &interactive_conflicts,
872 SVN_CONFIG_SECTION_MISCELLANY,
873 SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS,
874 TRUE));
875
876 /* Get targets from command line - unless we are running "help".
877 * The help sub-command will do its own parsing. */
878 if (strcmp(subcommand->name, "help"))
879 {
880 SVN_ERR(svn_client_args_to_target_array2(&opt_state.targets,
881 os, opt_state.targets,
882 ctx, FALSE, pool));
883
884 /* Add "." if user passed 0 arguments. */
885 svn_opt_push_implicit_dot_target(opt_state.targets, pool);
886 }
887
888 /* And now we finally run the subcommand. */
889 err = (*subcommand->cmd_func)(os, &command_baton, pool);
890 if (err)
891 {
892 /* For argument-related problems, suggest using the 'help'
893 subcommand. */
894 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
895 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
896 {
897 err = svn_error_quick_wrap(
898 err, apr_psprintf(pool,
899 _("Try 'svn help %s' for more information"),
900 subcommand->name));
901 }
902
903 if (err->apr_err == SVN_ERR_AUTHN_FAILED && opt_state.non_interactive)
904 {
905 err = svn_error_quick_wrap(err,
906 _("Authentication failed and interactive"
907 " prompting is disabled; see the"
908 " --force-interactive option"));
909 }
910
911 /* Tell the user about 'svn cleanup' if any error on the stack
912 was about locked working copies. */
913 if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
914 {
915 err = svn_error_quick_wrap(
916 err, _("Run 'svn cleanup' to remove locks "
917 "(type 'svn help cleanup' for details)"));
918 }
919
920 if (err->apr_err == SVN_ERR_SQLITE_BUSY)
921 {
922 err = svn_error_quick_wrap(err,
923 _("Another process is blocking the "
924 "working copy database, or the "
925 "underlying filesystem does not "
926 "support file locking; if the working "
927 "copy is on a network filesystem, make "
928 "sure file locking has been enabled "
929 "on the file server"));
930 }
931
932 if (svn_error_find_cause(err, SVN_ERR_RA_CANNOT_CREATE_TUNNEL) &&
933 (opt_state.auth_username || opt_state.auth_password))
934 {
935 err = svn_error_quick_wrap(
936 err, _("When using svn+ssh:// URLs, keep in mind that the "
937 "--username and --password options are ignored "
938 "because authentication is performed by SSH, not "
939 "Subversion"));
940 }
941
942 return err;
943 }
944
945 return SVN_NO_ERROR;
946 }
947
948 int
main(int argc,const char * argv[])949 main(int argc, const char *argv[])
950 {
951 apr_pool_t *pool;
952 int exit_code = EXIT_SUCCESS;
953 svn_error_t *err;
954
955 /* Initialize the app. */
956 if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
957 return EXIT_FAILURE;
958
959 /* Create our top-level pool. Use a separate mutexless allocator,
960 * given this application is single threaded.
961 */
962 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
963
964 err = sub_main(&exit_code, argc, argv, pool);
965
966 /* Flush stdout and report if it fails. It would be flushed on exit anyway
967 but this makes sure that output is not silently lost if it fails. */
968 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
969
970 if (err)
971 {
972 exit_code = EXIT_FAILURE;
973 svn_cmdline_handle_exit_error(err, NULL, "svn: ");
974 }
975
976 svn_pool_destroy(pool);
977 return exit_code;
978 }
979