monotone

monotone Mtn Source Tree

Root/commands.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <map>
11#include <algorithm>
12#include <iostream>
13
14#include "transforms.hh"
15#include "simplestring_xform.hh"
16#include "file_io.hh"
17#include "charset.hh"
18#include "diff_patch.hh"
19#include "inodeprint.hh"
20#include "cert.hh"
21#include "ui.hh"
22#include "cmd.hh"
23
24#ifndef _WIN32
25#include <boost/lexical_cast.hpp>
26#include <signal.h>
27#endif
28
29using std::cin;
30using std::make_pair;
31using std::map;
32using std::ostream;
33using std::pair;
34using std::set;
35using std::string;
36using std::strlen;
37using std::vector;
38
39// this file defines the task-oriented "top level" commands which can be
40// issued as part of a monotone command line. the command line can only
41// have one such command on it, followed by a vector of strings which are its
42// arguments. all --options will be processed by the main program *before*
43// calling a command
44//
45// we might expose this blunt command interface to scripting someday. but
46// not today.
47
48namespace commands
49{
50 const char * safe_gettext(const char * msgid)
51 {
52 if (strlen(msgid) == 0)
53 return msgid;
54
55 return _(msgid);
56 }
57
58 using std::map;
59 // This must be a pointer.
60 // It's used by the constructor of other static objects in different
61 // files (cmd_*.cc), and since they're in different files, there's no
62 // guarantee about what order they'll be initialized in. So have this
63 // be something that doesn't get automatic initialization, and initialize
64 // it ourselves the first time we use it.
65 static map<string, command *> * cmds;
66 command::command(string const & n,
67 string const & g,
68 string const & p,
69 string const & d,
70 bool u,
71 options::options_type const & o)
72 : name(n), cmdgroup(g), params_(p), desc_(d), use_workspace_options(u),
73 opts(o)
74 {
75 if (cmds == NULL)
76 cmds = new map<string, command *>;
77 (*cmds)[n] = this;
78 }
79 command::~command() {}
80 std::string command::params() {return safe_gettext(params_.c_str());}
81 std::string command::desc() {return safe_gettext(desc_.c_str());}
82 options::options_type command::get_options(vector<utf8> const & args)
83 {
84 return opts;
85 }
86 bool operator<(command const & self, command const & other);
87 std::string const & hidden_group()
88 {
89 static const std::string the_hidden_group("");
90 return the_hidden_group;
91 }
92};
93
94namespace std
95{
96 template <>
97 struct greater<commands::command *>
98 {
99 bool operator()(commands::command const * a, commands::command const * b)
100 {
101 return *a < *b;
102 }
103 };
104};
105
106namespace commands
107{
108 using std::greater;
109 using std::ostream;
110
111 bool operator<(command const & self, command const & other)
112 {
113 // *twitch*
114 return ((string(_(self.cmdgroup.c_str())) < string(_(other.cmdgroup.c_str())))
115 || ((self.cmdgroup == other.cmdgroup)
116 && (string(_(self.name.c_str())) < (string(_(other.name.c_str()))))));
117 }
118
119
120 string complete_command(string const & cmd)
121 {
122 if (cmd.length() == 0 || (*cmds).find(cmd) != (*cmds).end()) return cmd;
123
124 L(FL("expanding command '%s'") % cmd);
125
126 vector<string> matched;
127
128 for (map<string,command *>::const_iterator i = (*cmds).begin();
129 i != (*cmds).end(); ++i)
130 {
131 if (cmd.length() < i->first.length())
132 {
133 string prefix(i->first, 0, cmd.length());
134 if (cmd == prefix) matched.push_back(i->first);
135 }
136 }
137
138 // no matched commands
139 N(matched.size() != 0,
140 F("unknown command '%s'") % cmd);
141
142 // one matched command
143 if (matched.size() == 1)
144 {
145 string completed = *matched.begin();
146 L(FL("expanded command to '%s'") % completed);
147 return completed;
148 }
149
150 // more than one matched command
151 string err = (F("command '%s' has multiple ambiguous expansions:") % cmd).str();
152 for (vector<string>::iterator i = matched.begin();
153 i != matched.end(); ++i)
154 err += ('\n' + *i);
155 W(i18n_format(err));
156 return cmd;
157 }
158
159 void explain_usage(string const & cmd, ostream & out)
160 {
161 map<string,command *>::const_iterator i;
162
163 // try to get help on a specific command
164
165 i = (*cmds).find(cmd);
166
167 if (i != (*cmds).end())
168 {
169 string params = i->second->params();
170 vector<string> lines;
171 split_into_lines(params, lines);
172 for (vector<string>::const_iterator j = lines.begin();
173 j != lines.end(); ++j)
174 out << " " << i->second->name << ' ' << *j << '\n';
175 split_into_lines(i->second->desc(), lines);
176 for (vector<string>::const_iterator j = lines.begin();
177 j != lines.end(); ++j)
178 out << " " << *j << '\n';
179 out << '\n';
180 return;
181 }
182
183 vector<command *> sorted;
184 out << _("commands:") << '\n';
185 for (i = (*cmds).begin(); i != (*cmds).end(); ++i)
186 {
187 if (i->second->cmdgroup != hidden_group())
188 sorted.push_back(i->second);
189 }
190
191 sort(sorted.begin(), sorted.end(), greater<command *>());
192
193 string curr_group;
194 size_t col = 0;
195 size_t col2 = 0;
196 for (size_t i = 0; i < sorted.size(); ++i)
197 {
198 size_t cmp = display_width(utf8(safe_gettext(idx(sorted, i)->cmdgroup.c_str())));
199 col2 = col2 > cmp ? col2 : cmp;
200 }
201
202 for (size_t i = 0; i < sorted.size(); ++i)
203 {
204 if (idx(sorted, i)->cmdgroup != curr_group)
205 {
206 curr_group = idx(sorted, i)->cmdgroup;
207 out << '\n';
208 out << " " << safe_gettext(idx(sorted, i)->cmdgroup.c_str());
209 col = display_width(utf8(safe_gettext(idx(sorted, i)->cmdgroup.c_str()))) + 2;
210 while (col++ < (col2 + 3))
211 out << ' ';
212 }
213 out << ' ' << idx(sorted, i)->name;
214 col += idx(sorted, i)->name.size() + 1;
215 if (col >= 70)
216 {
217 out << '\n';
218 col = 0;
219 while (col++ < (col2 + 3))
220 out << ' ';
221 }
222 }
223 out << "\n\n";
224 }
225
226 int process(app_state & app, string const & cmd, vector<utf8> const & args)
227 {
228 if ((*cmds).find(cmd) != (*cmds).end())
229 {
230 L(FL("executing command '%s'") % cmd);
231
232 // at this point we process the data from _MTN/options if
233 // the command needs it.
234 if ((*cmds)[cmd]->use_workspace_options)
235 app.process_options();
236
237 (*cmds)[cmd]->exec(app, args);
238 return 0;
239 }
240 else
241 {
242 P(F("unknown command '%s'") % cmd);
243 return 1;
244 }
245 }
246
247 options::options_type command_options(vector<utf8> const & cmdline)
248 {
249 if (cmdline.empty())
250 return options::options_type();
251 string cmd = complete_command(idx(cmdline,0)());
252 if ((*cmds).find(cmd) != (*cmds).end())
253 {
254 return (*cmds)[cmd]->get_options(cmdline);
255 }
256 else
257 {
258 return options::options_type();
259 }
260 }
261
262 options::options_type toplevel_command_options(string const & cmd)
263 {
264 if ((*cmds).find(cmd) != (*cmds).end())
265 {
266 return (*cmds)[cmd]->opts;
267 }
268 else
269 {
270 return options::options_type();
271 }
272 }
273}
274////////////////////////////////////////////////////////////////////////
275
276CMD(help, N_("informative"), N_("command [ARGS...]"),
277 N_("display command help"), options::opts::none)
278{
279 if (args.size() < 1)
280 {
281 app.opts.help = true;
282 throw usage("");
283 }
284
285 string full_cmd = complete_command(idx(args, 0)());
286 if ((*cmds).find(full_cmd) == (*cmds).end())
287 throw usage("");
288
289 app.opts.help = true;
290 throw usage(full_cmd);
291}
292
293CMD(crash, hidden_group(), "{ N | E | I | exception | signal }",
294 "trigger the specified kind of crash", options::opts::none)
295{
296 if (args.size() != 1)
297 throw usage(name);
298 bool spoon_exists(false);
299 if (idx(args,0)() == "N")
300 N(spoon_exists, i18n_format("There is no spoon."));
301 else if (idx(args,0)() == "E")
302 E(spoon_exists, i18n_format("There is no spoon."));
303 else if (idx(args,0)() == "I")
304 {
305 I(spoon_exists);
306 }
307#define maybe_throw(ex) if(idx(args,0)()==#ex) throw ex("There is no spoon.")
308#define maybe_throw_bare(ex) if(idx(args,0)()==#ex) throw ex()
309 else maybe_throw_bare(std::bad_alloc);
310 else maybe_throw_bare(std::bad_cast);
311 else maybe_throw_bare(std::bad_typeid);
312 else maybe_throw_bare(std::bad_exception);
313 else maybe_throw_bare(std::exception);
314 else maybe_throw(std::domain_error);
315 else maybe_throw(std::invalid_argument);
316 else maybe_throw(std::length_error);
317 else maybe_throw(std::out_of_range);
318 else maybe_throw(std::range_error);
319 else maybe_throw(std::overflow_error);
320 else maybe_throw(std::underflow_error);
321 else maybe_throw(std::logic_error);
322 else maybe_throw(std::runtime_error);
323 else
324 {
325#ifndef _WIN32
326 try
327 {
328 int signo = boost::lexical_cast<int>(idx(args,0)());
329 if (0 < signo && signo <= 15)
330 {
331 raise(signo);
332 // control should not get here...
333 I(!"crash: raise returned");
334 }
335 }
336 catch (boost::bad_lexical_cast&)
337 { // fall through and throw usage
338 }
339#endif
340 throw usage(name);
341 }
342#undef maybe_throw
343#undef maybe_throw_bare
344}
345
346string
347describe_revision(app_state & app,
348 revision_id const & id)
349{
350 cert_name author_name(author_cert_name);
351 cert_name date_name(date_cert_name);
352
353 string description;
354
355 description += id.inner()();
356
357 // append authors and date of this revision
358 vector< revision<cert> > tmp;
359 app.get_project().get_revision_certs_by_name(id, author_name, tmp);
360 for (vector< revision<cert> >::const_iterator i = tmp.begin();
361 i != tmp.end(); ++i)
362 {
363 cert_value tv;
364 decode_base64(i->inner().value, tv);
365 description += " ";
366 description += tv();
367 }
368 app.get_project().get_revision_certs_by_name(id, date_name, tmp);
369 for (vector< revision<cert> >::const_iterator i = tmp.begin();
370 i != tmp.end(); ++i)
371 {
372 cert_value tv;
373 decode_base64(i->inner().value, tv);
374 description += " ";
375 description += tv();
376 }
377
378 return description;
379}
380
381
382void
383complete(app_state & app,
384 string const & str,
385 set<revision_id> & completion,
386 bool must_exist)
387{
388 // This copies the start of selectors::parse_selector().to avoid
389 // getting a log when there's no expansion happening...:
390 //
391 // this rule should always be enabled, even if the user specifies
392 // --norc: if you provide a revision id, you get a revision id.
393 if (str.find_first_not_of(constants::legal_id_bytes) == string::npos
394 && str.size() == constants::idlen)
395 {
396 completion.insert(revision_id(hexenc<id>(id(str))));
397 if (must_exist)
398 N(app.db.revision_exists(*completion.begin()),
399 F("no such revision '%s'") % *completion.begin());
400 return;
401 }
402
403 vector<pair<selectors::selector_type, string> >
404 sels(selectors::parse_selector(str, app));
405
406 P(F("expanding selection '%s'") % str);
407
408 // we jam through an "empty" selection on sel_ident type
409 set<string> completions;
410 selectors::selector_type ty = selectors::sel_ident;
411 selectors::complete_selector("", sels, ty, completions, app);
412
413 N(completions.size() != 0,
414 F("no match for selection '%s'") % str);
415
416 for (set<string>::const_iterator i = completions.begin();
417 i != completions.end(); ++i)
418 {
419 pair<set<revision_id>::const_iterator, bool> p =
420 completion.insert(revision_id(hexenc<id>(id(*i))));
421 P(F("expanded to '%s'") % *(p.first));
422 }
423}
424
425
426void
427complete(app_state & app,
428 string const & str,
429 revision_id & completion,
430 bool must_exist)
431{
432 set<revision_id> completions;
433
434 complete(app, str, completions, must_exist);
435
436 if (completions.size() > 1)
437 {
438 string err = (F("selection '%s' has multiple ambiguous expansions:") % str).str();
439 for (set<revision_id>::const_iterator i = completions.begin();
440 i != completions.end(); ++i)
441 err += ("\n" + describe_revision(app, *i));
442 N(completions.size() == 1, i18n_format(err));
443 }
444
445 completion = *completions.begin();
446}
447
448void
449notify_if_multiple_heads(app_state & app)
450{
451 set<revision_id> heads;
452 app.get_project().get_branch_heads(app.opts.branchname, heads);
453 if (heads.size() > 1) {
454 string prefixedline;
455 prefix_lines_with(_("note: "),
456 _("branch '%s' has multiple heads\n"
457 "perhaps consider '%s merge'"),
458 prefixedline);
459 P(i18n_format(prefixedline) % app.opts.branchname % ui.prog_name);
460 }
461}
462
463void
464process_commit_message_args(bool & given,
465 utf8 & log_message,
466 app_state & app,
467 utf8 message_prefix)
468{
469 // can't have both a --message and a --message-file ...
470 N(!app.opts.message_given || !app.opts.msgfile_given,
471 F("--message and --message-file are mutually exclusive"));
472
473 if (app.opts.message_given)
474 {
475 std::string msg;
476 join_lines(app.opts.message, msg);
477 log_message = utf8(msg);
478 if (message_prefix().length() != 0)
479 log_message = utf8(message_prefix() + "\n\n" + log_message());
480 given = true;
481 }
482 else if (app.opts.msgfile_given)
483 {
484 data dat;
485 read_data_for_command_line(app.opts.msgfile, dat);
486 external dat2 = external(dat());
487 system_to_utf8(dat2, log_message);
488 if (message_prefix().length() != 0)
489 log_message = utf8(message_prefix() + "\n\n" + log_message());
490 given = true;
491 }
492 else if (message_prefix().length() != 0)
493 {
494 log_message = message_prefix;
495 given = true;
496 }
497 else
498 given = false;
499}
500
501void
502get_content_paths(roster_t const & roster, map<file_id, file_path> & paths)
503{
504 node_map const & nodes = roster.all_nodes();
505 for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
506 {
507 node_t node = roster.get_node(i->first);
508 if (is_file_t(node))
509 {
510 split_path sp;
511 roster.get_name(i->first, sp);
512 file_t file = downcast_to_file_t(node);
513 paths.insert(make_pair(file->content, file_path(sp)));
514 }
515 }
516}
517
518
519// Local Variables:
520// mode: C++
521// fill-column: 76
522// c-file-style: "gnu"
523// indent-tabs-mode: nil
524// End:
525// 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