monotone

monotone Mtn Source Tree

Root/monotone.cc

1// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
2// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
3// all rights reserved.
4// licensed to the public under the terms of the GNU GPL (>= 2)
5// see the file COPYING for details
6
7#include <config.h>
8
9#include "popt/popt.h"
10#include <cstdio>
11#include <strings.h>
12#include <iterator>
13#include <iostream>
14#include <fstream>
15#include <sstream>
16#include <locale.h>
17
18#include <stdlib.h>
19
20#include "botan/botan.h"
21
22#include "i18n.h"
23
24#include "app_state.hh"
25#include "commands.hh"
26#include "sanity.hh"
27#include "cleanup.hh"
28#include "file_io.hh"
29#include "transforms.hh"
30#include "ui.hh"
31#include "mt_version.hh"
32#include "options.hh"
33#include "paths.hh"
34
35// main option processing and exception handling code
36
37using namespace std;
38
39char * argstr = NULL;
40long arglong = 0;
41
42// Options are split between two tables. The first one is command-specific
43// options (hence the `c' in `coptions'). The second is the global one
44// with options that aren't tied to specific commands.
45//
46// the intent is to ensure that any command specific options mean the same
47// thing to all commands that use them
48
49struct poptOption coptions[] =
50 {
51 {"branch", 'b', POPT_ARG_STRING, &argstr, OPT_BRANCH_NAME, gettext_noop("select branch cert for operation"), NULL},
52 {"revision", 'r', POPT_ARG_STRING, &argstr, OPT_REVISION, gettext_noop("select revision id for operation"), NULL},
53 {"message", 'm', POPT_ARG_STRING, &argstr, OPT_MESSAGE, gettext_noop("set commit changelog message"), NULL},
54 {"message-file", 0, POPT_ARG_STRING, &argstr, OPT_MSGFILE, gettext_noop("set filename containing commit changelog message"), NULL},
55 {"date", 0, POPT_ARG_STRING, &argstr, OPT_DATE, gettext_noop("override date/time for commit"), NULL},
56 {"author", 0, POPT_ARG_STRING, &argstr, OPT_AUTHOR, gettext_noop("override author for commit"), NULL},
57 {"depth", 0, POPT_ARG_LONG, &arglong, OPT_DEPTH, gettext_noop("limit the number of levels of directories to descend"), NULL},
58 {"last", 0, POPT_ARG_LONG, &arglong, OPT_LAST, gettext_noop("limit the log output to the given number of entries"), NULL},
59 {"pid-file", 0, POPT_ARG_STRING, &argstr, OPT_PIDFILE, gettext_noop("record process id of server"), NULL},
60 {"since", 0, POPT_ARG_STRING, &argstr, OPT_SINCE, "set history start for CVS pull", NULL},
61 {"full", 0, POPT_ARG_NONE, &argstr, OPT_FULL, "ignore already pulled CVS revisions", NULL},
62 {"brief", 0, POPT_ARG_NONE, NULL, OPT_BRIEF, gettext_noop("print a brief version of the normal output"), NULL},
63 {"diffs", 0, POPT_ARG_NONE, NULL, OPT_DIFFS, gettext_noop("print diffs along with logs"), NULL},
64 {"no-merges", 0, POPT_ARG_NONE, NULL, OPT_NO_MERGES, gettext_noop("skip merges when printing logs"), NULL},
65 {"set-default", 0, POPT_ARG_NONE, NULL, OPT_SET_DEFAULT, gettext_noop("use the current arguments as the future default"), NULL},
66 {"exclude", 0, POPT_ARG_STRING, &argstr, OPT_EXCLUDE, gettext_noop("leave out anything described by its argument"), NULL},
67 {"unified", 0, POPT_ARG_NONE, NULL, OPT_UNIFIED_DIFF, gettext_noop("use unified diff format"), NULL},
68 {"context", 0, POPT_ARG_NONE, NULL, OPT_CONTEXT_DIFF, gettext_noop("use context diff format"), NULL},
69 {"external", 0, POPT_ARG_NONE, NULL, OPT_EXTERNAL_DIFF, gettext_noop("use external diff hook for generating diffs"), NULL},
70 {"diff-args", 0, POPT_ARG_STRING, &argstr, OPT_EXTERNAL_DIFF_ARGS, gettext_noop("argument to pass external diff hook"), NULL},
71 {"lca", 0, POPT_ARG_NONE, NULL, OPT_LCA, gettext_noop("use least common ancestor as ancestor for merge"), NULL},
72 {"execute", 'e', POPT_ARG_NONE, NULL, OPT_EXECUTE, gettext_noop("perform the associated file operation"), NULL},
73 {"bind", 0, POPT_ARG_STRING, &argstr, OPT_BIND, gettext_noop("address:port to listen on (default :5253)"), NULL},
74 {"missing", 0, POPT_ARG_NONE, NULL, OPT_MISSING, gettext_noop("perform the operations for files missing from working directory"), NULL},
75 {"unknown", 0, POPT_ARG_NONE, NULL, OPT_UNKNOWN, gettext_noop("perform the operations for unknown files from working directory"), NULL},
76 {"key-to-push", 0, POPT_ARG_STRING, &argstr, OPT_KEY_TO_PUSH, gettext_noop("push the specified key even if it hasn't signed anything"), NULL},
77 { NULL, 0, 0, NULL, 0, NULL, NULL }
78 };
79
80struct poptOption options[] =
81 {
82 // Use the coptions table as well.
83 { NULL, 0, POPT_ARG_INCLUDE_TABLE, coptions, 0, NULL, NULL },
84
85 {"debug", 0, POPT_ARG_NONE, NULL, OPT_DEBUG, gettext_noop("print debug log to stderr while running"), NULL},
86 {"dump", 0, POPT_ARG_STRING, &argstr, OPT_DUMP, gettext_noop("file to dump debugging log to, on failure"), NULL},
87 {"quiet", 0, POPT_ARG_NONE, NULL, OPT_QUIET, gettext_noop("suppress log and progress messages"), NULL},
88 {"help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, gettext_noop("display help message"), NULL},
89 {"version", 0, POPT_ARG_NONE, NULL, OPT_VERSION, gettext_noop("print version number, then exit"), NULL},
90 {"full-version", 0, POPT_ARG_NONE, NULL, OPT_FULL_VERSION, gettext_noop("print detailed version number, then exit"), NULL},
91 {"xargs", '@', POPT_ARG_STRING, &argstr, OPT_ARGFILE, gettext_noop("insert command line arguments taken from the given file"), NULL},
92 {"ticker", 0, POPT_ARG_STRING, &argstr, OPT_TICKER, gettext_noop("set ticker style (count|dot|none)"), NULL},
93 {"nostd", 0, POPT_ARG_NONE, NULL, OPT_NOSTD, gettext_noop("do not load standard lua hooks"), NULL},
94 {"norc", 0, POPT_ARG_NONE, NULL, OPT_NORC, gettext_noop("do not load ~/.monotone/monotonerc or MT/monotonerc lua files"), NULL},
95 {"rcfile", 0, POPT_ARG_STRING, &argstr, OPT_RCFILE, gettext_noop("load extra rc file"), NULL},
96 {"key", 'k', POPT_ARG_STRING, &argstr, OPT_KEY_NAME, gettext_noop("set key for signatures"), NULL},
97 {"db", 'd', POPT_ARG_STRING, &argstr, OPT_DB_NAME, gettext_noop("set name of database"), NULL},
98 {"root", 0, POPT_ARG_STRING, &argstr, OPT_ROOT, gettext_noop("limit search for working copy to specified root"), NULL},
99 {"verbose", 0, POPT_ARG_NONE, NULL, OPT_VERBOSE, gettext_noop("verbose completion output"), NULL},
100 {"keydir", 0, POPT_ARG_STRING, &argstr, OPT_KEY_DIR, gettext_noop("set location of key store"), NULL},
101 {"confdir", 0, POPT_ARG_STRING, &argstr, OPT_CONF_DIR, gettext_noop("set location of configuration directory"), NULL},
102 { NULL, 0, 0, NULL, 0, NULL, NULL }
103 };
104
105// there are 3 variables which serve as roots for our system.
106//
107// "global_sanity" is a global object, which contains the error logging
108// system, which is constructed once and used by any nana logging actions.
109// see cleanup.hh for it
110//
111// "cmds" is a static table in commands.cc which associates top-level
112// commands, given on the command-line, to various version control tasks.
113//
114// "app_state" is a non-static object type which contains all the
115// application state (filesystem, database, network, lua interpreter,
116// etc). you can make more than one of these, and feed them to a command in
117// the command table.
118
119// our main function is run inside a boost execution monitor. this monitor
120// portably sets up handlers for various fatal conditions (signals, win32
121// structured exceptions, etc) and provides a uniform reporting interface
122// to any exceptions it catches. we augment this with a helper atexit()
123// which will also dump our internal logs when an explicit clean shutdown
124// flag is not set.
125//
126// in other words, this program should *never* unexpectedly terminate
127// without dumping some diagnostics.
128
129void
130dumper()
131{
132 if (!global_sanity.clean_shutdown)
133 global_sanity.dump_buffer();
134
135 Botan::Init::deinitialize();
136}
137
138
139struct
140utf8_argv
141{
142 int argc;
143 char **argv;
144
145 explicit utf8_argv(int ac, char **av)
146 : argc(ac),
147 argv(static_cast<char **>(malloc(ac * sizeof(char *))))
148 {
149 I(argv != NULL);
150 for (int i = 0; i < argc; ++i)
151 {
152 external ext(av[i]);
153 utf8 utf;
154 system_to_utf8(ext, utf);
155 argv[i] = static_cast<char *>(malloc(utf().size() + 1));
156 I(argv[i] != NULL);
157 memcpy(argv[i], utf().data(), utf().size());
158 argv[i][utf().size()] = static_cast<char>(0);
159 }
160 }
161
162 ~utf8_argv()
163 {
164 if (argv != NULL)
165 {
166 for (int i = 0; i < argc; ++i)
167 if (argv[i] != NULL)
168 free(argv[i]);
169 free(argv);
170 }
171 }
172};
173
174// Stupid type system tricks: to use a cleanup_ptr, we need to know the return
175// type of the cleanup function. But popt silently changed the return type of
176// poptFreeContext at some point, I guess because they thought it would be
177// "backwards compatible". We don't actually _use_ the return value of
178// poptFreeContext, so this little wrapper works.
179static void
180my_poptFreeContext(poptContext con)
181{
182 poptFreeContext(con);
183}
184
185// Read arguments from a file. The special file '-' means stdin.
186// Returned value must be free()'d, after arg parsing has completed.
187static void
188my_poptStuffArgFile(poptContext con, utf8 const & filename)
189{
190 utf8 argstr;
191 {
192 data dat;
193 read_data_for_command_line(filename, dat);
194 external ext(dat());
195 system_to_utf8(ext, argstr);
196 }
197
198 const char **argv = 0;
199 int argc = 0;
200 int rc;
201
202 // Parse the string. It's OK if there are no arguments.
203 rc = poptParseArgvString(argstr().c_str(), &argc, &argv);
204 N(rc >= 0 || rc == POPT_ERROR_NOARG,
205 F("problem parsing arguments from file %s: %s")
206 % filename % poptStrerror(rc));
207
208 if (rc != POPT_ERROR_NOARG)
209 {
210 // poptStuffArgs does not take an argc argument, but rather requires that
211 // the argv array be null-terminated.
212 I(argv[argc] == NULL);
213 N((rc = poptStuffArgs(con, argv)) >= 0,
214 F("weird error when stuffing arguments read from %s: %s\n")
215 % filename % poptStrerror(rc));
216 }
217
218 free(argv);
219}
220
221static string
222coption_string(int o)
223{
224 char buf[2] = { 0,0 };
225 for(struct poptOption *opt = coptions; opt->val; opt++)
226 if (o == opt->val)
227 {
228 buf[0] = opt->shortName;
229 return opt->longName
230 ? string("--") + string(opt->longName)
231 : string("-") + string(buf);
232 }
233 return string();
234}
235
236int
237cpp_main(int argc, char ** argv)
238{
239 int ret = 0;
240
241 atexit(&dumper);
242
243 // go-go gadget i18n
244
245 setlocale(LC_ALL, "");
246 bindtextdomain(PACKAGE, LOCALEDIR);
247 textdomain(PACKAGE);
248
249
250 // we want to catch any early informative_failures due to charset
251 // conversion etc
252 try
253 {
254
255 {
256 std::ostringstream cmdline_ss;
257 for (int i = 0; i < argc; ++i)
258 {
259 if (i)
260 cmdline_ss << ", ";
261 cmdline_ss << "'" << argv[i] << "'";
262 }
263 L(F("command line: %s\n") % cmdline_ss.str());
264 }
265
266 L(F("set locale: LC_ALL=%s\n")
267 % (setlocale(LC_ALL, NULL) == NULL ? "n/a" : setlocale(LC_ALL, NULL)));
268
269 // Set up secure memory allocation etc
270 Botan::Init::initialize();
271 Botan::set_default_allocator("malloc");
272
273 // decode all argv values into a UTF-8 array
274
275 save_initial_path();
276 utf8_argv uv(argc, argv);
277
278 // prepare for arg parsing
279
280 cleanup_ptr<poptContext, void>
281 ctx(poptGetContext(NULL, argc, (char const **) uv.argv, options, 0),
282 &my_poptFreeContext);
283
284 set<int> local_options;
285 for (poptOption *opt = coptions; opt->val; opt++)
286 local_options.insert(opt->val);
287
288 // process main program options
289
290 int opt;
291 bool requested_help = false;
292 set<int> used_local_options;
293
294 poptSetOtherOptionHelp(ctx(), _("[OPTION...] command [ARGS...]\n"));
295
296 try
297 {
298 app_state app;
299
300 while ((opt = poptGetNextOpt(ctx())) > 0)
301 {
302 if (local_options.find(opt) != local_options.end())
303 used_local_options.insert(opt);
304
305 switch(opt)
306 {
307 case OPT_DEBUG:
308 global_sanity.set_debug();
309 break;
310
311 case OPT_QUIET:
312 global_sanity.set_quiet();
313 break;
314
315 case OPT_NOSTD:
316 app.set_stdhooks(false);
317 break;
318
319 case OPT_NORC:
320 app.set_rcfiles(false);
321 break;
322
323 case OPT_VERBOSE:
324 app.set_verbose(true);
325 break;
326
327 case OPT_RCFILE:
328 app.add_rcfile(string(argstr));
329 break;
330
331 case OPT_DUMP:
332 global_sanity.filename = system_path(argstr);
333 break;
334
335 case OPT_DB_NAME:
336 app.set_database(system_path(argstr));
337 break;
338
339 case OPT_KEY_DIR:
340 app.set_key_dir(system_path(argstr));
341 break;
342
343 case OPT_CONF_DIR:
344 app.set_confdir(system_path(argstr));
345 break;
346
347 case OPT_TICKER:
348 if (string(argstr) == "dot")
349 ui.set_tick_writer(new tick_write_dot);
350 else if (string(argstr) == "count")
351 ui.set_tick_writer(new tick_write_count);
352 else if (string(argstr) == "none")
353 ui.set_tick_writer(new tick_write_nothing);
354 else
355 requested_help = true;
356 break;
357
358 case OPT_KEY_NAME:
359 app.set_signing_key(string(argstr));
360 break;
361
362 case OPT_BRANCH_NAME:
363 app.set_branch(string(argstr));
364 app.set_is_explicit_option(OPT_BRANCH_NAME);
365 break;
366
367 case OPT_VERSION:
368 print_version();
369 global_sanity.clean_shutdown = true;
370 return 0;
371
372 case OPT_FULL_VERSION:
373 print_full_version();
374 global_sanity.clean_shutdown = true;
375 return 0;
376
377 case OPT_REVISION:
378 app.add_revision(string(argstr));
379 break;
380
381 case OPT_MESSAGE:
382 app.set_message(string(argstr));
383 app.set_is_explicit_option(OPT_MESSAGE);
384 break;
385
386 case OPT_MSGFILE:
387 app.set_message_file(string(argstr));
388 app.set_is_explicit_option(OPT_MSGFILE);
389 break;
390
391 case OPT_DATE:
392 app.set_date(string(argstr));
393 break;
394
395 case OPT_AUTHOR:
396 app.set_author(string(argstr));
397 break;
398
399 case OPT_ROOT:
400 app.set_root(system_path(argstr));
401 break;
402
403 case OPT_LAST:
404 app.set_last(arglong);
405 break;
406
407 case OPT_DEPTH:
408 app.set_depth(arglong);
409 break;
410
411 case OPT_SINCE:
412 app.set_since(string(argstr));
413 break;
414
415 case OPT_FULL:
416 app.cvspull_full=true;
417 break;
418
419 case OPT_BRIEF:
420 global_sanity.set_brief();
421 break;
422
423 case OPT_DIFFS:
424 app.diffs = true;
425 break;
426
427 case OPT_NO_MERGES:
428 app.no_merges = true;
429 break;
430
431 case OPT_SET_DEFAULT:
432 app.set_default = true;
433 break;
434
435 case OPT_EXCLUDE:
436 app.add_exclude(utf8(string(argstr)));
437 break;
438
439 case OPT_PIDFILE:
440 app.set_pidfile(system_path(argstr));
441 break;
442
443 case OPT_ARGFILE:
444 my_poptStuffArgFile(ctx(), utf8(string(argstr)));
445 break;
446
447 case OPT_UNIFIED_DIFF:
448 app.set_diff_format(unified_diff);
449 break;
450
451 case OPT_CONTEXT_DIFF:
452 app.set_diff_format(context_diff);
453 break;
454
455 case OPT_EXTERNAL_DIFF:
456 app.set_diff_format(external_diff);
457 break;
458
459 case OPT_EXTERNAL_DIFF_ARGS:
460 app.set_diff_args(utf8(string(argstr)));
461 break;
462
463 case OPT_LCA:
464 app.use_lca = true;
465 break;
466
467 case OPT_EXECUTE:
468 app.execute = true;
469 break;
470
471 case OPT_BIND:
472 {
473 std::string arg(argstr);
474 std::string addr_part, port_part;
475 size_t l_colon = arg.find(':');
476 size_t r_colon = arg.rfind(':');
477
478 // not an ipv6 address, as that would have at least two colons
479 if (l_colon == r_colon)
480 {
481 addr_part = (r_colon == std::string::npos ? arg : arg.substr(0, r_colon));
482 port_part = (r_colon == std::string::npos ? "" : arg.substr(r_colon+1, arg.size() - r_colon));
483 }
484 else
485 {
486 // IPv6 addresses have a port specified in the style: [2001:388:0:13::]:80
487 size_t squareb = arg.rfind(']');
488 if ((arg.find('[') == 0) && (squareb != std::string::npos))
489 {
490 if (squareb < r_colon)
491 port_part = (r_colon == std::string::npos ? "" : arg.substr(r_colon+1, arg.size() - r_colon));
492 else
493 port_part = "";
494 addr_part = (squareb == std::string::npos ? arg.substr(1, arg.size()) : arg.substr(1, squareb-1));
495 }
496 else
497 {
498 addr_part = arg;
499 port_part = "";
500 }
501 }
502 app.bind_address = utf8(addr_part);
503 app.bind_port = utf8(port_part);
504 }
505 app.set_is_explicit_option(OPT_BIND);
506 break;
507
508 case OPT_MISSING:
509 app.missing = true;
510 break;
511
512 case OPT_UNKNOWN:
513 app.unknown = true;
514 break;
515
516 case OPT_KEY_TO_PUSH:
517 {
518 app.add_key_to_push(string(argstr));
519 }
520 break;
521
522 case OPT_HELP:
523 default:
524 requested_help = true;
525 break;
526 }
527 }
528
529 // verify that there are no errors in the command line
530
531 N(opt == -1,
532 F("syntax error near the \"%s\" option: %s") %
533 poptBadOption(ctx(), POPT_BADOPTION_NOALIAS) % poptStrerror(opt));
534
535 // complete the command if necessary
536
537 string cmd;
538 if (poptPeekArg(ctx()))
539 {
540 cmd = commands::complete_command(poptGetArg(ctx()));
541 }
542
543 // stop here if they asked for help
544
545 if (requested_help)
546 {
547 throw usage(cmd); // cmd may be empty, and that's fine.
548 }
549
550 // at this point we allow a working copy (meaning search for it
551 // and if found read MT/options) but don't require it. certain
552 // commands may subsequently require a working copy or fail
553
554 app.allow_working_copy();
555
556 // main options processed, now invoke the
557 // sub-command w/ remaining args
558
559 if (cmd.empty())
560 {
561 throw usage("");
562 }
563 else
564 {
565 // Make sure the local options used are really used by the
566 // given command.
567 set<int> command_options = commands::command_options(cmd);
568 for (set<int>::const_iterator i = used_local_options.begin();
569 i != used_local_options.end(); ++i)
570 N(command_options.find(*i) != command_options.end(),
571 F("monotone %s doesn't use the option %s")
572 % cmd % coption_string(*i));
573
574 vector<utf8> args;
575 while(poptPeekArg(ctx()))
576 {
577 args.push_back(utf8(string(poptGetArg(ctx()))));
578 }
579 ret = commands::process(app, cmd, args);
580 }
581 }
582 catch (usage & u)
583 {
584 // Make sure to hide documentation that's not part of
585 // the current command.
586 set<int> command_options = commands::command_options(u.which);
587 int count = 0;
588 for (poptOption *o = coptions; o->val != 0; o++)
589 {
590 if (command_options.find(o->val) != command_options.end())
591 {
592 o->argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN;
593 L(F("Removed 'hidden' from option # %d\n") % o->argInfo);
594 count++;
595 }
596 else
597 {
598 o->argInfo |= POPT_ARGFLAG_DOC_HIDDEN;
599 L(F("Added 'hidden' to option # %d\n") % o->argInfo);
600 }
601 }
602 free((void *)options[0].descrip); options[0].descrip = NULL;
603 if (count != 0)
604 {
605 ostringstream sstr;
606 sstr << F("Options specific to 'monotone %s':") % u.which;
607 options[0].descrip = strdup(sstr.str().c_str());
608
609 options[0].argInfo |= POPT_ARGFLAG_DOC_HIDDEN;
610 L(F("Added 'hidden' to option # %d\n") % options[0].argInfo);
611 }
612
613 poptPrintHelp(ctx(), stdout, 0);
614 cout << endl;
615 commands::explain_usage(u.which, cout);
616 global_sanity.clean_shutdown = true;
617 return 2;
618 }
619 }
620 catch (informative_failure & inf)
621 {
622 ui.inform(inf.what);
623 global_sanity.clean_shutdown = true;
624 return 1;
625 }
626 catch (std::ios_base::failure const & ex)
627 {
628 global_sanity.clean_shutdown = true;
629 return 1;
630 }
631
632 global_sanity.clean_shutdown = true;
633 return ret;
634}

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status