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