monotone

monotone Mtn Source Tree

Root/commands.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2// Copyright (C) 2007 Julio M. Merino Vidal <jmmv@NetBSD.org>
3//
4// This program is made available under the GNU GPL version 2.0 or
5// greater. See the accompanying file COPYING for details.
6//
7// This program is distributed WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9// PURPOSE.
10
11#include "base.hh"
12#include <map>
13#include <algorithm>
14#include <iostream>
15
16#include "transforms.hh"
17#include "simplestring_xform.hh"
18#include "file_io.hh"
19#include "charset.hh"
20#include "diff_patch.hh"
21#include "inodeprint.hh"
22#include "cert.hh"
23#include "ui.hh"
24#include "cmd.hh"
25#include "constants.hh"
26#include "app_state.hh"
27
28#ifndef _WIN32
29#include "lexical_cast.hh"
30#include <signal.h>
31#endif
32
33using std::cin;
34using std::make_pair;
35using std::map;
36using std::ostream;
37using std::pair;
38using std::set;
39using std::string;
40using std::strlen;
41using std::vector;
42
43CMD_GROUP(__root__, "__root__", "", NULL, "", "");
44
45//
46// Definition of top-level commands, used to classify the real commands
47// in logical groups.
48//
49// These top level commands, while part of the final identifiers and defined
50// as regular command groups, are handled separately. The user should not
51// see them except through the help command.
52//
53// XXX This is to easily maintain compatibilty with older versions. But
54// maybe this should be revised, because exposing the top level category
55// (being optional, of course), may not be a bad idea.
56//
57CMD_GROUP_NO_COMPLETE(automation, "automation", "", CMD_REF(__root__),
58 N_("Commands that aid in scripted execution"),
59 "");
60CMD_GROUP(database, "database", "", CMD_REF(__root__),
61 N_("Commands that manipulate the database"),
62 "");
63CMD_GROUP(debug, "debug", "", CMD_REF(__root__),
64 N_("Commands that aid in program debugging"),
65 "");
66CMD_GROUP(informative, "informative", "", CMD_REF(__root__),
67 N_("Commands for information retrieval"),
68 "");
69CMD_GROUP(key_and_cert, "key_and_cert", "", CMD_REF(__root__),
70 N_("Commands to manage keys and certificates"),
71 "");
72CMD_GROUP(network, "network", "", CMD_REF(__root__),
73 N_("Commands that access the network"),
74 "");
75CMD_GROUP(packet_io, "packet_io", "", CMD_REF(__root__),
76 N_("Commands for packet reading and writing"),
77 "");
78CMD_GROUP(rcs, "rcs", "", CMD_REF(__root__),
79 N_("Commands for interaction with RCS and CVS"),
80 "");
81CMD_GROUP(review, "review", "", CMD_REF(__root__),
82 N_("Commands to review revisions"),
83 "");
84CMD_GROUP(tree, "tree", "", CMD_REF(__root__),
85 N_("Commands to manipulate the tree"),
86 "");
87CMD_GROUP(variables, "variables", "", CMD_REF(__root__),
88 N_("Commands to manage persistent variables"),
89 "");
90CMD_GROUP(workspace, "workspace", "", CMD_REF(__root__),
91 N_("Commands that deal with the workspace"),
92 "");
93
94// this file defines the task-oriented "top level" commands which can be
95// issued as part of a monotone command line. the command line can only
96// have one such command on it, followed by a vector of strings which are its
97// arguments. all --options will be processed by the main program *before*
98// calling a command
99//
100// we might expose this blunt command interface to scripting someday. but
101// not today.
102
103namespace commands
104{
105 const char * safe_gettext(const char * msgid)
106 {
107 if (strlen(msgid) == 0)
108 return msgid;
109
110 return _(msgid);
111 }
112
113 // This must be a pointer.
114 // It's used by the constructor of other static objects in different
115 // files (cmd_*.cc), and since they're in different files, there's no
116 // guarantee about what order they'll be initialized in. So have this
117 // be something that doesn't get automatic initialization, and initialize
118 // it ourselves the first time we use it.
119 typedef map< command *, command * > relation_map;
120 static relation_map * cmds_relation_map = NULL;
121
122 static void init_children(void)
123 {
124 static bool children_inited = false;
125
126 if (!children_inited)
127 {
128 children_inited = true;
129
130 for (relation_map::iterator iter = cmds_relation_map->begin();
131 iter != cmds_relation_map->end(); iter++)
132 {
133 if ((*iter).second != NULL)
134 (*iter).second->children().insert((*iter).first);
135 }
136 }
137 }
138}
139
140//
141// Implementation of the commands::command class.
142//
143namespace commands {
144 command::command(std::string const & primary_name,
145 std::string const & other_names,
146 command * parent,
147 bool hidden,
148 std::string const & params,
149 std::string const & abstract,
150 std::string const & desc,
151 bool use_workspace_options,
152 options::options_type const & opts,
153 bool _allow_completion)
154 : m_primary_name(utf8(primary_name)),
155 m_parent(parent),
156 m_hidden(hidden),
157 m_params(utf8(params)),
158 m_abstract(utf8(abstract)),
159 m_desc(utf8(desc)),
160 m_use_workspace_options(use_workspace_options),
161 m_opts(opts),
162 m_allow_completion(_allow_completion)
163 {
164 // A warning about the parent pointer: commands are defined as global
165 // variables, so they are initialized during program startup. As they
166 // are spread over different compilation units, we have no idea of the
167 // order in which they will be initialized. Therefore, accessing
168 // *parent from here is dangerous.
169 //
170 // This is the reason for the cmds_relation_map. We cannot set up
171 // the m_children set until a late stage during program execution.
172
173 if (cmds_relation_map == NULL)
174 cmds_relation_map = new relation_map();
175 (*cmds_relation_map)[this] = m_parent;
176
177 m_names.insert(m_primary_name);
178
179 vector< utf8 > onv = split_into_words(utf8(other_names));
180 m_names.insert(onv.begin(), onv.end());
181 }
182
183 command::~command(void)
184 {
185 }
186
187 bool
188 command::allow_completion() const
189 {
190 return m_allow_completion &&
191 (m_parent?m_parent->allow_completion():true);
192 }
193
194 command_id
195 command::ident(void) const
196 {
197 I(this != CMD_REF(__root__));
198
199 command_id i;
200
201 if (parent() != CMD_REF(__root__))
202 i = parent()->ident();
203 i.push_back(primary_name());
204
205 I(!i.empty());
206 return i;
207 }
208
209 const utf8 &
210 command::primary_name(void) const
211 {
212 return m_primary_name;
213 }
214
215 const command::names_set &
216 command::names(void) const
217 {
218 return m_names;
219 }
220
221 command *
222 command::parent(void) const
223 {
224 return m_parent;
225 }
226
227 bool
228 command::hidden(void) const
229 {
230 return m_hidden;
231 }
232
233 std::string
234 command::params() const
235 {
236 return safe_gettext(m_params().c_str());
237 }
238
239 std::string
240 command::abstract() const
241 {
242 return safe_gettext(m_abstract().c_str());
243 }
244
245 std::string
246 command::desc() const
247 {
248 return abstract() + ".\n" + safe_gettext(m_desc().c_str());
249 }
250
251 options::options_type const &
252 command::opts(void) const
253 {
254 return m_opts;
255 }
256
257 bool
258 command::use_workspace_options(void) const
259 {
260 return m_use_workspace_options;
261 }
262
263 command::children_set &
264 command::children(void)
265 {
266 init_children();
267 return m_children;
268 }
269
270 command::children_set const &
271 command::children(void) const
272 {
273 init_children();
274 return m_children;
275 }
276
277 bool
278 command::is_leaf(void) const
279 {
280 return children().empty();
281 }
282
283 bool
284 command::operator<(command const & cmd) const
285 {
286 // *twitch*
287 return (parent()->primary_name() < cmd.parent()->primary_name() ||
288 ((parent() == cmd.parent()) &&
289 primary_name() < cmd.primary_name()));
290 }
291
292 bool
293 command::has_name(utf8 const & name) const
294 {
295 return names().find(name) != names().end();
296 }
297
298 command const *
299 command::find_command(command_id const & id) const
300 {
301 command const * cmd;
302
303 if (id.empty())
304 cmd = this;
305 else
306 {
307 utf8 component = *(id.begin());
308 command * match = find_child_by_name(component);
309
310 if (match != NULL)
311 {
312 command_id remaining(id.begin() + 1, id.end());
313 I(remaining.size() == id.size() - 1);
314 cmd = match->find_command(remaining);
315 }
316 else
317 cmd = NULL;
318 }
319
320 return cmd;
321 }
322
323 map< command_id, command * >
324 command::find_completions(utf8 const & prefix, command_id const & completed,
325 bool completion_ok)
326 const
327 {
328 map< command_id, command * > matches;
329
330 I(!prefix().empty());
331
332 for (children_set::const_iterator iter = children().begin();
333 iter != children().end(); iter++)
334 {
335 command * child = *iter;
336
337 for (names_set::const_iterator iter2 = child->names().begin();
338 iter2 != child->names().end(); iter2++)
339 {
340 command_id caux = completed;
341 caux.push_back(*iter2);
342
343 // If one of the command names was an exact match,
344 // do not try to find other possible completions.
345 // This would eventually hinder us to ever call a command
346 // whose name is also the prefix for another command in the
347 // same group (f.e. mtn automate cert and mtn automate certs)
348 if (prefix == *iter2)
349 {
350 // since the command children are not sorted, we
351 // need to ensure that no other partial completed
352 // commands matched
353 matches.clear();
354 matches[caux] = child;
355 return matches;
356 }
357
358 if (!child->hidden() &&
359 prefix().length() < (*iter2)().length() &&
360 allow_completion() && completion_ok)
361 {
362 string temp((*iter2)(), 0, prefix().length());
363 utf8 p(temp);
364 if (prefix == p)
365 matches[caux] = child;
366 }
367 }
368 }
369
370 return matches;
371 }
372
373 set< command_id >
374 command::complete_command(command_id const & id,
375 command_id completed,
376 bool completion_ok) const
377 {
378 I(this != CMD_REF(__root__) || !id.empty());
379 I(!id.empty());
380
381 set< command_id > matches;
382
383 utf8 component = *(id.begin());
384 command_id remaining(id.begin() + 1, id.end());
385
386 map< command_id, command * >
387 m2 = find_completions(component,
388 completed,
389 allow_completion() && completion_ok);
390 for (map< command_id, command * >::const_iterator iter = m2.begin();
391 iter != m2.end(); iter++)
392 {
393 command_id const & i2 = (*iter).first;
394 command * child = (*iter).second;
395
396 if (child->is_leaf() || remaining.empty())
397 matches.insert(i2);
398 else
399 {
400 I(remaining.size() == id.size() - 1);
401 command_id caux = completed;
402 caux.push_back(i2[i2.size() - 1]);
403 set< command_id > maux = child->complete_command(remaining, caux);
404 if (maux.empty())
405 matches.insert(i2);
406 else
407 matches.insert(maux.begin(), maux.end());
408 }
409 }
410
411 return matches;
412 }
413
414 command *
415 command::find_child_by_name(utf8 const & name) const
416 {
417 I(!name().empty());
418
419 command * cmd = NULL;
420
421 for (children_set::const_iterator iter = children().begin();
422 iter != children().end() && cmd == NULL; iter++)
423 {
424 command * child = *iter;
425
426 if (child->has_name(name))
427 cmd = child;
428 }
429
430 return cmd;
431 }
432};
433
434namespace std
435{
436 template <>
437 struct greater<commands::command *>
438 {
439 bool operator()(commands::command const * a, commands::command const * b)
440 {
441 return *a < *b;
442 }
443 };
444};
445
446namespace commands
447{
448 command_id
449 complete_command(args_vector const & args)
450 {
451 // Handle categories early; no completion allowed.
452 if (CMD_REF(__root__)->find_command(make_command_id(args[0]())) != NULL)
453 return make_command_id(args[0]());
454
455 command_id id;
456 for (args_vector::const_iterator iter = args.begin();
457 iter != args.end(); iter++)
458 id.push_back(utf8((*iter)()));
459
460 set< command_id > matches;
461
462 command::children_set const & cs = CMD_REF(__root__)->children();
463 for (command::children_set::const_iterator iter = cs.begin();
464 iter != cs.end(); iter++)
465 {
466 command const * child = *iter;
467
468 set< command_id > m2 = child->complete_command(id, child->ident());
469 matches.insert(m2.begin(), m2.end());
470 }
471
472 if (matches.size() >= 2)
473 {
474 // If there is an exact match at the lowest level, pick it. Needed
475 // to automatically resolve ambiguities between, e.g., 'drop' and
476 // 'dropkey'.
477 command_id tmp;
478
479 for (set< command_id >::const_iterator iter = matches.begin();
480 iter != matches.end() && tmp.empty(); iter++)
481 {
482 command_id const & id = *iter;
483 I(id.size() >= 2);
484 if (id[id.size() - 1]() == args[id.size() - 2]())
485 tmp = id;
486 }
487
488 if (!tmp.empty())
489 {
490 matches.clear();
491 matches.insert(tmp);
492 }
493 }
494
495 if (matches.empty())
496 {
497 N(false,
498 F("unknown command '%s'") % join_words(id)());
499 }
500 else if (matches.size() == 1)
501 {
502 id = *matches.begin();
503 }
504 else
505 {
506 I(matches.size() > 1);
507 string err =
508 (F("'%s' is ambiguous; possible completions are:") %
509 join_words(id)()).str();
510 for (set< command_id >::const_iterator iter = matches.begin();
511 iter != matches.end(); iter++)
512 err += '\n' + join_words(*iter)();
513 N(false, i18n_format(err));
514 }
515
516 I(!id.empty());
517 return id;
518 }
519
520 static command const *
521 find_command(command_id const & ident)
522 {
523 command const * cmd = CMD_REF(__root__)->find_command(ident);
524
525 // This function is only used internally with an identifier returned
526 // by complete_command. Therefore, it must always exist.
527 I(cmd != NULL);
528
529 return cmd;
530 }
531
532 // Prints the abstract description of the given command or command group
533 // properly indented. The tag starts at column two. The description has
534 // to start, at the very least, two spaces after the tag's end position;
535 // this is given by the colabstract parameter.
536 static void describe(const string & tag, const string & abstract,
537 size_t colabstract, ostream & out)
538 {
539 I(colabstract > 0);
540
541 size_t col = 0;
542 out << " " << tag << " ";
543 col += display_width(utf8(tag + " "));
544
545 out << string(colabstract - col, ' ');
546 col = colabstract;
547 out << format_text(abstract, colabstract, col) << '\n';
548 }
549
550 static void explain_children(command::children_set const & children,
551 ostream & out)
552 {
553 I(children.size() > 0);
554
555 vector< command const * > sorted;
556
557 size_t colabstract = 0;
558 for (command::children_set::const_iterator i = children.begin();
559 i != children.end(); i++)
560 {
561 command const * child = *i;
562
563 if (child->hidden())
564 continue;
565
566 size_t len = display_width(join_words(child->names(), ", ")) +
567 display_width(utf8(" "));
568 if (colabstract < len)
569 colabstract = len;
570
571 sorted.push_back(child);
572 }
573
574 sort(sorted.begin(), sorted.end(), std::greater< command * >());
575
576 for (vector< command const * >::const_iterator i = sorted.begin();
577 i != sorted.end(); i++)
578 describe(join_words((*i)->names(), ", ")(), (*i)->abstract(),
579 colabstract, out);
580 }
581
582 static void explain_cmd_usage(command_id const & ident, ostream & out)
583 {
584 I(ident.size() >= 1);
585
586 vector< string > lines;
587 command const * cmd = find_command(ident);
588
589 string visibleid = join_words(vector< utf8 >(ident.begin() + 1,
590 ident.end()))();
591
592 if (visibleid.empty())
593 out << format_text(F(safe_gettext("Commands in group '%s':")) %
594 join_words(ident)())
595 << "\n\n";
596 else
597 {
598 if (cmd->children().size() > 0)
599 out << format_text(F(safe_gettext("Subcommands of '%s %s':")) %
600 ui.prog_name % visibleid)
601 << "\n\n";
602 else
603 out << format_text(F(safe_gettext("Syntax specific to '%s %s':")) %
604 ui.prog_name % visibleid)
605 << "\n\n";
606 }
607
608 // Print command parameters.
609 string params = cmd->params();
610 split_into_lines(params, lines);
611 if (lines.size() > 0)
612 {
613 for (vector<string>::const_iterator j = lines.begin();
614 j != lines.end(); ++j)
615 out << " " << visibleid << ' ' << *j << '\n';
616 out << '\n';
617 }
618
619 // Explain children, if any.
620 if (!cmd->is_leaf())
621 {
622 explain_children(cmd->children(), out);
623 out << '\n';
624 }
625
626 // Print command description.
627 if (visibleid.empty())
628 out << format_text(F(safe_gettext("Purpose of group '%s':")) %
629 join_words(ident)())
630 << "\n\n";
631 else
632 out << format_text(F(safe_gettext("Description for '%s %s':")) %
633 ui.prog_name % visibleid)
634 << "\n\n";
635 out << format_text(cmd->desc(), 2) << "\n\n";
636
637 // Print all available aliases.
638 if (cmd->names().size() > 1)
639 {
640 command::names_set othernames = cmd->names();
641 othernames.erase(ident[ident.size() - 1]);
642 out << format_text(F("Aliases: %s.") %
643 join_words(othernames, ", ")(), 2)
644 << '\n';
645 }
646 }
647
648 command_id make_command_id(std::string const & path)
649 {
650 return split_into_words(utf8(path));
651 }
652
653 void explain_usage(command_id const & ident, ostream & out)
654 {
655 command const * cmd = find_command(ident);
656
657 if (ident.empty())
658 {
659 out << format_text("Command groups:") << "\n\n";
660 explain_children(CMD_REF(__root__)->children(), out);
661 out << '\n'
662 << format_text("For information on a specific command, type "
663 "'mtn help <command_name> [subcommand_name ...]'.")
664 << '\n'
665 << format_text("To see the commands available within a group, "
666 "type 'mtn help <group_name>'.")
667 << '\n'
668 << format_text("Note that you can always abbreviate a command "
669 "name as long as it does not conflict with other "
670 "names.")
671 << "\n\n";
672 }
673 else
674 explain_cmd_usage(ident, out);
675 }
676
677 void process(app_state & app, command_id const & ident,
678 args_vector const & args)
679 {
680 command const * cmd = CMD_REF(__root__)->find_command(ident);
681
682 string visibleid = join_words(vector< utf8 >(ident.begin() + 1,
683 ident.end()))();
684
685 N(!(!cmd->is_leaf() && cmd->parent() == CMD_REF(__root__)),
686 F("command '%s' is invalid; it is a group") % join_words(ident));
687
688 N(!(!cmd->is_leaf() && args.empty()),
689 F("no subcommand specified for '%s'") % visibleid);
690
691 N(!(!cmd->is_leaf() && !args.empty()),
692 F("could not match '%s' to a subcommand of '%s'") %
693 join_words(args) % visibleid);
694
695 L(FL("executing command '%s'") % visibleid);
696
697 // at this point we process the data from _MTN/options if
698 // the command needs it.
699 if (cmd->use_workspace_options())
700 app.process_options();
701
702 cmd->exec(app, ident, args);
703 }
704
705 options::options_type command_options(command_id const & ident)
706 {
707 command const * cmd = find_command(ident);
708 return cmd->opts();
709 }
710}
711////////////////////////////////////////////////////////////////////////
712
713CMD(help, "help", "", CMD_REF(informative), N_("command [ARGS...]"),
714 N_("Displays help about commands and options"),
715 "",
716 options::opts::none)
717{
718 if (args.size() < 1)
719 {
720 app.opts.help = true;
721 throw usage(command_id());
722 }
723
724 command_id id = commands::complete_command(args);
725 app.opts.help = true;
726 throw usage(id);
727}
728
729CMD_HIDDEN(crash, "crash", "", CMD_REF(debug),
730 "{ N | E | I | exception | signal }",
731 N_("Triggers the specified kind of crash"),
732 "",
733 options::opts::none)
734{
735 if (args.size() != 1)
736 throw usage(execid);
737 bool spoon_exists(false);
738 if (idx(args,0)() == "N")
739 N(spoon_exists, i18n_format("There is no spoon."));
740 else if (idx(args,0)() == "E")
741 E(spoon_exists, i18n_format("There is no spoon."));
742 else if (idx(args,0)() == "I")
743 {
744 I(spoon_exists);
745 }
746#define maybe_throw(ex) if(idx(args,0)()==#ex) throw ex("There is no spoon.")
747#define maybe_throw_bare(ex) if(idx(args,0)()==#ex) throw ex()
748 else maybe_throw_bare(std::bad_alloc);
749 else maybe_throw_bare(std::bad_cast);
750 else maybe_throw_bare(std::bad_typeid);
751 else maybe_throw_bare(std::bad_exception);
752 else maybe_throw_bare(std::exception);
753 else maybe_throw(std::domain_error);
754 else maybe_throw(std::invalid_argument);
755 else maybe_throw(std::length_error);
756 else maybe_throw(std::out_of_range);
757 else maybe_throw(std::range_error);
758 else maybe_throw(std::overflow_error);
759 else maybe_throw(std::underflow_error);
760 else maybe_throw(std::logic_error);
761 else maybe_throw(std::runtime_error);
762 else
763 {
764#ifndef _WIN32
765 try
766 {
767 int signo = boost::lexical_cast<int>(idx(args,0)());
768 if (0 < signo && signo <= 15)
769 {
770 raise(signo);
771 // control should not get here...
772 I(!"crash: raise returned");
773 }
774 }
775 catch (boost::bad_lexical_cast&)
776 { // fall through and throw usage
777 }
778#endif
779 throw usage(execid);
780 }
781#undef maybe_throw
782#undef maybe_throw_bare
783}
784
785string
786describe_revision(app_state & app,
787 revision_id const & id)
788{
789 cert_name author_name(author_cert_name);
790 cert_name date_name(date_cert_name);
791
792 string description;
793
794 description += id.inner()();
795
796 // append authors and date of this revision
797 vector< revision<cert> > tmp;
798 app.get_project().get_revision_certs_by_name(id, author_name, tmp);
799 for (vector< revision<cert> >::const_iterator i = tmp.begin();
800 i != tmp.end(); ++i)
801 {
802 cert_value tv;
803 decode_base64(i->inner().value, tv);
804 description += " ";
805 description += tv();
806 }
807 app.get_project().get_revision_certs_by_name(id, date_name, tmp);
808 for (vector< revision<cert> >::const_iterator i = tmp.begin();
809 i != tmp.end(); ++i)
810 {
811 cert_value tv;
812 decode_base64(i->inner().value, tv);
813 description += " ";
814 description += tv();
815 }
816
817 return description;
818}
819
820
821void
822complete(app_state & app,
823 string const & str,
824 set<revision_id> & completion,
825 bool must_exist)
826{
827 // This copies the start of selectors::parse_selector().to avoid
828 // getting a log when there's no expansion happening...:
829 //
830 // this rule should always be enabled, even if the user specifies
831 // --norc: if you provide a revision id, you get a revision id.
832 if (str.find_first_not_of(constants::legal_id_bytes) == string::npos
833 && str.size() == constants::idlen)
834 {
835 completion.insert(revision_id(hexenc<id>(id(str))));
836 if (must_exist)
837 N(app.db.revision_exists(*completion.begin()),
838 F("no such revision '%s'") % *completion.begin());
839 return;
840 }
841
842 vector<pair<selectors::selector_type, string> >
843 sels(selectors::parse_selector(str, app));
844
845 P(F("expanding selection '%s'") % str);
846
847 // we jam through an "empty" selection on sel_ident type
848 set<string> completions;
849 selectors::selector_type ty = selectors::sel_ident;
850 selectors::complete_selector("", sels, ty, completions, app);
851
852 N(completions.size() != 0,
853 F("no match for selection '%s'") % str);
854
855 for (set<string>::const_iterator i = completions.begin();
856 i != completions.end(); ++i)
857 {
858 pair<set<revision_id>::const_iterator, bool> p =
859 completion.insert(revision_id(hexenc<id>(id(*i))));
860 P(F("expanded to '%s'") % *(p.first));
861 }
862}
863
864
865void
866complete(app_state & app,
867 string const & str,
868 revision_id & completion,
869 bool must_exist)
870{
871 set<revision_id> completions;
872
873 complete(app, str, completions, must_exist);
874
875 if (completions.size() > 1)
876 {
877 string err = (F("selection '%s' has multiple ambiguous expansions:") % str).str();
878 for (set<revision_id>::const_iterator i = completions.begin();
879 i != completions.end(); ++i)
880 err += ("\n" + describe_revision(app, *i));
881 N(completions.size() == 1, i18n_format(err));
882 }
883
884 completion = *completions.begin();
885}
886
887void
888notify_if_multiple_heads(app_state & app)
889{
890 set<revision_id> heads;
891 app.get_project().get_branch_heads(app.opts.branchname, heads);
892 if (heads.size() > 1) {
893 string prefixedline;
894 prefix_lines_with(_("note: "),
895 _("branch '%s' has multiple heads\n"
896 "perhaps consider '%s merge'"),
897 prefixedline);
898 P(i18n_format(prefixedline) % app.opts.branchname % ui.prog_name);
899 }
900}
901
902void
903process_commit_message_args(bool & given,
904 utf8 & log_message,
905 app_state & app,
906 utf8 message_prefix)
907{
908 // can't have both a --message and a --message-file ...
909 N(!app.opts.message_given || !app.opts.msgfile_given,
910 F("--message and --message-file are mutually exclusive"));
911
912 if (app.opts.message_given)
913 {
914 string msg;
915 join_lines(app.opts.message, msg);
916 log_message = utf8(msg);
917 if (message_prefix().length() != 0)
918 log_message = utf8(message_prefix() + "\n\n" + log_message());
919 given = true;
920 }
921 else if (app.opts.msgfile_given)
922 {
923 data dat;
924 read_data_for_command_line(app.opts.msgfile, dat);
925 external dat2 = external(dat());
926 system_to_utf8(dat2, log_message);
927 if (message_prefix().length() != 0)
928 log_message = utf8(message_prefix() + "\n\n" + log_message());
929 given = true;
930 }
931 else if (message_prefix().length() != 0)
932 {
933 log_message = message_prefix;
934 given = true;
935 }
936 else
937 given = false;
938}
939
940#ifdef BUILD_UNIT_TESTS
941#include "unit_tests.hh"
942
943CMD_GROUP(top, "top", "", CMD_REF(__root__),
944 "", "");
945CMD(test, "test", "", CMD_REF(top),
946 "", "", "", options::opts::none) {}
947CMD(test1, "test1", "alias1", CMD_REF(top),
948 "", "", "", options::opts::none) {}
949CMD(test2, "test2", "alias2", CMD_REF(top),
950 "", "", "", options::opts::none) {}
951CMD_HIDDEN(test3, "test3", "", CMD_REF(top),
952 "", "", "", options::opts::none) {}
953
954CMD_GROUP(testg, "testg", "aliasg", CMD_REF(top),
955 "", "");
956CMD(testg1, "testg1", "", CMD_REF(testg),
957 "", "", "", options::opts::none) {}
958CMD(testg2, "testg2", "", CMD_REF(testg),
959 "", "", "", options::opts::none) {}
960CMD_HIDDEN(testg3, "testg3", "", CMD_REF(testg),
961 "", "", "", options::opts::none) {}
962
963static args_vector
964mkargs(const char *words)
965{
966 return split_into_words(arg_type(words));
967}
968
969UNIT_TEST(commands, make_command_id)
970{
971 using commands::command_id;
972 using commands::make_command_id;
973
974 {
975 command_id id = make_command_id("foo");
976 UNIT_TEST_CHECK(id.size() == 1);
977 UNIT_TEST_CHECK(id[0]() == "foo");
978 }
979
980 {
981 command_id id = make_command_id("foo bar");
982 UNIT_TEST_CHECK(id.size() == 2);
983 UNIT_TEST_CHECK(id[0]() == "foo");
984 UNIT_TEST_CHECK(id[1]() == "bar");
985 }
986}
987
988UNIT_TEST(commands, complete_command)
989{
990 using commands::command_id;
991 using commands::complete_command;
992 using commands::make_command_id;
993
994 // Single-word identifier, top-level category.
995 {
996 command_id id = complete_command(mkargs("top"));
997 UNIT_TEST_CHECK(id == make_command_id("top"));
998 }
999
1000 // Single-word identifier.
1001 {
1002 command_id id = complete_command(mkargs("testg"));
1003 UNIT_TEST_CHECK(id == make_command_id("top testg"));
1004 }
1005
1006 // Single-word identifier, non-primary name.
1007 {
1008 command_id id = complete_command(mkargs("alias1"));
1009 UNIT_TEST_CHECK(id == make_command_id("top alias1"));
1010 }
1011
1012 // Multi-word identifier.
1013 {
1014 command_id id = complete_command(mkargs("testg testg1"));
1015 UNIT_TEST_CHECK(id == make_command_id("top testg testg1"));
1016 }
1017
1018 // Multi-word identifier, non-primary names.
1019 {
1020 command_id id = complete_command(mkargs("al testg1"));
1021 UNIT_TEST_CHECK(id == make_command_id("top aliasg testg1"));
1022 }
1023}
1024
1025UNIT_TEST(commands, command_complete_command)
1026{
1027 using commands::command_id;
1028 using commands::make_command_id;
1029
1030 // Non-existent single-word identifier.
1031 {
1032 command_id id = make_command_id("foo");
1033 set< command_id > matches = CMD_REF(top)->complete_command(id);
1034 UNIT_TEST_REQUIRE(matches.size() == 0);
1035 }
1036
1037 // Non-existent multi-word identifier.
1038 {
1039 command_id id = make_command_id("foo bar");
1040 set< command_id > matches = CMD_REF(top)->complete_command(id);
1041 UNIT_TEST_REQUIRE(matches.size() == 0);
1042 }
1043
1044 // Single-word identifier with one match. Exact matches are found
1045 // before any possible completions.
1046 {
1047 command_id id = make_command_id("test");
1048 set< command_id > matches = CMD_REF(top)->complete_command(id);
1049 UNIT_TEST_REQUIRE(matches.size() == 1);
1050 UNIT_TEST_CHECK(*matches.begin() == make_command_id("test"));
1051 }
1052
1053 // Single-word identifier with one match, non-primary name.
1054 {
1055 command_id id = make_command_id("alias1");
1056 set< command_id > matches = CMD_REF(top)->complete_command(id);
1057 UNIT_TEST_REQUIRE(matches.size() == 1);
1058 UNIT_TEST_CHECK(*matches.begin() == make_command_id("alias1"));
1059 }
1060
1061 // Single-word identifier with multiple matches.
1062 {
1063 command_id id = make_command_id("tes");
1064 set< command_id > matches = CMD_REF(top)->complete_command(id);
1065 UNIT_TEST_REQUIRE(matches.size() == 4);
1066
1067 set< command_id > expected;
1068 expected.insert(make_command_id("test"));
1069 expected.insert(make_command_id("test1"));
1070 expected.insert(make_command_id("test2"));
1071 expected.insert(make_command_id("testg"));
1072 UNIT_TEST_CHECK(matches == expected);
1073 }
1074
1075 // Single-word identifier with multiple matches, non-primary name.
1076 {
1077 command_id id = make_command_id("alias");
1078 set< command_id > matches = CMD_REF(top)->complete_command(id);
1079 UNIT_TEST_REQUIRE(matches.size() == 3);
1080
1081 set< command_id > expected;
1082 expected.insert(make_command_id("alias1"));
1083 expected.insert(make_command_id("alias2"));
1084 expected.insert(make_command_id("aliasg"));
1085 UNIT_TEST_CHECK(matches == expected);
1086 }
1087
1088 // Multi-word identifier with one match.
1089 {
1090 command_id id = make_command_id("testg testg1");
1091 set< command_id > matches = CMD_REF(top)->complete_command(id);
1092 UNIT_TEST_REQUIRE(matches.size() == 1);
1093
1094 set< command_id > expected;
1095 expected.insert(make_command_id("testg testg1"));
1096 UNIT_TEST_CHECK(matches == expected);
1097 }
1098
1099 // Multi-word identifier with multiple matches.
1100 {
1101 command_id id = make_command_id("testg testg");
1102 set< command_id > matches = CMD_REF(top)->complete_command(id);
1103 UNIT_TEST_REQUIRE(matches.size() == 2);
1104
1105 set< command_id > expected;
1106 expected.insert(make_command_id("testg testg1"));
1107 expected.insert(make_command_id("testg testg2"));
1108 UNIT_TEST_CHECK(matches == expected);
1109 }
1110
1111 // Multi-word identifier with multiple matches at different levels.
1112 {
1113 command_id id = make_command_id("tes testg1");
1114 set< command_id > matches = CMD_REF(top)->complete_command(id);
1115 UNIT_TEST_REQUIRE(matches.size() == 4);
1116
1117 set< command_id > expected;
1118 expected.insert(make_command_id("test"));
1119 expected.insert(make_command_id("test1"));
1120 expected.insert(make_command_id("test2"));
1121 expected.insert(make_command_id("testg testg1"));
1122 UNIT_TEST_CHECK(matches == expected);
1123 }
1124
1125 // Multi-word identifier with one match and extra words.
1126 {
1127 command_id id = make_command_id("testg testg1 foo");
1128 set< command_id > matches = CMD_REF(top)->complete_command(id);
1129 UNIT_TEST_REQUIRE(matches.size() == 1);
1130
1131 set< command_id > expected;
1132 expected.insert(make_command_id("testg testg1"));
1133 UNIT_TEST_CHECK(matches == expected);
1134 }
1135}
1136
1137UNIT_TEST(commands, command_find_command)
1138{
1139 using commands::command;
1140 using commands::command_id;
1141 using commands::make_command_id;
1142
1143 // Non-existent single-word identifier.
1144 {
1145 command_id id = make_command_id("foo");
1146 command const * cmd = CMD_REF(top)->find_command(id);
1147 UNIT_TEST_CHECK(cmd == NULL);
1148 }
1149
1150 // Non-existent multi-word identifier.
1151 {
1152 command_id id = make_command_id("foo bar");
1153 command const * cmd = CMD_REF(top)->find_command(id);
1154 UNIT_TEST_CHECK(cmd == NULL);
1155 }
1156
1157 // Single-word identifier that could be completed.
1158 {
1159 command_id id = make_command_id("tes");
1160 command const * cmd = CMD_REF(top)->find_command(id);
1161 UNIT_TEST_CHECK(cmd == NULL);
1162 }
1163
1164 // Single-word identifier.
1165 {
1166 command_id id = make_command_id("test1");
1167 command const * cmd = CMD_REF(top)->find_command(id);
1168 UNIT_TEST_CHECK(cmd == CMD_REF(test1));
1169 }
1170
1171 // Hidden single-word identifier.
1172 {
1173 command_id id = make_command_id("test3");
1174 command const * cmd = CMD_REF(top)->find_command(id);
1175 UNIT_TEST_CHECK(cmd == CMD_REF(test3));
1176 }
1177
1178 // Multi-word identifier that could be completed.
1179 {
1180 command_id id = make_command_id("testg testg");
1181 command const * cmd = CMD_REF(top)->find_command(id);
1182 UNIT_TEST_CHECK(cmd == NULL);
1183 }
1184
1185 // Multi-word identifier.
1186 {
1187 command_id id = make_command_id("testg testg1");
1188 command const * cmd = CMD_REF(top)->find_command(id);
1189 UNIT_TEST_CHECK(cmd == CMD_REF(testg1));
1190 }
1191
1192 // Hidden multi-word identifier.
1193 {
1194 command_id id = make_command_id("testg testg3");
1195 command const * cmd = CMD_REF(top)->find_command(id);
1196 UNIT_TEST_CHECK(cmd == CMD_REF(testg3));
1197 }
1198
1199 // Multi-word identifier with extra words.
1200 {
1201 command_id id = make_command_id("testg testg1 foo");
1202 command const * cmd = CMD_REF(top)->find_command(id);
1203 UNIT_TEST_CHECK(cmd == NULL);
1204 }
1205}
1206#endif // BUILD_UNIT_TESTS
1207
1208// Local Variables:
1209// mode: C++
1210// fill-column: 76
1211// c-file-style: "gnu"
1212// indent-tabs-mode: nil
1213// End:
1214// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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