monotone

monotone Mtn Source Tree

Root/automate.cc

1// Copyright (C) 2004, 2007 Nathaniel Smith <njs@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 "base.hh"
11#include <algorithm>
12#include <iterator>
13#include <sstream>
14#include <unistd.h>
15#include "vector.hh"
16
17#include <boost/bind.hpp>
18#include <boost/function.hpp>
19#include <boost/lexical_cast.hpp>
20#include <boost/tuple/tuple.hpp>
21
22#include "app_state.hh"
23#include "basic_io.hh"
24#include "cert.hh"
25#include "cmd.hh"
26#include "commands.hh"
27#include "constants.hh"
28#include "inodeprint.hh"
29#include "keys.hh"
30#include "file_io.hh"
31#include "packet.hh"
32#include "restrictions.hh"
33#include "revision.hh"
34#include "transforms.hh"
35#include "vocab.hh"
36#include "globish.hh"
37#include "charset.hh"
38#include "safe_map.hh"
39
40using std::allocator;
41using std::basic_ios;
42using std::basic_stringbuf;
43using std::char_traits;
44using std::inserter;
45using std::make_pair;
46using std::map;
47using std::multimap;
48using std::ostream;
49using std::ostringstream;
50using std::pair;
51using std::set;
52using std::sort;
53using std::streamsize;
54using std::string;
55using std::vector;
56
57
58// Name: heads
59// Arguments:
60// 1: branch name (optional, default branch is used if non-existant)
61// Added in: 0.0
62// Purpose: Prints the heads of the given branch.
63// Output format: A list of revision ids, in hexadecimal, each followed by a
64// newline. Revision ids are printed in alphabetically sorted order.
65// Error conditions: If the branch does not exist, prints nothing. (There are
66// no heads.)
67CMD_AUTOMATE(heads, N_("[BRANCH]"),
68 N_("Prints the heads of the given branch"),
69 "",
70 options::opts::none)
71{
72 N(args.size() < 2,
73 F("wrong argument count"));
74
75 system_path database_option;
76 branch_name branch_option;
77 rsa_keypair_id key_option;
78 system_path keydir_option;
79 app.work.get_ws_options(database_option, branch_option,
80 key_option, keydir_option);
81
82 if (args.size() == 1 ) {
83 // branchname was explicitly given, use that
84 branch_option = branch_name(idx(args, 0)());
85 }
86 set<revision_id> heads;
87 app.get_project().get_branch_heads(branch_option, heads);
88 for (set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
89 output << (*i).inner()() << '\n';
90}
91
92// Name: ancestors
93// Arguments:
94// 1 or more: revision ids
95// Added in: 0.2
96// Purpose: Prints the ancestors (exclusive) of the given revisions
97// Output format: A list of revision ids, in hexadecimal, each followed by a
98// newline. Revision ids are printed in alphabetically sorted order.
99// Error conditions: If any of the revisions do not exist, prints nothing to
100// stdout, prints an error message to stderr, and exits with status 1.
101CMD_AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 [...]]]"),
102 N_("Prints the ancestors of the given revisions"),
103 "",
104 options::opts::none)
105{
106 N(args.size() > 0,
107 F("wrong argument count"));
108
109 set<revision_id> ancestors;
110 vector<revision_id> frontier;
111 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
112 {
113 revision_id rid((*i)());
114 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
115 frontier.push_back(rid);
116 }
117 while (!frontier.empty())
118 {
119 revision_id rid = frontier.back();
120 frontier.pop_back();
121 if(!null_id(rid)) {
122 set<revision_id> parents;
123 app.db.get_revision_parents(rid, parents);
124 for (set<revision_id>::const_iterator i = parents.begin();
125 i != parents.end(); ++i)
126 {
127 if (ancestors.find(*i) == ancestors.end())
128 {
129 frontier.push_back(*i);
130 ancestors.insert(*i);
131 }
132 }
133 }
134 }
135 for (set<revision_id>::const_iterator i = ancestors.begin();
136 i != ancestors.end(); ++i)
137 if (!null_id(*i))
138 output << (*i).inner()() << '\n';
139}
140
141
142// Name: descendents
143// Arguments:
144// 1 or more: revision ids
145// Added in: 0.1
146// Purpose: Prints the descendents (exclusive) of the given revisions
147// Output format: A list of revision ids, in hexadecimal, each followed by a
148// newline. Revision ids are printed in alphabetically sorted order.
149// Error conditions: If any of the revisions do not exist, prints nothing to
150// stdout, prints an error message to stderr, and exits with status 1.
151CMD_AUTOMATE(descendents, N_("REV1 [REV2 [REV3 [...]]]"),
152 N_("Prints the descendents of the given revisions"),
153 "",
154 options::opts::none)
155{
156 N(args.size() > 0,
157 F("wrong argument count"));
158
159 set<revision_id> descendents;
160 vector<revision_id> frontier;
161 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
162 {
163 revision_id rid((*i)());
164 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
165 frontier.push_back(rid);
166 }
167 while (!frontier.empty())
168 {
169 revision_id rid = frontier.back();
170 frontier.pop_back();
171 set<revision_id> children;
172 app.db.get_revision_children(rid, children);
173 for (set<revision_id>::const_iterator i = children.begin();
174 i != children.end(); ++i)
175 {
176 if (descendents.find(*i) == descendents.end())
177 {
178 frontier.push_back(*i);
179 descendents.insert(*i);
180 }
181 }
182 }
183 for (set<revision_id>::const_iterator i = descendents.begin();
184 i != descendents.end(); ++i)
185 output << (*i).inner()() << '\n';
186}
187
188
189// Name: erase_ancestors
190// Arguments:
191// 0 or more: revision ids
192// Added in: 0.1
193// Purpose: Prints all arguments, except those that are an ancestor of some
194// other argument. One way to think about this is that it prints the
195// minimal elements of the given set, under the ordering imposed by the
196// "child of" relation. Another way to think of it is if the arguments were
197// a branch, then we print the heads of that branch.
198// Output format: A list of revision ids, in hexadecimal, each followed by a
199// newline. Revision ids are printed in alphabetically sorted order.
200// Error conditions: If any of the revisions do not exist, prints nothing to
201// stdout, prints an error message to stderr, and exits with status 1.
202CMD_AUTOMATE(erase_ancestors, N_("[REV1 [REV2 [REV3 [...]]]]"),
203 N_("Erases the ancestors in a list of revisions"),
204 "",
205 options::opts::none)
206{
207 set<revision_id> revs;
208 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
209 {
210 revision_id rid((*i)());
211 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
212 revs.insert(rid);
213 }
214 erase_ancestors(revs, app);
215 for (set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
216 output << (*i).inner()() << '\n';
217}
218
219// Name: toposort
220// Arguments:
221// 0 or more: revision ids
222// Added in: 0.1
223// Purpose: Prints all arguments, topologically sorted. I.e., if A is an
224// ancestor of B, then A will appear before B in the output list.
225// Output format: A list of revision ids, in hexadecimal, each followed by a
226// newline. Revisions are printed in topologically sorted order.
227// Error conditions: If any of the revisions do not exist, prints nothing to
228// stdout, prints an error message to stderr, and exits with status 1.
229CMD_AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 [...]]]]"),
230 N_("Topologically sorts a list of revisions"),
231 "",
232 options::opts::none)
233{
234 set<revision_id> revs;
235 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
236 {
237 revision_id rid((*i)());
238 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
239 revs.insert(rid);
240 }
241 vector<revision_id> sorted;
242 toposort(revs, sorted, app);
243 for (vector<revision_id>::const_iterator i = sorted.begin();
244 i != sorted.end(); ++i)
245 output << (*i).inner()() << '\n';
246}
247
248// Name: ancestry_difference
249// Arguments:
250// 1: a revision id
251// 0 or more further arguments: also revision ids
252// Added in: 0.1
253// Purpose: Prints all ancestors of the first revision A, that are not also
254// ancestors of the other revision ids, the "Bs". For purposes of this
255// command, "ancestor" is an inclusive term; that is, A is an ancestor of
256// one of the Bs, it will not be printed, but otherwise, it will be; and
257// none of the Bs will ever be printed. If A is a new revision, and Bs are
258// revisions that you have processed before, then this command tells you
259// which revisions are new since then.
260// Output format: A list of revision ids, in hexadecimal, each followed by a
261// newline. Revisions are printed in topologically sorted order.
262// Error conditions: If any of the revisions do not exist, prints nothing to
263// stdout, prints an error message to stderr, and exits with status 1.
264CMD_AUTOMATE(ancestry_difference, N_("NEW_REV [OLD_REV1 [OLD_REV2 [...]]]"),
265 N_("Lists the ancestors of the first revision given, not in "
266 "the others"),
267 "",
268 options::opts::none)
269{
270 N(args.size() > 0,
271 F("wrong argument count"));
272
273 revision_id a;
274 set<revision_id> bs;
275 args_vector::const_iterator i = args.begin();
276 a = revision_id((*i)());
277 N(app.db.revision_exists(a), F("No such revision %s") % a);
278 for (++i; i != args.end(); ++i)
279 {
280 revision_id b((*i)());
281 N(app.db.revision_exists(b), F("No such revision %s") % b);
282 bs.insert(b);
283 }
284 set<revision_id> ancestors;
285 ancestry_difference(a, bs, ancestors, app);
286
287 vector<revision_id> sorted;
288 toposort(ancestors, sorted, app);
289 for (vector<revision_id>::const_iterator i = sorted.begin();
290 i != sorted.end(); ++i)
291 output << (*i).inner()() << '\n';
292}
293
294// Name: leaves
295// Arguments:
296// None
297// Added in: 0.1
298// Purpose: Prints the leaves of the revision graph, i.e., all revisions that
299// have no children. This is similar, but not identical to the
300// functionality of 'heads', which prints every revision in a branch, that
301// has no descendents in that branch. If every revision in the database was
302// in the same branch, then they would be identical. Generally, every leaf
303// is the head of some branch, but not every branch head is a leaf.
304// Output format: A list of revision ids, in hexadecimal, each followed by a
305// newline. Revision ids are printed in alphabetically sorted order.
306// Error conditions: None.
307CMD_AUTOMATE(leaves, "",
308 N_("Lists the leaves of the revision graph"),
309 "",
310 options::opts::none)
311{
312 N(args.size() == 0,
313 F("no arguments needed"));
314
315 // this might be more efficient in SQL, but for now who cares.
316 set<revision_id> leaves;
317 app.db.get_revision_ids(leaves);
318 multimap<revision_id, revision_id> graph;
319 app.db.get_revision_ancestry(graph);
320 for (multimap<revision_id, revision_id>::const_iterator
321 i = graph.begin(); i != graph.end(); ++i)
322 leaves.erase(i->first);
323 for (set<revision_id>::const_iterator i = leaves.begin();
324 i != leaves.end(); ++i)
325 output << (*i).inner()() << '\n';
326}
327
328// Name: roots
329// Arguments:
330// None
331// Added in: 4.3
332// Purpose: Prints the roots of the revision graph, i.e. all revisions that
333// have no parents.
334// Output format: A list of revision ids, in hexadecimal, each followed by a
335// newline. Revision ids are printed in alphabetically sorted order.
336// Error conditions: None.
337CMD_AUTOMATE(roots, "",
338 N_("Lists the roots of the revision graph"),
339 "",
340 options::opts::none)
341{
342 N(args.size() == 0,
343 F("no arguments needed"));
344
345 // the real root revisions are the children of one single imaginary root
346 // with an empty revision id
347 set<revision_id> roots;
348 revision_id nullid;
349 app.db.get_revision_children(nullid, roots);
350 for (set<revision_id>::const_iterator i = roots.begin();
351 i != roots.end(); ++i)
352 output << i->inner()() << '\n';
353}
354
355// Name: parents
356// Arguments:
357// 1: a revision id
358// Added in: 0.2
359// Purpose: Prints the immediate ancestors of the given revision, i.e., the
360// parents.
361// Output format: A list of revision ids, in hexadecimal, each followed by a
362// newline. Revision ids are printed in alphabetically sorted order.
363// Error conditions: If the revision does not exist, prints nothing to stdout,
364// prints an error message to stderr, and exits with status 1.
365CMD_AUTOMATE(parents, N_("REV"),
366 N_("Prints the parents of a revision"),
367 "",
368 options::opts::none)
369{
370 N(args.size() == 1,
371 F("wrong argument count"));
372
373 revision_id rid(idx(args, 0)());
374 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
375 set<revision_id> parents;
376 app.db.get_revision_parents(rid, parents);
377 for (set<revision_id>::const_iterator i = parents.begin();
378 i != parents.end(); ++i)
379 if (!null_id(*i))
380 output << (*i).inner()() << '\n';
381}
382
383// Name: children
384// Arguments:
385// 1: a revision id
386// Added in: 0.2
387// Purpose: Prints the immediate descendents of the given revision, i.e., the
388// children.
389// Output format: A list of revision ids, in hexadecimal, each followed by a
390// newline. Revision ids are printed in alphabetically sorted order.
391// Error conditions: If the revision does not exist, prints nothing to stdout,
392// prints an error message to stderr, and exits with status 1.
393CMD_AUTOMATE(children, N_("REV"),
394 N_("Prints the children of a revision"),
395 "",
396 options::opts::none)
397{
398 N(args.size() == 1,
399 F("wrong argument count"));
400
401 revision_id rid(idx(args, 0)());
402 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
403 set<revision_id> children;
404 app.db.get_revision_children(rid, children);
405 for (set<revision_id>::const_iterator i = children.begin();
406 i != children.end(); ++i)
407 if (!null_id(*i))
408 output << (*i).inner()() << '\n';
409}
410
411// Name: graph
412// Arguments:
413// None
414// Added in: 0.2
415// Purpose: Prints out the complete ancestry graph of this database.
416// Output format:
417// Each line begins with a revision id. Following this are zero or more
418// space-prefixed revision ids. Each revision id after the first is a
419// parent (in the sense of 'automate parents') of the first. For instance,
420// the following are valid lines:
421// 07804171823d963f78d6a0ff1763d694dd74ff40
422// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01
423// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01 a02e7a1390e3e4745c31be922f03f56450c13dce
424// The first would indicate that 07804171823d963f78d6a0ff1763d694dd74ff40
425// was a root node; the second would indicate that it had one parent, and
426// the third would indicate that it had two parents, i.e., was a merge.
427//
428// The output as a whole is alphabetically sorted; additionally, the parents
429// within each line are alphabetically sorted.
430// Error conditions: None.
431CMD_AUTOMATE(graph, "",
432 N_("Prints the complete ancestry graph"),
433 "",
434 options::opts::none)
435{
436 N(args.size() == 0,
437 F("no arguments needed"));
438
439 multimap<revision_id, revision_id> edges_mmap;
440 map<revision_id, set<revision_id> > child_to_parents;
441
442 app.db.get_revision_ancestry(edges_mmap);
443
444 for (multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
445 i != edges_mmap.end(); ++i)
446 {
447 if (child_to_parents.find(i->second) == child_to_parents.end())
448 child_to_parents.insert(make_pair(i->second, set<revision_id>()));
449 if (null_id(i->first))
450 continue;
451 map<revision_id, set<revision_id> >::iterator
452 j = child_to_parents.find(i->second);
453 I(j->first == i->second);
454 j->second.insert(i->first);
455 }
456
457 for (map<revision_id, set<revision_id> >::const_iterator
458 i = child_to_parents.begin();
459 i != child_to_parents.end(); ++i)
460 {
461 output << (i->first).inner()();
462 for (set<revision_id>::const_iterator j = i->second.begin();
463 j != i->second.end(); ++j)
464 output << ' ' << (*j).inner()();
465 output << '\n';
466 }
467}
468
469// Name: select
470// Arguments:
471// 1: selector
472// Added in: 0.2
473// Purpose: Prints all the revisions that match the given selector.
474// Output format: A list of revision ids, in hexadecimal, each followed by a
475// newline. Revision ids are printed in alphabetically sorted order.
476// Error conditions: None.
477CMD_AUTOMATE(select, N_("SELECTOR"),
478 N_("Lists the revisions that match a selector"),
479 "",
480 options::opts::none)
481{
482 N(args.size() == 1,
483 F("wrong argument count"));
484
485 vector<pair<selectors::selector_type, string> >
486 sels(selectors::parse_selector(args[0](), app));
487
488 // we jam through an "empty" selection on sel_ident type
489 set<string> completions;
490 selectors::selector_type ty = selectors::sel_ident;
491 selectors::complete_selector("", sels, ty, completions, app);
492
493 for (set<string>::const_iterator i = completions.begin();
494 i != completions.end(); ++i)
495 output << *i << '\n';
496}
497
498struct node_info
499{
500 bool exists;
501 // true if node_id is present in corresponding roster with the inventory map file_path
502 // false if not present, or present with a different file_path
503 // rest of data in this struct is invalid if false.
504 node_id id;
505 path::status type;
506 file_id ident;
507 full_attr_map_t attrs;
508
509 node_info() : exists(false), type(path::nonexistent) {}
510};
511
512static void
513get_node_info(node_t const & node, node_info & info)
514{
515 info.exists = true;
516 info.id = node->self;
517 info.attrs = node->attrs;
518 if (is_file_t(node))
519 {
520 info.type = path::file;
521 info.ident = downcast_to_file_t(node)->content;
522 }
523 else if (is_dir_t(node))
524 info.type = path::directory;
525 else
526 I(false);
527}
528
529struct inventory_item
530{
531 // Records information about a pair of nodes with the same node_id in the
532 // old roster and new roster, and the corresponding path in the
533 // filesystem.
534 node_info old_node;
535 node_info new_node;
536 file_path old_path;
537 file_path new_path;
538
539 path::status fs_type;
540 file_id fs_ident;
541
542 inventory_item() : fs_type(path::nonexistent) {}
543};
544
545typedef std::map<file_path, inventory_item> inventory_map;
546// file_path will typically be an existing filesystem file, but in the case
547// of a dropped or rename_source file it is only in the old roster, and in
548// the case of a file added --bookkeep_only or rename_target
549// --bookkeep_only, it is only in the new roster.
550
551static void
552inventory_rosters(roster_t const & old_roster,
553 roster_t const & new_roster,
554 node_restriction const & mask,
555 inventory_map & inventory)
556{
557 std::map<int, file_path> old_paths;
558 std::map<int, file_path> new_paths;
559
560 node_map const & old_nodes = old_roster.all_nodes();
561 for (node_map::const_iterator i = old_nodes.begin(); i != old_nodes.end(); ++i)
562 {
563 if (mask.includes(old_roster, i->first))
564 {
565 file_path fp;
566 old_roster.get_name(i->first, fp);
567 get_node_info(old_roster.get_node(i->first), inventory[fp].old_node);
568 old_paths[inventory[fp].old_node.id] = fp;
569 }
570 }
571
572 node_map const & new_nodes = new_roster.all_nodes();
573 for (node_map::const_iterator i = new_nodes.begin(); i != new_nodes.end(); ++i)
574 {
575 if (mask.includes(new_roster, i->first))
576 {
577 file_path fp;
578 new_roster.get_name(i->first, fp);
579 get_node_info(new_roster.get_node(i->first), inventory[fp].new_node);
580 new_paths[inventory[fp].new_node.id] = fp;
581 }
582 }
583
584 std::map<int, file_path>::iterator i;
585 for (i = old_paths.begin(); i != old_paths.end(); ++i)
586 {
587 if (new_paths.find(i->first) == new_paths.end())
588 {
589 // There is no new node available; this is either a drop or a
590 // rename to outside the current path restriction.
591
592 if (new_roster.has_node (i->first))
593 {
594 // record rename to outside restriction
595 new_roster.get_name (i->first, inventory[i->second].new_path);
596 continue;
597 }
598 else
599 // drop; no new path
600 continue;
601 }
602
603 file_path old_path(i->second);
604 file_path new_path(new_paths[i->first]);
605
606 // both paths are identical, no rename
607 if (old_path == new_path)
608 continue;
609
610 // record rename
611 inventory[new_path].old_path = old_path;
612 inventory[old_path].new_path = new_path;
613 }
614
615 // Now look for new_paths that are renames from outside the current
616 // restriction, and thus are not in old_paths.
617 // FIXME: only need this if restriction is not null
618 for (i = new_paths.begin(); i != new_paths.end(); ++i)
619 {
620 if (old_paths.find(i->first) == old_paths.end())
621 {
622 // There is no old node available; this is either added or a
623 // rename from outside the current path restriction.
624
625 if (old_roster.has_node (i->first))
626 {
627 // record rename from outside restriction
628 old_roster.get_name (i->first, inventory[i->second].old_path);
629 }
630 else
631 // added; no old path
632 continue;
633 }
634 }
635}
636
637struct inventory_itemizer : public tree_walker
638{
639 path_restriction const & mask;
640 inventory_map & inventory;
641 app_state & app;
642 inodeprint_map ipm;
643
644 inventory_itemizer(path_restriction const & m, inventory_map & i, app_state & a) :
645 mask(m), inventory(i), app(a)
646 {
647 if (app.work.in_inodeprints_mode())
648 {
649 data dat;
650 app.work.read_inodeprints(dat);
651 read_inodeprint_map(dat, ipm);
652 }
653 }
654 virtual bool visit_dir(file_path const & path);
655 virtual void visit_file(file_path const & path);
656};
657
658bool
659inventory_itemizer::visit_dir(file_path const & path)
660{
661 if(mask.includes(path))
662 {
663 inventory[path].fs_type = path::directory;
664 }
665 // always recurse into subdirectories
666 return true;
667}
668
669void
670inventory_itemizer::visit_file(file_path const & path)
671{
672 if (mask.includes(path))
673 {
674 inventory_item & item = inventory[path];
675
676 item.fs_type = path::file;
677
678 if (item.new_node.exists)
679 {
680 if (inodeprint_unchanged(ipm, path))
681 item.fs_ident = item.old_node.ident;
682 else
683 ident_existing_file(path, item.fs_ident);
684 }
685 }
686}
687
688static void
689inventory_filesystem(path_restriction const & mask, inventory_map & inventory, app_state & app)
690{
691 inventory_itemizer itemizer(mask, inventory, app);
692 file_path const root;
693 // The constructor file_path() returns ""; the root directory. walk_tree
694 // does not visit that node, so set fs_type now, if it meets the
695 // restriction.
696 if (mask.includes(root))
697 {
698 inventory[root].fs_type = path::directory;
699 }
700 walk_tree(root, itemizer);
701}
702
703namespace
704{
705 namespace syms
706 {
707 symbol const path("path");
708 symbol const old_type("old_type");
709 symbol const new_type("new_type");
710 symbol const fs_type("fs_type");
711 symbol const old_path("old_path");
712 symbol const new_path("new_path");
713 symbol const status("status");
714 symbol const changes("changes");
715 }
716}
717
718static void
719inventory_print_states(app_state & app, file_path const & fs_path,
720 inventory_item const & item, roster_t const & old_roster,
721 roster_t const & new_roster, basic_io::stanza & st)
722{
723 std::vector<std::string> states;
724
725 // if both nodes exist, the only interesting case is
726 // when the node ids aren't equal (so we have different nodes
727 // with one and the same path in the old and the new roster)
728 if (item.old_node.exists &&
729 item.new_node.exists &&
730 item.old_node.id != item.new_node.id)
731 {
732 if (new_roster.has_node(item.old_node.id))
733 states.push_back("rename_source");
734 else
735 states.push_back("dropped");
736
737 if (old_roster.has_node(item.new_node.id))
738 states.push_back("rename_target");
739 else
740 states.push_back("added");
741 }
742 // this can be either a drop or a renamed item
743 else if (item.old_node.exists &&
744 !item.new_node.exists)
745 {
746 if (new_roster.has_node(item.old_node.id))
747 states.push_back("rename_source");
748 else
749 states.push_back("dropped");
750 }
751 // this can be either an add or a renamed item
752 else if (!item.old_node.exists &&
753 item.new_node.exists)
754 {
755 if (old_roster.has_node(item.new_node.id))
756 states.push_back("rename_target");
757 else
758 states.push_back("added");
759 }
760
761 // check the state of the file system item
762 if (item.fs_type == path::nonexistent)
763 {
764 if (item.new_node.exists)
765 states.push_back("missing");
766 }
767 else // exists on filesystem
768 {
769 if (!item.new_node.exists)
770 {
771 if (app.lua.hook_ignore_file(fs_path))
772 states.push_back("ignored");
773 else
774 states.push_back("unknown");
775 }
776 else if (item.new_node.type != item.fs_type)
777 states.push_back("invalid");
778 else
779 states.push_back("known");
780 }
781
782 st.push_str_multi(syms::status, states);
783}
784
785static void
786inventory_print_changes(inventory_item const & item, roster_t const & old_roster,
787 basic_io::stanza & st)
788{
789 // old nodes do not have any recorded content changes and attributes,
790 // so we can't print anything for them here
791 if (!item.new_node.exists) return;
792
793 std::vector<string> changes;
794
795 // this is an existing item
796 if (old_roster.has_node(item.new_node.id))
797 {
798 // check if the content has changed - this makes only sense for files
799 // for which we can get the content id of both new and old nodes.
800 if (item.new_node.type == path::file && item.fs_type != path::nonexistent)
801 {
802 file_t old_file = downcast_to_file_t(old_roster.get_node(item.new_node.id));
803
804 switch (item.old_node.type)
805 {
806 case path::file:
807 case path::nonexistent:
808 // A file can be nonexistent due to mtn drop, user delete, mtn
809 // rename, or user rename. If it was drop or delete, it would
810 // not be in the new roster, and we would not get here. So
811 // it's a rename, and we can get the content. This lets us
812 // check if a user has edited a file after renaming it.
813 if (item.fs_ident != old_file->content)
814 changes.push_back("content");
815 break;
816
817 case path::directory:
818 break;
819 }
820 }
821
822 // now look for changed attributes
823 node_t old_node = old_roster.get_node(item.new_node.id);
824 if (old_node->attrs != item.new_node.attrs)
825 changes.push_back("attrs");
826 }
827 else
828 {
829 // FIXME: paranoia: shall we I(new_roster.has_node(item.new_node.id)) here?
830
831 // this is apparently a new item, if it is a file it gets at least
832 // the "content" marker and we also check for recorded attributes
833 if (item.new_node.type == path::file)
834 changes.push_back("content");
835
836 if (item.new_node.attrs.size() > 0)
837 changes.push_back("attrs");
838 }
839
840 if (!changes.empty())
841 st.push_str_multi(syms::changes, changes);
842}
843
844// Name: inventory
845// Arguments: [PATH]...
846// Added in: 1.0
847// Modified to basic_io in: 4.1
848
849// Purpose: Prints a summary of every file or directory found in the
850// workspace or its associated base manifest.
851
852// See monotone.texi for output format description.
853//
854// Error conditions: If no workspace book keeping _MTN directory is found,
855// prints an error message to stderr, and exits with status 1.
856
857CMD_AUTOMATE(inventory, N_("[PATH]..."),
858 N_("Prints a summary of files found in the workspace"),
859 "",
860 options::opts::depth | options::opts::exclude)
861{
862 app.require_workspace();
863
864 parent_map parents;
865 app.work.get_parent_rosters(parents);
866 // for now, until we've figured out what the format could look like
867 // and what conceptional model we can implement
868 // see: http://www.venge.net/mtn-wiki/MultiParentWorkspaceFallout
869 N(parents.size() == 1,
870 F("this command can only be used in a single-parent workspace"));
871
872 roster_t new_roster, old_roster = parent_roster(parents.begin());
873 temp_node_id_source nis;
874
875 app.work.get_current_roster_shape(new_roster, nis);
876
877 inventory_map inventory;
878 vector<file_path> includes = args_to_paths(args);
879 vector<file_path> excludes = args_to_paths(app.opts.exclude_patterns);
880
881 node_restriction nmask(includes, excludes, app.opts.depth, old_roster, new_roster, app);
882 inventory_rosters(old_roster, new_roster, nmask, inventory);
883
884 path_restriction pmask(includes, excludes, app.opts.depth, app);
885 inventory_filesystem(pmask, inventory, app);
886
887 basic_io::printer pr;
888
889 for (inventory_map::const_iterator i = inventory.begin(); i != inventory.end();
890 ++i)
891 {
892 basic_io::stanza st;
893 inventory_item const & item = i->second;
894
895 st.push_file_pair(syms::path, i->first);
896
897 if (item.old_node.exists)
898 {
899 switch (item.old_node.type)
900 {
901 case path::file: st.push_str_pair(syms::old_type, "file"); break;
902 case path::directory: st.push_str_pair(syms::old_type, "directory"); break;
903 case path::nonexistent: I(false);
904 }
905
906 if (item.new_path.as_internal().length() > 0)
907 // new_path is only present if different from either inventory map path or old_path
908 st.push_file_pair(syms::new_path, item.new_path);
909 }
910
911 if (item.new_node.exists)
912 {
913 switch (item.new_node.type)
914 {
915 case path::file: st.push_str_pair(syms::new_type, "file"); break;
916 case path::directory: st.push_str_pair(syms::new_type, "directory"); break;
917 case path::nonexistent: I(false);
918 }
919
920 if (item.old_path.as_internal().length() > 0)
921 st.push_file_pair(syms::old_path, item.old_path);
922 }
923
924 switch (item.fs_type)
925 {
926 case path::file: st.push_str_pair(syms::fs_type, "file"); break;
927 case path::directory: st.push_str_pair(syms::fs_type, "directory"); break;
928 case path::nonexistent: st.push_str_pair(syms::fs_type, "none"); break;
929 }
930
931 inventory_print_states(app, i->first, item, old_roster, new_roster, st);
932 inventory_print_changes(item, old_roster, st);
933
934 pr.print_stanza(st);
935 }
936
937 output.write(pr.buf.data(), pr.buf.size());
938}
939
940// Name: get_revision
941// Arguments:
942// 1: a revision id (optional, determined from the workspace if
943// non-existant)
944// Added in: 1.0
945
946// Purpose: Prints change information for the specified revision id.
947// There are several changes that are described; each of these is
948// described by a different basic_io stanza. The first string pair
949// of each stanza indicates the type of change represented.
950//
951// All stanzas are formatted by basic_io. Stanzas are separated
952// by a blank line. Values will be escaped, '\' to '\\' and
953// '"' to '\"'.
954//
955// Possible values of this first value are along with an ordered list of
956// basic_io formatted stanzas that will be provided are:
957//
958// 'format_version'
959// used in case this format ever needs to change.
960// format: ('format_version', the string "1")
961// occurs: exactly once
962// 'new_manifest'
963// represents the new manifest associated with the revision.
964// format: ('new_manifest', manifest id)
965// occurs: exactly one
966// 'old_revision'
967// represents a parent revision.
968// format: ('old_revision', revision id)
969// occurs: either one or two times
970// 'delete
971// represents a file or directory that was deleted.
972// format: ('delete', path)
973// occurs: zero or more times
974// 'rename'
975// represents a file or directory that was renamed.
976// format: ('rename, old filename), ('to', new filename)
977// occurs: zero or more times
978// 'add_dir'
979// represents a directory that was added.
980// format: ('add_dir, path)
981// occurs: zero or more times
982// 'add_file'
983// represents a file that was added.
984// format: ('add_file', path), ('content', file id)
985// occurs: zero or more times
986// 'patch'
987// represents a file that was modified.
988// format: ('patch', filename), ('from', file id), ('to', file id)
989// occurs: zero or more times
990// 'clear'
991// represents an attr that was removed.
992// format: ('clear', filename), ('attr', attr name)
993// occurs: zero or more times
994// 'set'
995// represents an attr whose value was changed.
996// format: ('set', filename), ('attr', attr name), ('value', attr value)
997// occurs: zero or more times
998//
999// These stanzas will always occur in the order listed here; stanzas of
1000// the same type will be sorted by the filename they refer to.
1001// Error conditions: If the revision specified is unknown or invalid
1002// prints an error message to stderr and exits with status 1.
1003CMD_AUTOMATE(get_revision, N_("[REVID]"),
1004 N_("Shows change information for a revision"),
1005 "",
1006 options::opts::none)
1007{
1008 N(args.size() < 2,
1009 F("wrong argument count"));
1010
1011 temp_node_id_source nis;
1012 revision_data dat;
1013 revision_id ident;
1014
1015 if (args.size() == 0)
1016 {
1017 roster_t new_roster;
1018 parent_map old_rosters;
1019 revision_t rev;
1020
1021 app.require_workspace();
1022 app.work.get_parent_rosters(old_rosters);
1023 app.work.get_current_roster_shape(new_roster, nis);
1024 app.work.update_current_roster_from_filesystem(new_roster);
1025
1026 make_revision(old_rosters, new_roster, rev);
1027 calculate_ident(rev, ident);
1028 write_revision(rev, dat);
1029 }
1030 else
1031 {
1032 ident = revision_id(idx(args, 0)());
1033 N(app.db.revision_exists(ident),
1034 F("no revision %s found in database") % ident);
1035 app.db.get_revision(ident, dat);
1036 }
1037
1038 L(FL("dumping revision %s") % ident);
1039 output.write(dat.inner()().data(), dat.inner()().size());
1040}
1041
1042// Name: get_base_revision_id
1043// Arguments: none
1044// Added in: 2.0
1045// Purpose: Prints the revision id the current workspace is based
1046// on. This is the value stored in _MTN/revision
1047// Error conditions: If no workspace book keeping _MTN directory is found,
1048// prints an error message to stderr, and exits with status 1.
1049CMD_AUTOMATE(get_base_revision_id, "",
1050 N_("Shows the revision on which the workspace is based"),
1051 "",
1052 options::opts::none)
1053{
1054 N(args.size() == 0,
1055 F("no arguments needed"));
1056
1057 app.require_workspace();
1058
1059 parent_map parents;
1060 app.work.get_parent_rosters(parents);
1061 N(parents.size() == 1,
1062 F("this command can only be used in a single-parent workspace"));
1063
1064 output << parent_id(parents.begin()) << '\n';
1065}
1066
1067// Name: get_current_revision_id
1068// Arguments: none
1069// Added in: 2.0
1070// Purpose: Prints the revision id of the current workspace. This is the
1071// id of the revision that would be committed by an unrestricted
1072// commit calculated from _MTN/revision, _MTN/work and any edits to
1073// files in the workspace.
1074// Error conditions: If no workspace book keeping _MTN directory is found,
1075// prints an error message to stderr, and exits with status 1.
1076CMD_AUTOMATE(get_current_revision_id, "",
1077 N_("Shows the revision of the current workspace"),
1078 "",
1079 options::opts::none)
1080{
1081 N(args.size() == 0,
1082 F("no arguments needed"));
1083
1084 app.require_workspace();
1085
1086 parent_map parents;
1087 roster_t new_roster;
1088 revision_id new_revision_id;
1089 revision_t rev;
1090 temp_node_id_source nis;
1091
1092 app.require_workspace();
1093 app.work.get_current_roster_shape(new_roster, nis);
1094 app.work.update_current_roster_from_filesystem(new_roster);
1095
1096 app.work.get_parent_rosters(parents);
1097 make_revision(parents, new_roster, rev);
1098
1099 calculate_ident(rev, new_revision_id);
1100
1101 output << new_revision_id << '\n';
1102}
1103
1104// Name: get_manifest_of
1105// Arguments:
1106// 1: a revision id (optional, determined from the workspace if not given)
1107// Added in: 2.0
1108// Purpose: Prints the contents of the manifest associated with the
1109// given revision ID.
1110//
1111// Output format:
1112// There is one basic_io stanza for each file or directory in the
1113// manifest.
1114//
1115// All stanzas are formatted by basic_io. Stanzas are separated
1116// by a blank line. Values will be escaped, '\' to '\\' and
1117// '"' to '\"'.
1118//
1119// Possible values of this first value are along with an ordered list of
1120// basic_io formatted stanzas that will be provided are:
1121//
1122// 'format_version'
1123// used in case this format ever needs to change.
1124// format: ('format_version', the string "1")
1125// occurs: exactly once
1126// 'dir':
1127// represents a directory. The path "" (the empty string) is used
1128// to represent the root of the tree.
1129// format: ('dir', pathname)
1130// occurs: one or more times
1131// 'file':
1132// represents a file.
1133// format: ('file', pathname), ('content', file id)
1134// occurs: zero or more times
1135//
1136// In addition, 'dir' and 'file' stanzas may have attr information
1137// included. These are appended to the stanza below the basic
1138// dir/file information, with one line describing each attr. These
1139// lines take the form ('attr', attr name, attr value).
1140//
1141// Stanzas are sorted by the path string.
1142//
1143// Error conditions: If the revision ID specified is unknown or
1144// invalid prints an error message to stderr and exits with status 1.
1145CMD_AUTOMATE(get_manifest_of, N_("[REVID]"),
1146 N_("Shows the manifest associated with a revision"),
1147 "",
1148 options::opts::none)
1149{
1150 N(args.size() < 2,
1151 F("wrong argument count"));
1152
1153 manifest_data dat;
1154 manifest_id mid;
1155 roster_t new_roster;
1156
1157 if (args.size() == 0)
1158 {
1159 temp_node_id_source nis;
1160
1161 app.require_workspace();
1162 app.work.get_current_roster_shape(new_roster, nis);
1163 app.work.update_current_roster_from_filesystem(new_roster);
1164 }
1165 else
1166 {
1167 revision_id rid = revision_id(idx(args, 0)());
1168 N(app.db.revision_exists(rid),
1169 F("no revision %s found in database") % rid);
1170 app.db.get_roster(rid, new_roster);
1171 }
1172
1173 calculate_ident(new_roster, mid);
1174 write_manifest_of_roster(new_roster, dat);
1175 L(FL("dumping manifest %s") % mid);
1176 output.write(dat.inner()().data(), dat.inner()().size());
1177}
1178
1179
1180// Name: packet_for_rdata
1181// Arguments:
1182// 1: a revision id
1183// Added in: 2.0
1184// Purpose: Prints the revision data in packet format
1185//
1186// Output format: revision data in "monotone read" compatible packet
1187// format
1188//
1189// Error conditions: If the revision id specified is unknown or
1190// invalid prints an error message to stderr and exits with status 1.
1191CMD_AUTOMATE(packet_for_rdata, N_("REVID"),
1192 N_("Prints the revision data in packet format"),
1193 "",
1194 options::opts::none)
1195{
1196 N(args.size() == 1,
1197 F("wrong argument count"));
1198
1199 packet_writer pw(output);
1200
1201 revision_id r_id(idx(args, 0)());
1202 revision_data r_data;
1203
1204 N(app.db.revision_exists(r_id),
1205 F("no such revision '%s'") % r_id);
1206 app.db.get_revision(r_id, r_data);
1207 pw.consume_revision_data(r_id,r_data);
1208}
1209
1210// Name: packets_for_certs
1211// Arguments:
1212// 1: a revision id
1213// Added in: 2.0
1214// Purpose: Prints the certs associated with a revision in packet format
1215//
1216// Output format: certs in "monotone read" compatible packet format
1217//
1218// Error conditions: If the revision id specified is unknown or
1219// invalid prints an error message to stderr and exits with status 1.
1220CMD_AUTOMATE(packets_for_certs, N_("REVID"),
1221 N_("Prints the certs associated with a revision in "
1222 "packet format"),
1223 "",
1224 options::opts::none)
1225{
1226 N(args.size() == 1,
1227 F("wrong argument count"));
1228
1229 packet_writer pw(output);
1230
1231 revision_id r_id(idx(args, 0)());
1232 vector< revision<cert> > certs;
1233
1234 N(app.db.revision_exists(r_id),
1235 F("no such revision '%s'") % r_id);
1236 app.get_project().get_revision_certs(r_id, certs);
1237 for (size_t i = 0; i < certs.size(); ++i)
1238 pw.consume_revision_cert(idx(certs,i));
1239}
1240
1241// Name: packet_for_fdata
1242// Arguments:
1243// 1: a file id
1244// Added in: 2.0
1245// Purpose: Prints the file data in packet format
1246//
1247// Output format: file data in "monotone read" compatible packet format
1248//
1249// Error conditions: If the file id specified is unknown or invalid
1250// prints an error message to stderr and exits with status 1.
1251CMD_AUTOMATE(packet_for_fdata, N_("FILEID"),
1252 N_("Prints the file data in packet format"),
1253 "",
1254 options::opts::none)
1255{
1256 N(args.size() == 1,
1257 F("wrong argument count"));
1258
1259 packet_writer pw(output);
1260
1261 file_id f_id(idx(args, 0)());
1262 file_data f_data;
1263
1264 N(app.db.file_version_exists(f_id),
1265 F("no such file '%s'") % f_id);
1266 app.db.get_file_version(f_id, f_data);
1267 pw.consume_file_data(f_id,f_data);
1268}
1269
1270// Name: packet_for_fdelta
1271// Arguments:
1272// 1: a file id
1273// 2: a file id
1274// Added in: 2.0
1275// Purpose: Prints the file delta in packet format
1276//
1277// Output format: file delta in "monotone read" compatible packet format
1278//
1279// Error conditions: If any of the file ids specified are unknown or
1280// invalid prints an error message to stderr and exits with status 1.
1281CMD_AUTOMATE(packet_for_fdelta, N_("OLD_FILE NEW_FILE"),
1282 N_("Prints the file delta in packet format"),
1283 "",
1284 options::opts::none)
1285{
1286 N(args.size() == 2,
1287 F("wrong argument count"));
1288
1289 packet_writer pw(output);
1290
1291 file_id f_old_id(idx(args, 0)());
1292 file_id f_new_id(idx(args, 1)());
1293 file_data f_old_data, f_new_data;
1294
1295 N(app.db.file_version_exists(f_old_id),
1296 F("no such revision '%s'") % f_old_id);
1297 N(app.db.file_version_exists(f_new_id),
1298 F("no such revision '%s'") % f_new_id);
1299 app.db.get_file_version(f_old_id, f_old_data);
1300 app.db.get_file_version(f_new_id, f_new_data);
1301 delta del;
1302 diff(f_old_data.inner(), f_new_data.inner(), del);
1303 pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
1304}
1305
1306// Name: common_ancestors
1307// Arguments:
1308// 1 or more revision ids
1309// Added in: 2.1
1310// Purpose: Prints all revisions which are ancestors of all of the
1311// revisions given as arguments.
1312// Output format: A list of revision ids, in hexadecimal, each
1313// followed by a newline. Revisions are printed in alphabetically
1314// sorted order.
1315// Error conditions: If any of the revisions do not exist, prints
1316// nothing to stdout, prints an error message to stderr, and exits
1317// with status 1.
1318CMD_AUTOMATE(common_ancestors, N_("REV1 [REV2 [REV3 [...]]]"),
1319 N_("Prints revisions that are common ancestors of a list "
1320 "of revisions"),
1321 "",
1322 options::opts::none)
1323{
1324 N(args.size() > 0,
1325 F("wrong argument count"));
1326
1327 set<revision_id> ancestors, common_ancestors;
1328 vector<revision_id> frontier;
1329 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
1330 {
1331 revision_id rid((*i)());
1332 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
1333 ancestors.clear();
1334 ancestors.insert(rid);
1335 frontier.push_back(rid);
1336 while (!frontier.empty())
1337 {
1338 revision_id rid = frontier.back();
1339 frontier.pop_back();
1340 if(!null_id(rid))
1341 {
1342 set<revision_id> parents;
1343 app.db.get_revision_parents(rid, parents);
1344 for (set<revision_id>::const_iterator i = parents.begin();
1345 i != parents.end(); ++i)
1346 {
1347 if (ancestors.find(*i) == ancestors.end())
1348 {
1349 frontier.push_back(*i);
1350 ancestors.insert(*i);
1351 }
1352 }
1353 }
1354 }
1355 if (common_ancestors.empty())
1356 common_ancestors = ancestors;
1357 else
1358 {
1359 set<revision_id> common;
1360 set_intersection(ancestors.begin(), ancestors.end(),
1361 common_ancestors.begin(), common_ancestors.end(),
1362 inserter(common, common.begin()));
1363 common_ancestors = common;
1364 }
1365 }
1366
1367 for (set<revision_id>::const_iterator i = common_ancestors.begin();
1368 i != common_ancestors.end(); ++i)
1369 if (!null_id(*i))
1370 output << (*i).inner()() << '\n';
1371}
1372
1373// Name: branches
1374// Arguments:
1375// None
1376// Added in: 2.2
1377// Purpose:
1378// Prints all branch certs present in the revision graph, that are not
1379// excluded by the lua hook 'ignore_branch'.
1380// Output format:
1381// Zero or more lines, each the name of a branch. The lines are printed
1382// in alphabetically sorted order.
1383// Error conditions:
1384// None.
1385CMD_AUTOMATE(branches, "",
1386 N_("Prints all branch certs in the revision graph"),
1387 "",
1388 options::opts::none)
1389{
1390 N(args.size() == 0,
1391 F("no arguments needed"));
1392
1393 set<branch_name> names;
1394
1395 app.get_project().get_branch_list(names);
1396
1397 for (set<branch_name>::const_iterator i = names.begin();
1398 i != names.end(); ++i)
1399 {
1400 if (!app.lua.hook_ignore_branch(*i))
1401 output << (*i) << '\n';
1402 }
1403}
1404
1405// Name: tags
1406// Arguments:
1407// A branch pattern (optional).
1408// Added in: 2.2
1409// Purpose:
1410// If a branch pattern is given, prints all tags that are attached to
1411// revisions on branches matched by the pattern; otherwise prints all tags
1412// of the revision graph.
1413//
1414// If a branch name is ignored by means of the lua hook 'ignore_branch',
1415// it is neither printed, nor can it be matched by a pattern.
1416// Output format:
1417// There is one basic_io stanza for each tag.
1418//
1419// All stanzas are formatted by basic_io. Stanzas are separated
1420// by a blank line. Values will be escaped, '\' to '\\' and
1421// '"' to '\"'.
1422//
1423// Each stanza has exactly the following four entries:
1424//
1425// 'tag'
1426// the value of the tag cert, i.e. the name of the tag
1427// 'revision'
1428// the hexadecimal id of the revision the tag is attached to
1429// 'signer'
1430// the name of the key used to sign the tag cert
1431// 'branches'
1432// a (possibly empty) list of all branches the tagged revision is on
1433//
1434// Stanzas are printed in arbitrary order.
1435// Error conditions:
1436// A run-time exception is thrown for illegal patterns.
1437CMD_AUTOMATE(tags, N_("[BRANCH_PATTERN]"),
1438 N_("Prints all tags attached to a set of branches"),
1439 "",
1440 options::opts::none)
1441{
1442 N(args.size() < 2,
1443 F("wrong argument count"));
1444
1445 globish incl("*");
1446 bool filtering(false);
1447
1448 if (args.size() == 1) {
1449 incl = globish(idx(args, 0)());
1450 filtering = true;
1451 }
1452
1453 basic_io::printer prt;
1454 basic_io::stanza stz;
1455 stz.push_str_pair(symbol("format_version"), "1");
1456 prt.print_stanza(stz);
1457
1458 set<tag_t> tags;
1459 app.get_project().get_tags(tags);
1460
1461 for (set<tag_t>::const_iterator tag = tags.begin();
1462 tag != tags.end(); ++tag)
1463 {
1464 set<branch_name> branches;
1465 app.get_project().get_revision_branches(tag->ident, branches);
1466
1467 bool show(!filtering);
1468 vector<string> branch_names;
1469
1470 for (set<branch_name>::const_iterator branch = branches.begin();
1471 branch != branches.end(); ++branch)
1472 {
1473 if (app.lua.hook_ignore_branch(*branch))
1474 continue;
1475
1476 if (!show && incl.matches((*branch)()))
1477 show = true;
1478 branch_names.push_back((*branch)());
1479 }
1480
1481 if (show)
1482 {
1483 basic_io::stanza stz;
1484 stz.push_str_pair(symbol("tag"), tag->name());
1485 stz.push_hex_pair(symbol("revision"), tag->ident.inner());
1486 stz.push_str_pair(symbol("signer"), tag->key());
1487 stz.push_str_multi(symbol("branches"), branch_names);
1488 prt.print_stanza(stz);
1489 }
1490 }
1491 output.write(prt.buf.data(), prt.buf.size());
1492}
1493
1494namespace
1495{
1496 namespace syms
1497 {
1498 symbol const key("key");
1499 symbol const signature("signature");
1500 symbol const name("name");
1501 symbol const value("value");
1502 symbol const trust("trust");
1503
1504 symbol const public_hash("public_hash");
1505 symbol const private_hash("private_hash");
1506 symbol const public_location("public_location");
1507 symbol const private_location("private_location");
1508 }
1509};
1510
1511// Name: genkey
1512// Arguments:
1513// 1: the key ID
1514// 2: the key passphrase
1515// Added in: 3.1
1516// Purpose: Generates a key with the given ID and passphrase
1517//
1518// Output format: a basic_io stanza for the new key, as for ls keys
1519//
1520// Sample output:
1521// name "tbrownaw@gmail.com"
1522// public_hash [475055ec71ad48f5dfaf875b0fea597b5cbbee64]
1523// private_hash [7f76dae3f91bb48f80f1871856d9d519770b7f8a]
1524// public_location "database" "keystore"
1525// private_location "keystore"
1526//
1527// Error conditions: If the passphrase is empty or the key already exists,
1528// prints an error message to stderr and exits with status 1.
1529CMD_AUTOMATE(genkey, N_("KEYID PASSPHRASE"),
1530 N_("Generates a key"),
1531 "",
1532 options::opts::none)
1533{
1534 N(args.size() == 2,
1535 F("wrong argument count"));
1536
1537 rsa_keypair_id ident;
1538 internalize_rsa_keypair_id(idx(args, 0), ident);
1539
1540 utf8 passphrase = idx(args, 1);
1541
1542 bool exists = app.keys.key_pair_exists(ident);
1543 if (app.db.database_specified())
1544 {
1545 transaction_guard guard(app.db);
1546 exists = exists || app.db.public_key_exists(ident);
1547 guard.commit();
1548 }
1549
1550 N(!exists, F("key '%s' already exists") % ident);
1551
1552 keypair kp;
1553 P(F("generating key-pair '%s'") % ident);
1554 generate_key_pair(kp, passphrase);
1555 P(F("storing key-pair '%s' in %s/")
1556 % ident % app.keys.get_key_dir());
1557 app.keys.put_key_pair(ident, kp);
1558
1559 basic_io::printer prt;
1560 basic_io::stanza stz;
1561 hexenc<id> privhash, pubhash;
1562 vector<string> publocs, privlocs;
1563 key_hash_code(ident, kp.pub, pubhash);
1564 key_hash_code(ident, kp.priv, privhash);
1565
1566 publocs.push_back("keystore");
1567 privlocs.push_back("keystore");
1568
1569 stz.push_str_pair(syms::name, ident());
1570 stz.push_hex_pair(syms::public_hash, pubhash);
1571 stz.push_hex_pair(syms::private_hash, privhash);
1572 stz.push_str_multi(syms::public_location, publocs);
1573 stz.push_str_multi(syms::private_location, privlocs);
1574 prt.print_stanza(stz);
1575
1576 output.write(prt.buf.data(), prt.buf.size());
1577
1578}
1579
1580// Name: get_option
1581// Arguments:
1582// 1: an options name
1583// Added in: 3.1
1584// Purpose: Show the value of the named option in _MTN/options
1585//
1586// Output format: A string
1587//
1588// Sample output (for 'mtn automate get_option branch:
1589// net.venge.monotone
1590//
1591CMD_AUTOMATE(get_option, N_("OPTION"),
1592 N_("Shows the value of an option"),
1593 "",
1594 options::opts::none)
1595{
1596 N(args.size() == 1,
1597 F("wrong argument count"));
1598
1599 // this command requires a workspace to be run on
1600 app.require_workspace();
1601
1602 system_path database_option;
1603 branch_name branch_option;
1604 rsa_keypair_id key_option;
1605 system_path keydir_option;
1606 app.work.get_ws_options(database_option, branch_option,
1607 key_option, keydir_option);
1608
1609 string opt = args[0]();
1610
1611 if (opt == "database")
1612 output << database_option << '\n';
1613 else if (opt == "branch")
1614 output << branch_option << '\n';
1615 else if (opt == "key")
1616 output << key_option << '\n';
1617 else if (opt == "keydir")
1618 output << keydir_option << '\n';
1619 else
1620 N(false, F("'%s' is not a recognized workspace option") % opt);
1621}
1622
1623// Name: get_content_changed
1624// Arguments:
1625// 1: a revision ID
1626// 2: a file name
1627// Added in: 3.1
1628// Purpose: Returns a list of revision IDs in which the content
1629// was most recently changed, relative to the revision ID specified
1630// in argument 1. This equates to a content mark following
1631// the *-merge algorithm.
1632//
1633// Output format: Zero or more basic_io stanzas, each specifying a
1634// revision ID for which a content mark is set.
1635//
1636// Each stanza has exactly one entry:
1637//
1638// 'content_mark'
1639// the hexadecimal id of the revision the content mark is attached to
1640// Sample output (for 'mtn automate get_content_changed 3bccff99d08421df72519b61a4dded16d1139c33 ChangeLog):
1641// content_mark [276264b0b3f1e70fc1835a700e6e61bdbe4c3f2f]
1642//
1643CMD_AUTOMATE(get_content_changed, N_("REV FILE"),
1644 N_("Lists the revisions that changed the content relative "
1645 "to another revision"),
1646 "",
1647 options::opts::none)
1648{
1649 N(args.size() == 2,
1650 F("wrong argument count"));
1651
1652 roster_t new_roster;
1653 revision_id ident;
1654 marking_map mm;
1655
1656 ident = revision_id(idx(args, 0)());
1657 N(app.db.revision_exists(ident),
1658 F("no revision %s found in database") % ident);
1659 app.db.get_roster(ident, new_roster, mm);
1660
1661 file_path path = file_path_external(idx(args,1));
1662 N(new_roster.has_node(path),
1663 F("file %s is unknown for revision %s") % path % ident);
1664
1665 node_t node = new_roster.get_node(path);
1666 marking_map::const_iterator m = mm.find(node->self);
1667 I(m != mm.end());
1668 marking_t mark = m->second;
1669
1670 basic_io::printer prt;
1671 for (set<revision_id>::const_iterator i = mark.file_content.begin();
1672 i != mark.file_content.end(); ++i)
1673 {
1674 basic_io::stanza st;
1675 st.push_hex_pair(basic_io::syms::content_mark, i->inner());
1676 prt.print_stanza(st);
1677 }
1678 output.write(prt.buf.data(), prt.buf.size());
1679}
1680
1681// Name: get_corresponding_path
1682// Arguments:
1683// 1: a source revision ID
1684// 2: a file name (in the source revision)
1685// 3: a target revision ID
1686// Added in: 3.1
1687// Purpose: Given a the file name in the source revision, a filename
1688// will if possible be returned naming the file in the target revision.
1689// This allows the same file to be matched between revisions, accounting
1690// for renames and other changes.
1691//
1692// Output format: Zero or one basic_io stanzas. Zero stanzas will be
1693// output if the file does not exist within the target revision; this is
1694// not considered an error.
1695// If the file does exist in the target revision, a single stanza with the
1696// following details is output.
1697//
1698// The stanza has exactly one entry:
1699//
1700// 'file'
1701// the file name corresponding to "file name" (arg 2) in the target revision
1702//
1703// Sample output (for automate get_corresponding_path 91f25c8ee830b11b52dd356c925161848d4274d0 foo2 dae0d8e3f944c82a9688bcd6af99f5b837b41968; see automate_get_corresponding_path test)
1704// file "foo"
1705CMD_AUTOMATE(get_corresponding_path, N_("REV1 FILE REV2"),
1706 N_("Prints the name of a file in a target revision relative "
1707 "to a given revision"),
1708 "",
1709 options::opts::none)
1710{
1711 N(args.size() == 3,
1712 F("wrong argument count"));
1713
1714 roster_t new_roster, old_roster;
1715 revision_id ident, old_ident;
1716
1717 ident = revision_id(idx(args, 0)());
1718 N(app.db.revision_exists(ident),
1719 F("no revision %s found in database") % ident);
1720 app.db.get_roster(ident, new_roster);
1721
1722 old_ident = revision_id(idx(args, 2)());
1723 N(app.db.revision_exists(old_ident),
1724 F("no revision %s found in database") % old_ident);
1725 app.db.get_roster(old_ident, old_roster);
1726
1727 file_path path = file_path_external(idx(args,1));
1728 N(new_roster.has_node(path),
1729 F("file %s is unknown for revision %s") % path % ident);
1730
1731 node_t node = new_roster.get_node(path);
1732 basic_io::printer prt;
1733 if (old_roster.has_node(node->self))
1734 {
1735 file_path old_path;
1736 basic_io::stanza st;
1737 old_roster.get_name(node->self, old_path);
1738 st.push_file_pair(basic_io::syms::file, old_path);
1739 prt.print_stanza(st);
1740 }
1741 output.write(prt.buf.data(), prt.buf.size());
1742}
1743
1744// Name: put_file
1745// Arguments:
1746// base FILEID (optional)
1747// file contents (binary, intended for automate stdio use)
1748// Added in: 4.1
1749// Purpose:
1750// Store a file in the database.
1751// Optionally encode it as a file_delta
1752// Output format:
1753// The ID of the new file (40 digit hex string)
1754// Error conditions:
1755// a runtime exception is thrown if base revision is not available
1756CMD_AUTOMATE(put_file, N_("[FILEID] CONTENTS"),
1757 N_("Stores a file in the database"),
1758 "",
1759 options::opts::none)
1760{
1761 N(args.size() == 1 || args.size() == 2,
1762 F("wrong argument count"));
1763
1764 file_id sha1sum;
1765 transaction_guard tr(app.db);
1766 if (args.size() == 1)
1767 {
1768 file_data dat(idx(args, 0)());
1769 calculate_ident(dat, sha1sum);
1770
1771 app.db.put_file(sha1sum, dat);
1772 }
1773 else if (args.size() == 2)
1774 {
1775 file_data dat(idx(args, 1)());
1776 calculate_ident(dat, sha1sum);
1777 file_id base_id(idx(args, 0)());
1778 N(app.db.file_version_exists(base_id),
1779 F("no file version %s found in database") % base_id);
1780
1781 // put_file_version won't do anything if the target ID already exists,
1782 // but we can save the delta calculation by checking here too
1783 if (!app.db.file_version_exists(sha1sum))
1784 {
1785 file_data olddat;
1786 app.db.get_file_version(base_id, olddat);
1787 delta del;
1788 diff(olddat.inner(), dat.inner(), del);
1789
1790 app.db.put_file_version(base_id, sha1sum, file_delta(del));
1791 }
1792 }
1793 else I(false);
1794
1795 tr.commit();
1796 output << sha1sum << '\n';
1797}
1798
1799// Name: put_revision
1800// Arguments:
1801// revision-data
1802// Added in: 4.1
1803// Purpose:
1804// Store a revision into the database.
1805// Output format:
1806// The ID of the new revision
1807// Error conditions:
1808// none
1809CMD_AUTOMATE(put_revision, N_("REVISION-DATA"),
1810 N_("Stores a revision into the database"),
1811 "",
1812 options::opts::none)
1813{
1814 N(args.size() == 1,
1815 F("wrong argument count"));
1816
1817 revision_t rev;
1818 read_revision(revision_data(idx(args, 0)()), rev);
1819
1820 // recalculate manifest
1821 temp_node_id_source nis;
1822 rev.new_manifest = manifest_id();
1823 for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e)
1824 {
1825 // calculate new manifest
1826 roster_t old_roster;
1827 if (!null_id(e->first)) app.db.get_roster(e->first, old_roster);
1828 roster_t new_roster = old_roster;
1829 editable_roster_base eros(new_roster, nis);
1830 e->second->apply_to(eros);
1831 if (null_id(rev.new_manifest))
1832 // first edge, initialize manifest
1833 calculate_ident(new_roster, rev.new_manifest);
1834 else
1835 // following edge, make sure that all csets end at the same manifest
1836 {
1837 manifest_id calculated;
1838 calculate_ident(new_roster, calculated);
1839 I(calculated == rev.new_manifest);
1840 }
1841 }
1842
1843 revision_id id;
1844 calculate_ident(rev, id);
1845
1846 // If the database refuses the revision, make sure this is because it's
1847 // already there.
1848 E(app.db.put_revision(id, rev) || app.db.revision_exists(id),
1849 F("missing prerequisite for revision %s") % id);
1850
1851 output << id << '\n';
1852}
1853
1854// Name: cert
1855// Arguments:
1856// revision ID
1857// certificate name
1858// certificate value
1859// Added in: 4.1
1860// Purpose:
1861// Add a revision certificate (like mtn cert).
1862// Output format:
1863// nothing
1864// Error conditions:
1865// none
1866CMD_AUTOMATE(cert, N_("REVISION-ID NAME VALUE"),
1867 N_("Adds a revision certificate"),
1868 "",
1869 options::opts::none)
1870{
1871 N(args.size() == 3,
1872 F("wrong argument count"));
1873
1874 cert c;
1875 revision_id rid(idx(args, 0)());
1876
1877 transaction_guard guard(app.db);
1878 N(app.db.revision_exists(rid),
1879 F("no such revision '%s'") % rid);
1880 make_simple_cert(rid.inner(), cert_name(idx(args, 1)()),
1881 cert_value(idx(args, 2)()), app, c);
1882 revision<cert> rc(c);
1883 app.db.put_revision_cert(rc);
1884 guard.commit();
1885}
1886
1887// Name: db_set
1888// Arguments:
1889// variable domain
1890// variable name
1891// veriable value
1892// Added in: 4.1
1893// Purpose:
1894// Set a database variable (like mtn database set)
1895// Output format:
1896// nothing
1897// Error conditions:
1898// none
1899CMD_AUTOMATE(db_set, N_("DOMAIN NAME VALUE"),
1900 N_("Sets a database variable"),
1901 "",
1902 options::opts::none)
1903{
1904 N(args.size() == 3,
1905 F("wrong argument count"));
1906
1907 var_domain domain = var_domain(idx(args, 0)());
1908 utf8 name = idx(args, 1);
1909 utf8 value = idx(args, 2);
1910 var_key key(domain, var_name(name()));
1911 app.db.set_var(key, var_value(value()));
1912}
1913
1914// Name: db_get
1915// Arguments:
1916// variable domain
1917// variable name
1918// Added in: 4.1
1919// Purpose:
1920// Get a database variable (like mtn database ls vars | grep NAME)
1921// Output format:
1922// variable value
1923// Error conditions:
1924// a runtime exception is thrown if the variable is not set
1925CMD_AUTOMATE(db_get, N_("DOMAIN NAME"),
1926 N_("Gets a database variable"),
1927 "",
1928 options::opts::none)
1929{
1930 N(args.size() == 2,
1931 F("wrong argument count"));
1932
1933 var_domain domain = var_domain(idx(args, 0)());
1934 utf8 name = idx(args, 1);
1935 var_key key(domain, var_name(name()));
1936 var_value value;
1937 try
1938 {
1939 app.db.get_var(key, value);
1940 }
1941 catch (std::logic_error)
1942 {
1943 N(false, F("variable not found"));
1944 }
1945 output << value();
1946}
1947
1948// Local Variables:
1949// mode: C++
1950// fill-column: 76
1951// c-file-style: "gnu"
1952// indent-tabs-mode: nil
1953// End:
1954// 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