monotone

monotone Mtn Source Tree

Root/src/cmd_list.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2// Copyright (C) 2012 Stephen Leake <stephen_leake@stephe-leake.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 <algorithm>
13#include "safe_map.hh"
14#include <utility>
15#include <iostream>
16#include <iterator>
17
18#include <boost/tuple/tuple.hpp>
19
20#include "basic_io.hh"
21#include "cert.hh"
22#include "charset.hh"
23#include "cmd.hh"
24#include "roster.hh"
25#include "database.hh"
26#include "date_format.hh"
27#include "globish.hh"
28#include "keys.hh"
29#include "key_store.hh"
30#include "restrictions.hh"
31#include "revision.hh"
32#include "simplestring_xform.hh"
33#include "transforms.hh"
34#include "ui.hh"
35#include "vocab_cast.hh"
36#include "app_state.hh"
37#include "project.hh"
38#include "vocab_cast.hh"
39#include "work.hh"
40
41using std::cout;
42using std::make_pair;
43using std::map;
44using std::ostream_iterator;
45using std::pair;
46using std::set;
47using std::sort;
48using std::copy;
49using std::ostream;
50using std::string;
51using std::vector;
52
53CMD_GROUP(list, "list", "ls", CMD_REF(informative),
54 N_("Shows database objects"),
55 N_("This command is used to query information from the database. "
56 "It shows database objects, or the current workspace manifest, "
57 "or known, unknown, intentionally ignored, missing, or "
58 "changed-state files."));
59
60namespace {
61 // for 'ls certs' and 'ls tags'
62 string format_key(key_identity_info const & info)
63 {
64 string out;
65
66 hexenc<id> hexid;
67 encode_hexenc(info.id.inner(), hexid);
68
69 out += info.official_name();
70 out += " (";
71 out += hexid().substr(0, 10) + "...";
72 if (info.given_name != info.official_name)
73 {
74 out += "; ";
75 out += info.given_name();
76 }
77 out += ")";
78
79 return out;
80 }
81 string format_key_for_ls_keys(key_identity_info const & info)
82 {
83 string out;
84
85 hexenc<id> hexid;
86 encode_hexenc(info.id.inner(), hexid);
87
88 out += hexid();
89 out += " ";
90 out += info.official_name();
91 if (info.given_name != info.official_name)
92 {
93 out += " (";
94 out += info.given_name();
95 out += ")";
96 }
97
98 return out;
99 }
100}
101
102CMD(certs, "certs", "", CMD_REF(list), "REVID",
103 N_("Lists certificates attached to a revision"),
104 "",
105 options::opts::none)
106{
107 if (args.size() != 1)
108 throw usage(execid);
109
110 database db(app);
111 project_t project(db);
112 key_store keys(app);
113 vector<cert> certs;
114
115 transaction_guard guard(db, false);
116
117 string date_fmt = get_date_format(app.opts, app.lua, date_time_long);
118
119 revision_id ident;
120 complete(app.opts, app.lua, project, idx(args, 0)(), ident);
121 vector<cert> ts;
122 project.get_revision_certs(ident, ts);
123
124 for (size_t i = 0; i < ts.size(); ++i)
125 certs.push_back(idx(ts, i));
126
127 {
128 set<key_id> checked;
129 for (size_t i = 0; i < certs.size(); ++i)
130 {
131 if (checked.find(idx(certs, i).key) == checked.end() &&
132 !db.public_key_exists(idx(certs, i).key))
133 P(F("no public key %s found in database")
134 % idx(certs, i).key);
135 checked.insert(idx(certs, i).key);
136 }
137 }
138
139 // Make the output deterministic; this is useful for the test suite, in
140 // particular.
141 sort(certs.begin(), certs.end());
142
143 string str = _("Key : %s\n"
144 "Sig : %s\n"
145 "Name : %s\n"
146 "Value : %s\n");
147 string extra_str = " : %s\n";
148
149 string::size_type colon_pos = str.find(':');
150
151 if (colon_pos != string::npos)
152 {
153 string substr(str, 0, colon_pos);
154 colon_pos = display_width(utf8(substr, origin::internal));
155 extra_str = string(colon_pos, ' ') + ": %s\n";
156 }
157
158 for (size_t i = 0; i < certs.size(); ++i)
159 {
160 cert_status status = db.check_cert(idx(certs, i));
161 cert_value tv = idx(certs, i).value;
162 string washed;
163 if (guess_binary(tv()))
164 {
165 washed = "<binary data>";
166 }
167 else
168 {
169 washed = tv();
170 }
171
172 string stat;
173 switch (status)
174 {
175 case cert_ok:
176 stat = _("ok");
177 break;
178 case cert_bad:
179 stat = _("bad");
180 break;
181 case cert_unknown:
182 stat = _("unknown");
183 break;
184 }
185
186 vector<string> lines;
187 split_into_lines(washed, lines);
188 std::string value_first_line = lines.empty() ? "" : idx(lines, 0);
189
190 if (idx(certs, i).name == date_cert_name)
191 {
192 if (!date_fmt.empty())
193 {
194 value_first_line = date_t(value_first_line).as_formatted_localtime(date_fmt);
195 }
196 else
197 {
198 value_first_line = date_t(value_first_line).as_iso_8601_extended();
199 }
200 }
201
202 key_identity_info identity;
203 identity.id = idx(certs, i).key;
204 project.complete_key_identity_from_id(keys, app.lua, identity);
205
206 cout << string(guess_terminal_width(), '-') << '\n'
207 << (i18n_format(str)
208 % format_key(identity)
209 % stat
210 % idx(certs, i).name
211 % value_first_line);
212
213 for (size_t i = 1; i < lines.size(); ++i)
214 cout << (i18n_format(extra_str) % idx(lines, i));
215 }
216
217 if (!certs.empty())
218 cout << '\n';
219
220 guard.commit();
221}
222
223CMD(duplicates, "duplicates", "", CMD_REF(list), "",
224 N_("Lists duplicate files in the specified revision."
225 " If no revision is specified, use the workspace"),
226 "",
227 options::opts::revision)
228{
229 if (!args.empty())
230 throw usage(execid);
231
232 revision_id rev_id;
233 roster_t roster;
234 database db(app);
235 project_t project(db);
236
237 E(app.opts.revision.size() <= 1, origin::user,
238 F("more than one revision given"));
239
240 if (app.opts.revision.empty())
241 {
242 workspace work(app);
243 temp_node_id_source nis;
244
245 work.get_current_roster_shape(db, nis, roster);
246 }
247 else
248 {
249 complete(app.opts, app.lua, project,
250 idx(app.opts.revision, 0)(), rev_id);
251 E(db.revision_exists(rev_id), origin::user,
252 F("no revision %s found in database") % rev_id);
253 db.get_roster(rev_id, roster);
254 }
255
256 // To find the duplicate files, we put all file_ids in a map
257 // and count how many times they occur in the roster.
258 //
259 // Structure of file_id_map is following:
260 // first : file_id
261 // second :
262 // first : unsigned int
263 // second : file_paths (=vector<file_path>)
264 typedef std::vector<file_path> file_paths;
265 typedef std::pair<unsigned int, file_paths> file_count;
266 typedef std::map<file_id, file_count> file_id_map;
267 file_id_map file_map;
268
269 node_map const & nodes = roster.all_nodes();
270 for (node_map::const_iterator i = nodes.begin();
271 i != nodes.end(); ++i)
272 {
273 node_t node = i->second;
274 if (is_file_t(node))
275 {
276 file_t f = downcast_to_file_t(node);
277 file_path p;
278 roster.get_name(i->first, p);
279
280 file_id_map::iterator iter = file_map.find(f->content);
281 if (iter == file_map.end())
282 {
283 file_paths paths;
284 paths.push_back(p);
285 file_count fc(1, paths);
286 file_map.insert(make_pair(f->content, fc));
287 }
288 else
289 {
290 iter->second.first++;
291 iter->second.second.push_back(p);
292 }
293 }
294 }
295
296 string empty_checksum(40, ' ');
297 for (file_id_map::const_iterator i = file_map.begin();
298 i != file_map.end(); ++i)
299 {
300 if (i->second.first > 1)
301 {
302 bool first_print = true;
303 for (file_paths::const_iterator j = i->second.second.begin();
304 j != i->second.second.end(); ++j)
305 {
306 if (first_print)
307 {
308 cout << i->first;
309 first_print = false;
310 }
311 else
312 cout << empty_checksum;
313
314 cout << " " << *j << '\n';
315 }
316 }
317 }
318}
319
320struct key_location_info
321{
322 key_identity_info identity;
323 vector<string> public_locations;
324 vector<string> private_locations;
325};
326typedef map<key_id, key_location_info> key_map;
327
328namespace {
329 void get_key_list(database & db,
330 key_store & keys,
331 lua_hooks & lua,
332 project_t & project,
333 key_map & items)
334 {
335 items.clear();
336
337 {
338 vector<key_id> dbkeys;
339 if (db.database_specified())
340 {
341 db.get_key_ids(dbkeys);
342 for (vector<key_id>::iterator i = dbkeys.begin();
343 i != dbkeys.end(); i++)
344 {
345 key_identity_info identity;
346 identity.id = *i;
347 project.complete_key_identity_from_id(lua, identity);
348 items[*i].identity = identity;
349 items[*i].public_locations.push_back("database");
350 }
351 }
352 }
353 {
354 vector<key_id> kskeys;
355 keys.get_key_ids(kskeys);
356 for (vector<key_id>::iterator i = kskeys.begin();
357 i != kskeys.end(); i++)
358 {
359 key_identity_info identity;
360 identity.id = *i;
361 project.complete_key_identity_from_id(keys, lua, identity);
362 items[*i].identity = identity;
363 items[*i].public_locations.push_back("keystore");
364 items[*i].private_locations.push_back("keystore");
365 }
366 }
367 }
368}
369
370CMD(keys, "keys", "", CMD_REF(list), "[PATTERN]",
371 N_("Lists keys that match a pattern"),
372 "",
373 options::opts::none)
374{
375 if (args.size() > 1)
376 throw usage(execid);
377
378 database db(app, database::maybe_unspecified);
379 key_store keys(app);
380 project_t project(db);
381
382 key_map items;
383 get_key_list(db, keys, app.lua, project, items);
384
385 if (items.empty())
386 {
387 P(F("no keys found"));
388 }
389
390 key_map matched_items;
391 if (args.size() == 1)
392 {
393 globish pattern(idx(args, 0)(), origin::user);
394 for (key_map::iterator i = items.begin(); i != items.end(); ++i)
395 {
396 string const & alias = i->second.identity.official_name();
397 if (pattern.matches(alias))
398 {
399 matched_items.insert(*i);
400 }
401 }
402 if (matched_items.empty())
403 {
404 W(F("no keys found matching '%s'") % idx(args, 0)());
405 }
406 }
407 else
408 {
409 matched_items = items;
410 }
411
412 bool have_keystore_only_key = false;
413 // sort key => rendered line
414 map<string, string> public_rendered;
415 map<string, string> private_rendered;
416
417 set<string> seen_aliases;
418 set<string> duplicate_aliases;
419
420 for (key_map::iterator i = matched_items.begin();
421 i != matched_items.end(); ++i)
422 {
423 key_identity_info const & identity = i->second.identity;
424 string const & alias = i->second.identity.official_name();
425 vector<string> const & public_locations = i->second.public_locations;
426 vector<string> const & private_locations = i->second.private_locations;
427
428 if (seen_aliases.find(alias) != seen_aliases.end())
429 {
430 duplicate_aliases.insert(alias);
431 }
432 seen_aliases.insert(alias);
433
434 string rendered_basic = format_key_for_ls_keys(identity);
435 string sort_key = alias + identity.id.inner()();
436
437 if (!public_locations.empty())
438 {
439 string rendered = rendered_basic;
440 if (public_locations.size() == 1 &&
441 idx(public_locations, 0) == "keystore")
442 {
443 have_keystore_only_key = true;
444 rendered += " (*)";
445 }
446 public_rendered.insert(make_pair(sort_key, rendered));
447 }
448 if (!private_locations.empty())
449 {
450 private_rendered.insert(make_pair(sort_key, rendered_basic));
451 }
452 }
453
454 if (!public_rendered.empty())
455 {
456 cout << "\n[public keys]\n";
457 for (map<string, string>::iterator i = public_rendered.begin();
458 i != public_rendered.end(); ++i)
459 {
460 cout << i->second << "\n";
461 }
462 if (have_keystore_only_key)
463 {
464 cout << (F("(*) - only in '%s/'")
465 % keys.get_key_dir()) << '\n';
466 }
467 cout << "\n";
468 }
469 if (!private_rendered.empty())
470 {
471 cout << "\n[private keys]\n";
472 for (map<string, string>::iterator i = private_rendered.begin();
473 i != private_rendered.end(); ++i)
474 {
475 cout << i->second << "\n";
476 }
477 cout << "\n";
478 }
479
480 if (!duplicate_aliases.empty())
481 {
482 W(F("Some key names refer to multiple keys"));
483 for (set<string>::iterator i = duplicate_aliases.begin();
484 i != duplicate_aliases.end(); i++)
485 {
486 P(F("Duplicate Key: %s") % *i);
487 }
488 }
489
490}
491
492CMD(branches, "branches", "", CMD_REF(list), "[PATTERN]",
493 N_("Lists branches in the database that match a pattern"),
494 "",
495 options::opts::exclude)
496{
497 globish inc("*", origin::internal);
498 if (args.size() == 1)
499 inc = globish(idx(args,0)(), origin::user);
500 else if (args.size() > 1)
501 throw usage(execid);
502
503 database db(app);
504 project_t project(db);
505 globish exc(app.opts.exclude);
506 set<branch_name> names;
507 project.get_branch_list(inc, names, !app.opts.ignore_suspend_certs);
508
509 for (set<branch_name>::const_iterator i = names.begin();
510 i != names.end(); ++i)
511 if (!exc.matches((*i)()) && !app.lua.hook_ignore_branch(*i))
512 cout << *i << '\n';
513}
514
515CMD(epochs, "epochs", "", CMD_REF(list), "[BRANCH [...]]",
516 N_("Lists the current epoch of branches that match a pattern"),
517 "",
518 options::opts::none)
519{
520 database db(app);
521 map<branch_name, epoch_data> epochs;
522 db.get_epochs(epochs);
523
524 if (args.empty())
525 {
526 for (map<branch_name, epoch_data>::const_iterator
527 i = epochs.begin();
528 i != epochs.end(); ++i)
529 {
530 cout << encode_hexenc(i->second.inner()(),
531 i->second.inner().made_from)
532 << ' ' << i->first << '\n';
533 }
534 }
535 else
536 {
537 for (args_vector::const_iterator i = args.begin();
538 i != args.end();
539 ++i)
540 {
541 map<branch_name, epoch_data>::const_iterator j =
542 epochs.find(typecast_vocab<branch_name>((*i)));
543 E(j != epochs.end(), origin::user, F("no epoch for branch '%s'") % *i);
544 cout << encode_hexenc(j->second.inner()(),
545 j->second.inner().made_from)
546 << ' ' << j->first << '\n';
547 }
548 }
549}
550
551CMD(tags, "tags", "", CMD_REF(list), "[PATTERN]",
552 N_("Lists all tags in the database"),
553 "",
554 options::opts::exclude)
555{
556 globish inc("*", origin::internal);
557 if (args.size() == 1)
558 inc = globish(idx(args,0)(), origin::user);
559 else if (args.size() > 1)
560 throw usage(execid);
561
562 database db(app);
563 set<tag_t> tags;
564 project_t project(db);
565 cert_name branch = branch_cert_name;
566
567 project.get_tags(tags);
568
569 for (set<tag_t>::const_iterator i = tags.begin(); i != tags.end(); ++i)
570 {
571 key_identity_info identity;
572 identity.id = i->key;
573 project.complete_key_identity_from_id(app.lua, identity);
574
575 vector<cert> certs;
576 project.get_revision_certs(i->ident, certs);
577
578 globish exc(app.opts.exclude);
579
580 if (inc.matches(i->name()) && !exc.matches(i->name()))
581 {
582 hexenc<id> hexid;
583 encode_hexenc(i->ident.inner(), hexid);
584
585 cout << i->name << ' ' << hexid().substr(0,10) << "... ";
586
587 for (vector<cert>::const_iterator c = certs.begin();
588 c != certs.end(); ++c)
589 {
590 if (c->name == branch)
591 {
592 cout << c->value << ' ';
593 }
594 }
595
596 cout << format_key(identity) << '\n';
597 }
598 }
599}
600
601CMD(vars, "vars", "", CMD_REF(list), "[DOMAIN]",
602 N_("Lists variables in the whole database or a domain"),
603 "",
604 options::opts::none)
605{
606 bool filterp;
607 var_domain filter;
608 if (args.empty())
609 {
610 filterp = false;
611 }
612 else if (args.size() == 1)
613 {
614 filterp = true;
615 filter = typecast_vocab<var_domain>(idx(args, 0));
616 }
617 else
618 throw usage(execid);
619
620 database db(app);
621 map<var_key, var_value> vars;
622 db.get_vars(vars);
623 for (map<var_key, var_value>::const_iterator i = vars.begin();
624 i != vars.end(); ++i)
625 {
626 if (filterp && !(i->first.first == filter))
627 continue;
628 cout << i->first.first << ": "
629 << i->first.second << ' '
630 << i->second << '\n';
631 }
632}
633
634static void
635print_workspace_info(database & db, lua_hooks & lua,
636 ostream & out, string const & indent = string())
637{
638 bool has_valid_workspaces = false;
639
640 vector<system_path> workspaces;
641 db.get_registered_workspaces(workspaces);
642 system_path db_path = db.get_filename();
643
644 database_path_helper helper(lua);
645 for (vector<system_path>::const_iterator k = workspaces.begin();
646 k != workspaces.end(); ++k)
647 {
648 system_path workspace_path(*k);
649 if (!directory_exists(workspace_path / bookkeeping_root_component))
650 {
651 L(FL("ignoring missing workspace '%s'") % workspace_path);
652 continue;
653 }
654
655 options workspace_opts;
656 workspace::get_options(workspace_path, workspace_opts);
657
658 system_path workspace_db_path;
659 helper.get_database_path(workspace_opts, workspace_db_path);
660
661 if (workspace_db_path != db_path)
662 {
663 L(FL("ignoring workspace '%s', expected database %s, "
664 "but has %s configured in _MTN/options")
665 % workspace_path % db_path % workspace_db_path);
666 continue;
667 }
668
669 has_valid_workspaces = true;
670
671 string workspace_branch = workspace_opts.branch();
672 if (!workspace_opts.branch_given)
673 workspace_branch = _("<no branch set>");
674
675 out << indent << F("%s (in '%s')") % workspace_branch % workspace_path << '\n';
676 }
677
678 if (!has_valid_workspaces)
679 out << indent << F("no known valid workspaces") << '\n';
680}
681
682CMD(workspaces, "workspaces", "", CMD_REF(list), "",
683 N_("Lists known workspaces of a specified database"),
684 "",
685 options::opts::none)
686{
687 database db(app.opts, app.lua);
688 db.ensure_open();
689 print_workspace_info(db, app.lua, cout);
690}
691
692CMD(databases, "databases", "dbs", CMD_REF(list), "",
693 N_("Lists managed databases and their known workspaces"),
694 "",
695 options::opts::none)
696{
697 vector<system_path> search_paths, files, dirs;
698
699 E(app.lua.hook_get_default_database_locations(search_paths), origin::user,
700 F("no default database location configured"));
701
702 globish file_matcher;
703 E(app.lua.hook_get_default_database_glob(file_matcher), origin::user,
704 F("could not query default database glob"));
705
706 for (vector<system_path>::const_iterator i = search_paths.begin();
707 i != search_paths.end(); ++i)
708 {
709 system_path search_path(*i);
710
711 fill_path_vec<system_path> fill_files(search_path, files, false);
712 fill_path_vec<system_path> fill_dirs(search_path, dirs, true);
713 read_directory(search_path, fill_files, fill_dirs);
714
715 for (vector<system_path>::const_iterator j = files.begin();
716 j != files.end(); ++j)
717 {
718 system_path db_path(*j);
719
720 // a little optimization, so we don't scan and open every file
721 string p = db_path.as_internal();
722 if (!file_matcher.matches(p))
723 {
724 L(FL("ignoring file '%s'") % db_path);
725 continue;
726 }
727
728 string db_alias = ":" + db_path.as_internal().substr(
729 search_path.as_internal().size() + 1
730 );
731
732 options db_opts;
733 db_opts.dbname_type = managed_db;
734 db_opts.dbname_alias = db_alias;
735 db_opts.dbname_given = true;
736
737 database db(db_opts, app.lua);
738 try
739 {
740 db.ensure_open();
741 }
742 catch (recoverable_failure & f)
743 {
744 string prefix = _("misuse: ");
745 string failure = f.what();
746 for (size_t pos = failure.find(prefix);
747 pos != string::npos; pos = failure.find(prefix))
748 failure.replace(pos, prefix.size(), "");
749
750 W(F("%s") % failure);
751 W(F("ignoring database '%s'") % db_path);
752 continue;
753 }
754
755 cout << F("%s (in %s):") % db_alias % search_path << "\n";
756 print_workspace_info(db, app.lua, cout, "\t");
757 }
758 }
759}
760
761CMD(known, "known", "", CMD_REF(list), "",
762 N_("Lists workspace files that belong to the current branch"),
763 "",
764 options::opts::depth | options::opts::exclude)
765{
766 database db(app);
767 workspace work(app);
768
769 roster_t new_roster;
770 temp_node_id_source nis;
771 work.get_current_roster_shape(db, nis, new_roster);
772
773 node_restriction mask(args_to_paths(args),
774 args_to_paths(app.opts.exclude),
775 app.opts.depth,
776 new_roster, ignored_file(work));
777
778 // to be printed sorted
779 vector<file_path> print_paths;
780
781 node_map const & nodes = new_roster.all_nodes();
782 for (node_map::const_iterator i = nodes.begin();
783 i != nodes.end(); ++i)
784 {
785 node_id nid = i->first;
786
787 if (!new_roster.is_root(nid)
788 && mask.includes(new_roster, nid))
789 {
790 file_path p;
791 new_roster.get_name(nid, p);
792 print_paths.push_back(p);
793 }
794 }
795
796 sort(print_paths.begin(), print_paths.end());
797 copy(print_paths.begin(), print_paths.end(),
798 ostream_iterator<file_path>(cout, "\n"));
799}
800
801static void get_unknown_ignored(app_state & app,
802 args_vector const & args,
803 bool recurse,
804 set<file_path> & unknown,
805 set<file_path> & ignored)
806{
807 database db(app);
808 workspace work(app);
809
810 vector<file_path> roots = args_to_paths(args);
811 path_restriction mask(roots, args_to_paths(app.opts.exclude),
812 app.opts.depth, ignored_file(work));
813
814 // if no starting paths have been specified use the workspace root
815 if (roots.empty())
816 roots.push_back(file_path());
817
818 work.find_unknown_and_ignored(db, mask, recurse, roots, unknown, ignored);
819}
820
821CMD_PRESET_OPTIONS(unknown)
822{
823 opts.recursive=true;
824}
825CMD(unknown, "unknown", "", CMD_REF(list), "[PATH]",
826 N_("Lists workspace files that are unknown in the current branch"),
827 "",
828 options::opts::depth | options::opts::exclude | options::opts::recursive)
829{
830 set<file_path> unknown, _;
831 get_unknown_ignored(app, args, app.opts.recursive, unknown, _);
832
833 copy(unknown.begin(), unknown.end(),
834 ostream_iterator<file_path>(cout, "\n"));
835}
836
837CMD_PRESET_OPTIONS(ignored)
838{
839 opts.recursive=true;
840}
841CMD(ignored, "ignored", "", CMD_REF(list), "[PATH]",
842 N_("Lists workspace files that are ignored in the current branch"),
843 "",
844 options::opts::depth | options::opts::exclude | options::opts::recursive)
845{
846 set<file_path> _, ignored;
847 get_unknown_ignored(app, args, app.opts.recursive, _, ignored);
848
849 copy(ignored.begin(), ignored.end(),
850 ostream_iterator<file_path>(cout, "\n"));
851}
852
853CMD(missing, "missing", "", CMD_REF(list), "",
854 N_("Lists files that belong to the branch but are not in the workspace"),
855 "",
856 options::opts::depth | options::opts::exclude)
857{
858 database db(app);
859 workspace work(app);
860 temp_node_id_source nis;
861 roster_t current_roster_shape;
862 work.get_current_roster_shape(db, nis, current_roster_shape);
863 node_restriction mask(args_to_paths(args),
864 args_to_paths(app.opts.exclude),
865 app.opts.depth,
866 current_roster_shape, ignored_file(work));
867
868 set<file_path> missing;
869 work.find_missing(current_roster_shape, mask, missing);
870
871 copy(missing.begin(), missing.end(),
872 ostream_iterator<file_path>(cout, "\n"));
873}
874
875
876CMD(changed, "changed", "", CMD_REF(list), "[PATH...]",
877 N_("Lists files that have changed with respect to the current revision"),
878 "",
879 options::opts::depth | options::opts::exclude)
880{
881 database db(app);
882 workspace work(app);
883
884 parent_map parents;
885 roster_t new_roster;
886 temp_node_id_source nis;
887 work.get_current_roster_shape(db, nis, new_roster);
888
889 work.get_parent_rosters(db, parents);
890
891 node_restriction mask(args_to_paths(args),
892 args_to_paths(app.opts.exclude),
893 app.opts.depth,
894 parents, new_roster, ignored_file(work));
895
896 work.update_current_roster_from_filesystem(new_roster, mask);
897 revision_t rrev;
898 make_restricted_revision(parents, new_roster, mask, rrev);
899
900 // to be printed sorted, with duplicates removed
901 set<file_path> print_paths;
902
903 for (edge_map::const_iterator i = rrev.edges.begin();
904 i != rrev.edges.end(); i++)
905 {
906 set<node_id> nodes;
907 roster_t const & old_roster
908 = *safe_get(parents, edge_old_revision(i)).first;
909 select_nodes_modified_by_cset(edge_changes(i),
910 old_roster, new_roster, nodes);
911
912 for (set<node_id>::const_iterator i = nodes.begin(); i != nodes.end();
913 ++i)
914 {
915 file_path p;
916 if (new_roster.has_node(*i))
917 new_roster.get_name(*i, p);
918 else
919 old_roster.get_name(*i, p);
920 print_paths.insert(p);
921 }
922 }
923
924 copy(print_paths.begin(), print_paths.end(),
925 ostream_iterator<file_path>(cout, "\n"));
926}
927
928namespace
929{
930 namespace syms
931 {
932 // certs
933 symbol const key("key");
934 symbol const signature("signature");
935 symbol const name("name");
936 symbol const value("value");
937 symbol const trust("trust");
938
939 // keys
940 symbol const hash("hash");
941 symbol const given_name("given_name");
942 symbol const local_name("local_name");
943 symbol const public_location("public_location");
944 symbol const private_location("private_location");
945 }
946};
947
948// Name: keys
949// Arguments: none
950// Added in: 1.1
951// Changed in: 10.0
952// Purpose: Prints all keys in the keystore, and if a database is given
953// also all keys in the database, in basic_io format.
954// Output format: For each key, a basic_io stanza is printed. The items in
955// the stanza are:
956// name - the key identifier
957// hash - the hash of the key
958// public_location - where the public half of the key is stored
959// private_location - where the private half of the key is stored
960// The *_location items may have multiple values, as shown below
961// for public_location.
962// If the private key does not exist, then the private_hash and
963// private_location items will be absent.
964//
965// Sample output:
966// name "tbrownaw@gmail.com"
967// hash [475055ec71ad48f5dfaf875b0fea597b5cbbee64]
968// public_location "database" "keystore"
969// private_location "keystore"
970//
971// name "njs@pobox.com"
972// hash [de84b575d5e47254393eba49dce9dc4db98ed42d]
973// public_location "database"
974//
975// name "foo@bar.com"
976// hash [7b6ce0bd83240438e7a8c7c207d8654881b763f6]
977// public_location "keystore"
978// private_location "keystore"
979//
980// Error conditions: None.
981CMD_AUTOMATE(keys, "",
982 N_("Lists all keys in the keystore"),
983 "",
984 options::opts::none)
985{
986 E(args.empty(), origin::user,
987 F("no arguments needed"));
988
989 database db(app, database::maybe_unspecified);
990 key_store keys(app);
991 project_t project(db);
992
993 key_map items;
994 get_key_list(db, keys, app.lua, project, items);
995
996 basic_io::printer prt;
997 for (key_map::iterator i = items.begin(); i != items.end(); ++i)
998 {
999 basic_io::stanza stz;
1000 stz.push_binary_pair(syms::hash, i->first.inner());
1001 stz.push_str_pair(syms::given_name, i->second.identity.given_name());
1002 stz.push_str_pair(syms::local_name, i->second.identity.official_name());
1003 stz.push_str_multi(syms::public_location, i->second.public_locations);
1004 if (!i->second.private_locations.empty())
1005 stz.push_str_multi(syms::private_location, i->second.private_locations);
1006 prt.print_stanza(stz);
1007 }
1008 output.write(prt.buf.data(), prt.buf.size());
1009}
1010
1011// Name: certs
1012// Arguments:
1013// 1: a revision id
1014// Added in: 1.0
1015// Purpose: Prints all certificates associated with the given revision
1016// ID. Each certificate is contained in a basic IO stanza. For each
1017// certificate, the following values are provided:
1018//
1019// 'key' : a string indicating the key used to sign this certificate.
1020// 'signature': a string indicating the status of the signature.
1021// Possible values of this string are:
1022// 'ok' : the signature is correct
1023// 'bad' : the signature is invalid
1024// 'unknown' : signature was made with an unknown key
1025// 'name' : the name of this certificate
1026// 'value' : the value of this certificate
1027// 'trust' : is this certificate trusted by the defined trust metric
1028// Possible values of this string are:
1029// 'trusted' : this certificate is trusted
1030// 'untrusted' : this certificate is not trusted
1031//
1032// Output format: All stanzas are formatted by basic_io. Stanzas are
1033// seperated by a blank line. Values will be escaped, '\' -> '\\' and
1034// '"' -> '\"'.
1035//
1036// Error conditions: If a certificate is signed with an unknown public
1037// key, a warning message is printed to stderr. If the revision
1038// specified is unknown or invalid prints an error message to stderr
1039// and exits with status 1.
1040CMD_AUTOMATE(certs, N_("REV"),
1041 N_("Prints all certificates attached to a revision"),
1042 "",
1043 options::opts::none)
1044{
1045 E(args.size() == 1, origin::user,
1046 F("wrong argument count"));
1047
1048 database db(app);
1049 project_t project(db);
1050
1051 vector<cert> certs;
1052
1053 transaction_guard guard(db, false);
1054
1055 hexenc<id> hrid(idx(args, 0)(), origin::user);
1056 revision_id rid(decode_hexenc_as<revision_id>(hrid(), origin::user));
1057
1058 E(db.revision_exists(rid), origin::user,
1059 F("no revision %s found in database") % hrid);
1060
1061 vector<cert> ts;
1062 // FIXME_PROJECTS: after projects are implemented,
1063 // use the db version instead if no project is specified.
1064 project.get_revision_certs(rid, ts);
1065
1066 for (size_t i = 0; i < ts.size(); ++i)
1067 certs.push_back(idx(ts, i));
1068
1069 {
1070 set<key_id> checked;
1071 for (size_t i = 0; i < certs.size(); ++i)
1072 {
1073 if (checked.find(idx(certs, i).key) == checked.end() &&
1074 !db.public_key_exists(idx(certs, i).key))
1075 W(F("no public key %s found in database")
1076 % idx(certs, i).key);
1077 checked.insert(idx(certs, i).key);
1078 }
1079 }
1080
1081 // Make the output deterministic; this is useful for the test suite,
1082 // in particular.
1083 sort(certs.begin(), certs.end());
1084
1085 basic_io::printer pr;
1086
1087 for (size_t i = 0; i < certs.size(); ++i)
1088 {
1089 basic_io::stanza st;
1090 cert_status status = db.check_cert(idx(certs, i));
1091 cert_value tv = idx(certs, i).value;
1092 cert_name name = idx(certs, i).name;
1093 set<key_identity_info> signers;
1094
1095 key_identity_info identity;
1096 identity.id = idx(certs, i).key;
1097 project.complete_key_identity_from_id(app.lua, identity);
1098 signers.insert(identity);
1099
1100 bool trusted =
1101 app.lua.hook_get_revision_cert_trust(signers, rid.inner(),
1102 name, tv);
1103
1104 hexenc<id> keyid_enc;
1105 encode_hexenc(identity.id.inner(), keyid_enc);
1106 st.push_hex_pair(syms::key, keyid_enc);
1107
1108 string stat;
1109 switch (status)
1110 {
1111 case cert_ok:
1112 stat = "ok";
1113 break;
1114 case cert_bad:
1115 stat = "bad";
1116 break;
1117 case cert_unknown:
1118 stat = "unknown";
1119 break;
1120 }
1121 st.push_str_pair(syms::signature, stat);
1122
1123 st.push_str_pair(syms::name, name());
1124 st.push_str_pair(syms::value, tv());
1125 st.push_str_pair(syms::trust, (trusted ? "trusted" : "untrusted"));
1126
1127 pr.print_stanza(st);
1128 }
1129 output.write(pr.buf.data(), pr.buf.size());
1130
1131 guard.commit();
1132}
1133
1134
1135// Local Variables:
1136// mode: C++
1137// fill-column: 76
1138// c-file-style: "gnu"
1139// indent-tabs-mode: nil
1140// End:
1141// 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