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