monotone

monotone Mtn Source Tree

Root/src/automate.cc

1// Copyright (C) 2004 Nathaniel Smith <njs@pobox.com>
2// 2007, 2010 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 <iterator>
14#include <sstream>
15#include <unistd.h>
16#include "vector.hh"
17
18#include <boost/bind.hpp>
19#include <boost/function.hpp>
20#include "lexical_cast.hh"
21#include <boost/tuple/tuple.hpp>
22
23#include "app_state.hh"
24#include "automate_stdio_helpers.hh"
25#include "project.hh"
26#include "basic_io.hh"
27#include "cert.hh"
28#include "cmd.hh"
29#include "commands.hh"
30#include "constants.hh"
31#include "inodeprint.hh"
32#include "keys.hh"
33#include "key_store.hh"
34#include "file_io.hh"
35#include "options_applicator.hh"
36#include "packet.hh"
37#include "restrictions.hh"
38#include "revision.hh"
39#include "roster.hh"
40#include "transforms.hh"
41#include "simplestring_xform.hh"
42#include "vocab.hh"
43#include "globish.hh"
44#include "charset.hh"
45#include "safe_map.hh"
46#include "work.hh"
47#include "xdelta.hh"
48#include "database.hh"
49#include "vocab_cast.hh"
50
51using std::allocator;
52using std::basic_ios;
53using std::basic_stringbuf;
54using std::char_traits;
55using std::find;
56using std::inserter;
57using std::make_pair;
58using std::map;
59using std::multimap;
60using std::ostream;
61using std::ostringstream;
62using std::pair;
63using std::set;
64using std::sort;
65using std::streamsize;
66using std::string;
67using std::vector;
68
69
70// Name: heads
71// Arguments:
72// 1: branch name (optional, default branch is used if non-existant)
73// Added in: 0.0
74// Purpose: Prints the heads of the given branch.
75// Output format: A list of revision ids, in hexadecimal, each followed by a
76// newline. Revision ids are printed in alphabetically sorted order.
77// Error conditions: If the branch does not exist, prints nothing. (There are
78// no heads.)
79CMD_AUTOMATE(heads, N_("[BRANCH]"),
80 N_("Prints the heads of the given branch"),
81 "",
82 options::opts::none)
83{
84 E(args.size() < 2, origin::user,
85 F("wrong argument count"));
86
87 database db(app);
88 project_t project(db);
89
90 branch_name branch;
91 if (args.size() == 1)
92 // branchname was explicitly given, use that
93 branch = typecast_vocab<branch_name>(idx(args, 0));
94 else
95 {
96 workspace::require_workspace(F("with no argument, this command prints the heads of the workspace's branch"));
97 branch = app.opts.branch;
98 }
99
100 set<revision_id> heads;
101 project.get_branch_heads(branch, heads, app.opts.ignore_suspend_certs);
102 for (set<revision_id>::const_iterator i = heads.begin();
103 i != heads.end(); ++i)
104 output << *i << '\n';
105}
106
107// Name: ancestors
108// Arguments:
109// 1 or more: revision ids
110// Added in: 0.2
111// Purpose: Prints the ancestors (exclusive) of the given revisions
112// Output format: A list of revision ids, in hexadecimal, each followed by a
113// newline. Revision ids are printed in alphabetically sorted order.
114// Error conditions: If any of the revisions do not exist, prints nothing to
115// stdout, prints an error message to stderr, and exits with status 1.
116CMD_AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 [...]]]"),
117 N_("Prints the ancestors of the given revisions"),
118 "",
119 options::opts::none)
120{
121 E(args.size() > 0, origin::user,
122 F("wrong argument count"));
123
124 database db(app);
125
126 set<revision_id> ancestors;
127 vector<revision_id> frontier;
128 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
129 {
130 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
131 E(db.revision_exists(rid), origin::user,
132 F("no revision %s found in database") % rid);
133 frontier.push_back(rid);
134 }
135 while (!frontier.empty())
136 {
137 revision_id rid = frontier.back();
138 frontier.pop_back();
139 if(!null_id(rid)) {
140 set<revision_id> parents;
141 db.get_revision_parents(rid, parents);
142 for (set<revision_id>::const_iterator i = parents.begin();
143 i != parents.end(); ++i)
144 {
145 if (ancestors.find(*i) == ancestors.end())
146 {
147 frontier.push_back(*i);
148 ancestors.insert(*i);
149 }
150 }
151 }
152 }
153 for (set<revision_id>::const_iterator i = ancestors.begin();
154 i != ancestors.end(); ++i)
155 if (!null_id(*i))
156 output << *i << '\n';
157}
158
159
160// Name: descendents
161// Arguments:
162// 1 or more: revision ids
163// Added in: 0.1
164// Purpose: Prints the descendents (exclusive) of the given revisions
165// Output format: A list of revision ids, in hexadecimal, each followed by a
166// newline. Revision ids are printed in alphabetically sorted order.
167// Error conditions: If any of the revisions do not exist, prints nothing to
168// stdout, prints an error message to stderr, and exits with status 1.
169CMD_AUTOMATE(descendents, N_("REV1 [REV2 [REV3 [...]]]"),
170 N_("Prints the descendents of the given revisions"),
171 "",
172 options::opts::none)
173{
174 E(args.size() > 0, origin::user,
175 F("wrong argument count"));
176
177 database db(app);
178
179 set<revision_id> descendents;
180 vector<revision_id> frontier;
181 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
182 {
183 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
184 E(db.revision_exists(rid), origin::user,
185 F("no revision %s found in database") % rid);
186 frontier.push_back(rid);
187 }
188 while (!frontier.empty())
189 {
190 revision_id rid = frontier.back();
191 frontier.pop_back();
192 set<revision_id> children;
193 db.get_revision_children(rid, children);
194 for (set<revision_id>::const_iterator i = children.begin();
195 i != children.end(); ++i)
196 {
197 if (descendents.find(*i) == descendents.end())
198 {
199 frontier.push_back(*i);
200 descendents.insert(*i);
201 }
202 }
203 }
204 for (set<revision_id>::const_iterator i = descendents.begin();
205 i != descendents.end(); ++i)
206 output << *i << '\n';
207}
208
209
210// Name: erase_ancestors
211// Arguments:
212// 0 or more: revision ids
213// Added in: 0.1
214// Purpose: Prints all arguments, except those that are an ancestor of some
215// other argument. One way to think about this is that it prints the
216// minimal elements of the given set, under the ordering imposed by the
217// "child of" relation. Another way to think of it is if the arguments were
218// a branch, then we print the heads of that branch.
219// Output format: A list of revision ids, in hexadecimal, each followed by a
220// newline. Revision ids are printed in alphabetically sorted order.
221// Error conditions: If any of the revisions do not exist, prints nothing to
222// stdout, prints an error message to stderr, and exits with status 1.
223CMD_AUTOMATE(erase_ancestors, N_("[REV1 [REV2 [REV3 [...]]]]"),
224 N_("Erases the ancestors in a list of revisions"),
225 "",
226 options::opts::none)
227{
228 database db(app);
229
230 set<revision_id> revs;
231 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
232 {
233 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
234 E(db.revision_exists(rid), origin::user,
235 F("no revision %s found in database") % rid);
236 revs.insert(rid);
237 }
238 erase_ancestors(db, revs);
239 for (set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
240 output << *i << '\n';
241}
242
243// Name: erase_descendants
244// Arguments:
245// 0 or more: revision ids
246// Added in: 13.1
247// Purpose: Prints all arguments, except those that are a descendant of some
248// other argument. One way to think about this is that it prints the
249// minimal elements of the given set, under the ordering imposed by the
250// "parent of" relation. Another way to think of it is if the arguments were
251// a branch, then we print the roots of that branch.
252// Output format: A list of revision ids, in hexadecimal, each followed by a
253// newline. Revision ids are printed in alphabetically sorted order.
254// Error conditions: If any of the revisions do not exist, prints nothing to
255// stdout, prints an error message to stderr, and exits with status 1.
256CMD_AUTOMATE(erase_descendants, N_("[REV1 [REV2 [REV3 [...]]]]"),
257 N_("Erases the descendants in a list of revisions"),
258 "",
259 options::opts::none)
260{
261 database db(app);
262
263 set<revision_id> revs;
264 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
265 {
266 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
267 E(db.revision_exists(rid), origin::user,
268 F("no revision %s found in database") % rid);
269 revs.insert(rid);
270 }
271 erase_descendants(db, revs);
272 for (set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
273 output << *i << '\n';
274}
275
276// Name: toposort
277// Arguments:
278// 0 or more: revision ids
279// Added in: 0.1
280// Purpose: Prints all arguments, topologically sorted. I.e., if A is an
281// ancestor of B, then A will appear before B in the output list.
282// Output format: A list of revision ids, in hexadecimal, each followed by a
283// newline. Revisions are printed in topologically sorted order.
284// Error conditions: If any of the revisions do not exist, prints nothing to
285// stdout, prints an error message to stderr, and exits with status 1.
286CMD_AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 [...]]]]"),
287 N_("Topologically sorts a list of revisions"),
288 "",
289 options::opts::none)
290{
291 database db(app);
292
293 set<revision_id> revs;
294 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
295 {
296 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
297 E(db.revision_exists(rid), origin::user,
298 F("no revision %s found in database") % rid);
299 revs.insert(rid);
300 }
301 vector<revision_id> sorted;
302 toposort(db, revs, sorted);
303 for (vector<revision_id>::const_iterator i = sorted.begin();
304 i != sorted.end(); ++i)
305 output << *i << '\n';
306}
307
308// Name: ancestry_difference
309// Arguments:
310// 1: a revision id
311// 0 or more further arguments: also revision ids
312// Added in: 0.1
313// Purpose: Prints all ancestors of the first revision A, that are not also
314// ancestors of the other revision ids, the "Bs". For purposes of this
315// command, "ancestor" is an inclusive term; that is, A is an ancestor of
316// one of the Bs, it will not be printed, but otherwise, it will be; and
317// none of the Bs will ever be printed. If A is a new revision, and Bs are
318// revisions that you have processed before, then this command tells you
319// which revisions are new since then.
320// Output format: A list of revision ids, in hexadecimal, each followed by a
321// newline. Revisions are printed in topologically sorted order.
322// Error conditions: If any of the revisions do not exist, prints nothing to
323// stdout, prints an error message to stderr, and exits with status 1.
324CMD_AUTOMATE(ancestry_difference, N_("NEW_REV [OLD_REV1 [OLD_REV2 [...]]]"),
325 N_("Lists the ancestors of the first revision given, not in "
326 "the others"),
327 "",
328 options::opts::none)
329{
330 E(args.size() > 0, origin::user,
331 F("wrong argument count"));
332
333 database db(app);
334
335 revision_id a;
336 set<revision_id> bs;
337 args_vector::const_iterator i = args.begin();
338 a = decode_hexenc_as<revision_id>((*i)(), origin::user);
339 E(db.revision_exists(a), origin::user,
340 F("no revision %s found in database") % a);
341 for (++i; i != args.end(); ++i)
342 {
343 revision_id b(decode_hexenc_as<revision_id>((*i)(), origin::user));
344 E(db.revision_exists(b), origin::user,
345 F("no revision %s found in database") % b);
346 bs.insert(b);
347 }
348 set<revision_id> ancestors;
349 ancestry_difference(db, a, bs, ancestors);
350
351 vector<revision_id> sorted;
352 toposort(db, ancestors, sorted);
353 for (vector<revision_id>::const_iterator i = sorted.begin();
354 i != sorted.end(); ++i)
355 output << *i << '\n';
356}
357
358// Name: leaves
359// Arguments:
360// None
361// Added in: 0.1
362// Purpose: Prints the leaves of the revision graph, i.e., all revisions that
363// have no children. This is similar, but not identical to the
364// functionality of 'heads', which prints every revision in a branch, that
365// has no descendents in that branch. If every revision in the database was
366// in the same branch, then they would be identical. Generally, every leaf
367// is the head of some branch, but not every branch head is a leaf.
368// Output format: A list of revision ids, in hexadecimal, each followed by a
369// newline. Revision ids are printed in alphabetically sorted order.
370// Error conditions: None.
371CMD_AUTOMATE(leaves, "",
372 N_("Lists the leaves of the revision graph"),
373 "",
374 options::opts::none)
375{
376 E(args.size() == 0, origin::user,
377 F("no arguments needed"));
378
379 database db(app);
380
381 set<revision_id> leaves;
382 db.get_leaves(leaves);
383 for (set<revision_id>::const_iterator i = leaves.begin();
384 i != leaves.end(); ++i)
385 output << *i << '\n';
386}
387
388// Name: roots
389// Arguments:
390// None
391// Added in: 4.3
392// Purpose: Prints the roots of the revision graph, i.e. all revisions that
393// have no parents.
394// Output format: A list of revision ids, in hexadecimal, each followed by a
395// newline. Revision ids are printed in alphabetically sorted order.
396// Error conditions: None.
397CMD_AUTOMATE(roots, "",
398 N_("Lists the roots of the revision graph"),
399 "",
400 options::opts::none)
401{
402 E(args.size() == 0, origin::user,
403 F("no arguments needed"));
404
405 database db(app);
406
407 // the real root revisions are the children of one single imaginary root
408 // with an empty revision id
409 set<revision_id> roots;
410 revision_id nullid;
411 db.get_revision_children(nullid, roots);
412 for (set<revision_id>::const_iterator i = roots.begin();
413 i != roots.end(); ++i)
414 output << *i << '\n';
415}
416
417// Name: parents
418// Arguments:
419// 1: a revision id
420// Added in: 0.2
421// Purpose: Prints the immediate ancestors of the given revision, i.e., the
422// parents.
423// Output format: A list of revision ids, in hexadecimal, each followed by a
424// newline. Revision ids are printed in alphabetically sorted order.
425// Error conditions: If the revision does not exist, prints nothing to stdout,
426// prints an error message to stderr, and exits with status 1.
427CMD_AUTOMATE(parents, N_("REV"),
428 N_("Prints the parents of a revision"),
429 "",
430 options::opts::none)
431{
432 E(args.size() == 1, origin::user,
433 F("wrong argument count"));
434
435 database db(app);
436
437 revision_id rid(decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user));
438 E(db.revision_exists(rid), origin::user,
439 F("no revision %s found in database") % rid);
440 set<revision_id> parents;
441 db.get_revision_parents(rid, parents);
442 for (set<revision_id>::const_iterator i = parents.begin();
443 i != parents.end(); ++i)
444 if (!null_id(*i))
445 output << *i << '\n';
446}
447
448// Name: children
449// Arguments:
450// 1: a revision id
451// Added in: 0.2
452// Purpose: Prints the immediate descendents of the given revision, i.e., the
453// children.
454// Output format: A list of revision ids, in hexadecimal, each followed by a
455// newline. Revision ids are printed in alphabetically sorted order.
456// Error conditions: If the revision does not exist, prints nothing to stdout,
457// prints an error message to stderr, and exits with status 1.
458CMD_AUTOMATE(children, N_("REV"),
459 N_("Prints the children of a revision"),
460 "",
461 options::opts::none)
462{
463 E(args.size() == 1, origin::user,
464 F("wrong argument count"));
465
466 database db(app);
467
468 revision_id rid(decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user));
469 E(db.revision_exists(rid), origin::user,
470 F("no revision %s found in database") % rid);
471 set<revision_id> children;
472 db.get_revision_children(rid, children);
473 for (set<revision_id>::const_iterator i = children.begin();
474 i != children.end(); ++i)
475 if (!null_id(*i))
476 output << *i << '\n';
477}
478
479// Name: graph
480// Arguments:
481// None
482// Added in: 0.2
483// Purpose: Prints out the complete ancestry graph of this database.
484// Output format:
485// Each line begins with a revision id. Following this are zero or more
486// space-prefixed revision ids. Each revision id after the first is a
487// parent (in the sense of 'automate parents') of the first. For instance,
488// the following are valid lines:
489// 07804171823d963f78d6a0ff1763d694dd74ff40
490// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01
491// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01 a02e7a1390e3e4745c31be922f03f56450c13dce
492// The first would indicate that 07804171823d963f78d6a0ff1763d694dd74ff40
493// was a root node; the second would indicate that it had one parent, and
494// the third would indicate that it had two parents, i.e., was a merge.
495//
496// The output as a whole is alphabetically sorted; additionally, the parents
497// within each line are alphabetically sorted.
498// Error conditions: None.
499CMD_AUTOMATE(graph, "",
500 N_("Prints the complete ancestry graph"),
501 "",
502 options::opts::none)
503{
504 E(args.size() == 0, origin::user,
505 F("no arguments needed"));
506
507 database db(app);
508
509 multimap<revision_id, revision_id> edges_mmap;
510 map<revision_id, set<revision_id> > child_to_parents;
511
512 db.get_reverse_ancestry(edges_mmap);
513
514 for (multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
515 i != edges_mmap.end(); ++i)
516 {
517 if (child_to_parents.find(i->first) == child_to_parents.end())
518 child_to_parents.insert(make_pair(i->first, set<revision_id>()));
519 if (null_id(i->second))
520 continue;
521 map<revision_id, set<revision_id> >::iterator
522 j = child_to_parents.find(i->first);
523 I(j->first == i->first);
524 j->second.insert(i->second);
525 }
526
527 for (map<revision_id, set<revision_id> >::const_iterator
528 i = child_to_parents.begin();
529 i != child_to_parents.end(); ++i)
530 {
531 output << i->first;
532 for (set<revision_id>::const_iterator j = i->second.begin();
533 j != i->second.end(); ++j)
534 output << ' ' << *j;
535 output << '\n';
536 }
537}
538
539// Name: select
540// Arguments:
541// 1: selector
542// Added in: 0.2
543// Purpose: Prints all the revisions that match the given selector.
544// Output format: A list of revision ids, in hexadecimal, each followed by a
545// newline. Revision ids are printed in alphabetically sorted order.
546// Error conditions: None.
547CMD_AUTOMATE(select, N_("SELECTOR"),
548 N_("Lists the revisions that match a selector"),
549 "",
550 options::opts::none)
551{
552 E(args.size() == 1, origin::user,
553 F("wrong argument count"));
554
555 database db(app);
556 project_t project(db);
557 set<revision_id> completions;
558
559 // FIXME: replace this with
560 // complete(app.opts, app.lua, project, idx(args, 0)(), completions);
561 // some time which errors out if no completions could be found for a
562 // specific selector - this breaks BC with earlier automate versions though
563 expand_selector(app.opts, app.lua, project, idx(args, 0)(), completions);
564
565 for (set<revision_id>::const_iterator i = completions.begin();
566 i != completions.end(); ++i)
567 output << *i << '\n';
568}
569
570struct node_info
571{
572 bool exists;
573 // true if node_id is present in corresponding roster with the inventory map file_path
574 // false if not present, or present with a different file_path
575 // rest of data in this struct is invalid if false.
576 node_id id;
577 path::status type;
578 file_id ident;
579 attr_map_t attrs;
580
581 node_info() : exists(false), id(the_null_node), type(path::nonexistent) {}
582};
583
584static void
585get_node_info(const_node_t const & node, node_info & info)
586{
587 info.exists = true;
588 info.id = node->self;
589 info.attrs = node->attrs;
590 if (is_file_t(node))
591 {
592 info.type = path::file;
593 info.ident = downcast_to_file_t(node)->content;
594 }
595 else if (is_dir_t(node))
596 info.type = path::directory;
597 else
598 I(false);
599}
600
601struct inventory_item
602{
603 // Records information about a pair of nodes with the same node_id in the
604 // old roster and new roster, and the corresponding path in the
605 // filesystem.
606 node_info old_node;
607 node_info new_node;
608 file_path old_path;
609 file_path new_path;
610
611 path::status fs_type;
612 file_id fs_ident;
613
614 inventory_item() : fs_type(path::nonexistent) {}
615};
616
617typedef std::map<file_path, inventory_item> inventory_map;
618// file_path will typically be an existing filesystem file, but in the case
619// of a dropped or rename_source file it is only in the old roster, and in
620// the case of a file added --bookkeep_only or rename_target
621// --bookkeep_only, it is only in the new roster.
622
623static void
624inventory_rosters(roster_t const & old_roster,
625 roster_t const & new_roster,
626 node_restriction const & nmask,
627 path_restriction const & pmask,
628 inventory_map & inventory)
629{
630 std::map<int, file_path> old_paths;
631 std::map<int, file_path> new_paths;
632
633 node_map const & old_nodes = old_roster.all_nodes();
634 for (node_map::const_iterator i = old_nodes.begin(); i != old_nodes.end(); ++i)
635 {
636 if (nmask.includes(old_roster, i->first))
637 {
638 file_path fp;
639 old_roster.get_name(i->first, fp);
640 if (pmask.includes(fp))
641 {
642 get_node_info(old_roster.get_node(i->first), inventory[fp].old_node);
643 old_paths[inventory[fp].old_node.id] = fp;
644 }
645 }
646 }
647
648 node_map const & new_nodes = new_roster.all_nodes();
649 for (node_map::const_iterator i = new_nodes.begin(); i != new_nodes.end(); ++i)
650 {
651 if (nmask.includes(new_roster, i->first))
652 {
653 file_path fp;
654 new_roster.get_name(i->first, fp);
655 if (pmask.includes(fp))
656 {
657 get_node_info(new_roster.get_node(i->first), inventory[fp].new_node);
658 new_paths[inventory[fp].new_node.id] = fp;
659 }
660 }
661 }
662
663 std::map<int, file_path>::iterator i;
664 for (i = old_paths.begin(); i != old_paths.end(); ++i)
665 {
666 if (new_paths.find(i->first) == new_paths.end())
667 {
668 // There is no new node available; this is either a drop or a
669 // rename to outside the current path restriction.
670
671 if (new_roster.has_node(i->first))
672 {
673 // record rename to outside restriction
674 new_roster.get_name(i->first, inventory[i->second].new_path);
675 continue;
676 }
677 else
678 // drop; no new path
679 continue;
680 }
681
682 file_path old_path(i->second);
683 file_path new_path(new_paths[i->first]);
684
685 // both paths are identical, no rename
686 if (old_path == new_path)
687 continue;
688
689 // record rename
690 inventory[new_path].old_path = old_path;
691 inventory[old_path].new_path = new_path;
692 }
693
694 // Now look for new_paths that are renames from outside the current
695 // restriction, and thus are not in old_paths.
696 // FIXME: only need this if restriction is not null
697 for (i = new_paths.begin(); i != new_paths.end(); ++i)
698 {
699 if (old_paths.find(i->first) == old_paths.end())
700 {
701 // There is no old node available; this is either added or a
702 // rename from outside the current path restriction.
703
704 if (old_roster.has_node(i->first))
705 {
706 // record rename from outside restriction
707 old_roster.get_name(i->first, inventory[i->second].old_path);
708 }
709 else
710 // added; no old path
711 continue;
712 }
713 }
714}
715
716// check if the include/exclude paths contains paths to renamed nodes
717// if yes, add the corresponding old/new name of these nodes to the
718// paths as well, so the tree walker code will correctly identify them later
719// on or skips them if they should be excluded
720static void
721inventory_determine_corresponding_paths(roster_t const & old_roster,
722 roster_t const & new_roster,
723 vector<file_path> const & includes,
724 vector<file_path> const & excludes,
725 vector<file_path> & additional_includes,
726 vector<file_path> & additional_excludes)
727{
728 // at first check the includes vector
729 for (int i=0, s=includes.size(); i<s; i++)
730 {
731 file_path fp = includes.at(i);
732
733 if (old_roster.has_node(fp))
734 {
735 const_node_t node = old_roster.get_node(fp);
736 if (new_roster.has_node(node->self))
737 {
738 file_path new_path;
739 new_roster.get_name(node->self, new_path);
740 if (fp != new_path &&
741 find(includes.begin(), includes.end(), new_path) == includes.end())
742 {
743 additional_includes.push_back(new_path);
744 }
745 }
746 }
747
748 if (new_roster.has_node(fp))
749 {
750 const_node_t node = new_roster.get_node(fp);
751 if (old_roster.has_node(node->self))
752 {
753 file_path old_path;
754 old_roster.get_name(node->self, old_path);
755 if (fp != old_path &&
756 find(includes.begin(), includes.end(), old_path) == includes.end())
757 {
758 additional_includes.push_back(old_path);
759 }
760 }
761 }
762 }
763
764 // and now the excludes vector
765 vector<file_path> new_excludes;
766 for (int i=0, s=excludes.size(); i<s; i++)
767 {
768 file_path fp = excludes.at(i);
769
770 if (old_roster.has_node(fp))
771 {
772 const_node_t node = old_roster.get_node(fp);
773 if (new_roster.has_node(node->self))
774 {
775 file_path new_path;
776 new_roster.get_name(node->self, new_path);
777 if (fp != new_path &&
778 find(excludes.begin(), excludes.end(), new_path) == excludes.end())
779 {
780 additional_excludes.push_back(new_path);
781 }
782 }
783 }
784
785 if (new_roster.has_node(fp))
786 {
787 const_node_t node = new_roster.get_node(fp);
788 if (old_roster.has_node(node->self))
789 {
790 file_path old_path;
791 old_roster.get_name(node->self, old_path);
792 if (fp != old_path &&
793 find(excludes.begin(), excludes.end(), old_path) == excludes.end())
794 {
795 additional_excludes.push_back(old_path);
796 }
797 }
798 }
799 }
800}
801
802struct inventory_itemizer : public tree_walker
803{
804 path_restriction const & mask;
805 inventory_map & inventory;
806 inodeprint_map ipm;
807 workspace & work;
808
809 inventory_itemizer(workspace & work,
810 path_restriction const & m,
811 inventory_map & i)
812 : mask(m), inventory(i), work(work)
813 {
814 if (work.in_inodeprints_mode())
815 {
816 data dat;
817 work.read_inodeprints(dat);
818 read_inodeprint_map(dat, ipm);
819 }
820 }
821 virtual bool visit_dir(file_path const & path);
822 virtual void visit_file(file_path const & path);
823};
824
825bool
826inventory_itemizer::visit_dir(file_path const & path)
827{
828 if(mask.includes(path))
829 {
830 inventory[path].fs_type = path::directory;
831 }
832 // don't recurse into ignored subdirectories
833 return !work.ignore_file(path);
834}
835
836void
837inventory_itemizer::visit_file(file_path const & path)
838{
839 if (mask.includes(path))
840 {
841 inventory_item & item = inventory[path];
842
843 item.fs_type = path::file;
844
845 if (item.new_node.exists)
846 {
847 if (inodeprint_unchanged(ipm, path))
848 item.fs_ident = item.old_node.ident;
849 else
850 ident_existing_file(path, item.fs_ident);
851 }
852 }
853}
854
855static void
856inventory_filesystem(workspace & work,
857 path_restriction const & mask,
858 inventory_map & inventory)
859{
860 inventory_itemizer itemizer(work, mask, inventory);
861 file_path const root;
862 // The constructor file_path() returns ""; the root directory. walk_tree
863 // does not visit that node, so set fs_type now, if it meets the
864 // restriction.
865 if (mask.includes(root))
866 {
867 inventory[root].fs_type = path::directory;
868 }
869 walk_tree(root, itemizer);
870}
871
872namespace
873{
874 namespace syms
875 {
876 symbol const path("path");
877 symbol const old_type("old_type");
878 symbol const new_type("new_type");
879 symbol const fs_type("fs_type");
880 symbol const old_path("old_path");
881 symbol const new_path("new_path");
882 symbol const status("status");
883 symbol const birth("birth");
884 symbol const changes("changes");
885 }
886}
887
888static void
889inventory_determine_states(workspace & work, file_path const & fs_path,
890 inventory_item const & item, roster_t const & old_roster,
891 roster_t const & new_roster, vector<string> & states)
892{
893 // if both nodes exist, the only interesting case is
894 // when the node ids aren't equal (so we have different nodes
895 // with one and the same path in the old and the new roster)
896 if (item.old_node.exists &&
897 item.new_node.exists &&
898 item.old_node.id != item.new_node.id)
899 {
900 if (new_roster.has_node(item.old_node.id))
901 states.push_back("rename_source");
902 else
903 states.push_back("dropped");
904
905 if (old_roster.has_node(item.new_node.id))
906 states.push_back("rename_target");
907 else
908 states.push_back("added");
909 }
910 // this can be either a drop or a renamed item
911 else if (item.old_node.exists &&
912 !item.new_node.exists)
913 {
914 if (new_roster.has_node(item.old_node.id))
915 states.push_back("rename_source");
916 else
917 states.push_back("dropped");
918 }
919 // this can be either an add or a renamed item
920 else if (!item.old_node.exists &&
921 item.new_node.exists)
922 {
923 if (old_roster.has_node(item.new_node.id))
924 states.push_back("rename_target");
925 else
926 states.push_back("added");
927 }
928
929 // check the state of the file system item
930 if (item.fs_type == path::nonexistent)
931 {
932 if (item.new_node.exists)
933 {
934 states.push_back("missing");
935
936 // If this node is in a directory that is ignored in .mtn-ignore,
937 // we will output this warning. Note that we don't detect a known
938 // file that is ignored but not in an ignored directory.
939 if (work.ignore_file(fs_path))
940 W(F("'%s' is both known and ignored; "
941 "it will be shown as 'missing'. Check '.mtn-ignore'")
942 % fs_path);
943 }
944 }
945 else // exists on filesystem
946 {
947 if (!item.new_node.exists)
948 {
949 if (work.ignore_file(fs_path))
950 {
951 states.push_back("ignored");
952 }
953 else
954 {
955 states.push_back("unknown");
956 }
957 }
958 else if (item.new_node.type != item.fs_type)
959 {
960 states.push_back("invalid");
961 }
962 else
963 {
964 states.push_back("known");
965 }
966 }
967}
968
969static void
970inventory_determine_changes(inventory_item const & item, roster_t const & old_roster,
971 vector<string> & changes)
972{
973 // old nodes do not have any recorded content changes and attributes,
974 // so we can't print anything for them here
975 if (!item.new_node.exists)
976 return;
977
978 // this is an existing item
979 if (old_roster.has_node(item.new_node.id))
980 {
981 // check if the content has changed - this makes only sense for files
982 // for which we can get the content id of both new and old nodes.
983 if (item.new_node.type == path::file && item.fs_type != path::nonexistent)
984 {
985 const_file_t old_file = downcast_to_file_t(old_roster.get_node(item.new_node.id));
986
987 switch (item.old_node.type)
988 {
989 case path::file:
990 case path::nonexistent:
991 // A file can be nonexistent due to mtn drop, user delete, mtn
992 // rename, or user rename. If it was drop or delete, it would
993 // not be in the new roster, and we would not get here. So
994 // it's a rename, and we can get the content. This lets us
995 // check if a user has edited a file after renaming it.
996 if (item.fs_ident != old_file->content)
997 changes.push_back("content");
998 break;
999
1000 case path::directory:
1001 break;
1002 }
1003 }
1004
1005 // now look for changed attributes
1006 const_node_t old_node = old_roster.get_node(item.new_node.id);
1007 if (old_node->attrs != item.new_node.attrs)
1008 changes.push_back("attrs");
1009 }
1010 else
1011 {
1012 // FIXME: paranoia: shall we I(new_roster.has_node(item.new_node.id)) here?
1013
1014 // this is apparently a new item, if it is a file it gets at least
1015 // the "content" marker and we also check for recorded attributes
1016 if (item.new_node.type == path::file)
1017 changes.push_back("content");
1018
1019 if (!item.new_node.attrs.empty())
1020 changes.push_back("attrs");
1021 }
1022}
1023
1024static revision_id
1025inventory_determine_birth(inventory_item const & item,
1026 roster_t const & old_roster,
1027 marking_map const & old_marking)
1028{
1029 revision_id rid;
1030 if (old_roster.has_node(item.new_node.id))
1031 {
1032 const_node_t node = old_roster.get_node(item.new_node.id);
1033 const_marking_t const & mark = old_marking.get_marking(node->self);
1034 rid = mark->birth_revision;
1035 }
1036 return rid;
1037}
1038
1039// Name: inventory
1040// Arguments: [PATH]...
1041// Added in: 1.0
1042// Modified to basic_io in: 4.1
1043
1044// Purpose: Prints a summary of every file or directory found in the
1045// workspace or its associated base manifest.
1046
1047// See monotone.texi for output format description.
1048//
1049// Error conditions: If no workspace book keeping _MTN directory is found,
1050// prints an error message to stderr, and exits with status 1.
1051
1052CMD_AUTOMATE(inventory, N_("[PATH]..."),
1053 N_("Prints a summary of files found in the workspace"),
1054 "",
1055 options::opts::depth |
1056 options::opts::exclude |
1057 options::opts::automate_inventory_opts)
1058{
1059 database db(app);
1060 workspace work(app);
1061
1062 parent_map parents;
1063 work.get_parent_rosters(db, parents);
1064 // for now, until we've figured out what the format could look like
1065 // and what conceptional model we can implement
1066 // see: http://wiki.monotone.ca/MultiParentWorkspaceFallout/
1067 E(parents.size() == 1, origin::user,
1068 F("this command can only be used in a single-parent workspace"));
1069
1070 roster_t new_roster, old_roster = parent_roster(parents.begin());
1071 marking_map old_marking = parent_marking(parents.begin());
1072 temp_node_id_source nis;
1073
1074 work.get_current_roster_shape(db, nis, new_roster);
1075
1076 inventory_map inventory;
1077 vector<file_path> includes = args_to_paths(args);
1078 vector<file_path> excludes = args_to_paths(app.opts.exclude);
1079
1080 if (!app.opts.no_corresponding_renames)
1081 {
1082 vector<file_path> add_includes, add_excludes;
1083 inventory_determine_corresponding_paths(old_roster, new_roster,
1084 includes, excludes,
1085 add_includes, add_excludes);
1086
1087 copy(add_includes.begin(), add_includes.end(),
1088 inserter(includes, includes.end()));
1089
1090 copy(add_excludes.begin(), add_excludes.end(),
1091 inserter(excludes, excludes.end()));
1092 }
1093
1094 node_restriction nmask(includes, excludes, app.opts.depth,
1095 old_roster, new_roster, ignored_file(work));
1096 // skip the check of the workspace paths because some of them might
1097 // be missing and the user might want to query the recorded structure
1098 // of them anyways
1099 path_restriction pmask(includes, excludes, app.opts.depth,
1100 path_restriction::skip_check);
1101
1102 inventory_rosters(old_roster, new_roster, nmask, pmask, inventory);
1103 inventory_filesystem(work, pmask, inventory);
1104
1105 basic_io::printer pr;
1106
1107 for (inventory_map::const_iterator i = inventory.begin(); i != inventory.end();
1108 ++i)
1109 {
1110 file_path const & fp = i->first;
1111 inventory_item const & item = i->second;
1112
1113 //
1114 // check if we should output this element at all
1115 //
1116 vector<string> states;
1117 inventory_determine_states(work, fp, item,
1118 old_roster, new_roster, states);
1119
1120 if (find(states.begin(), states.end(), "ignored") != states.end() &&
1121 app.opts.no_ignored)
1122 continue;
1123
1124 if (find(states.begin(), states.end(), "unknown") != states.end() &&
1125 app.opts.no_unknown)
1126 continue;
1127
1128 vector<string> changes;
1129 inventory_determine_changes(item, old_roster, changes);
1130
1131 revision_id birth_revision =
1132 inventory_determine_birth(item, old_roster, old_marking);
1133
1134 bool is_tracked =
1135 find(states.begin(), states.end(), "unknown") == states.end() &&
1136 find(states.begin(), states.end(), "ignored") == states.end();
1137
1138 bool has_changed =
1139 find(states.begin(), states.end(), "rename_source") != states.end() ||
1140 find(states.begin(), states.end(), "rename_target") != states.end() ||
1141 find(states.begin(), states.end(), "added") != states.end() ||
1142 find(states.begin(), states.end(), "dropped") != states.end() ||
1143 find(states.begin(), states.end(), "missing") != states.end() ||
1144 !changes.empty();
1145
1146 if (is_tracked && !has_changed && app.opts.no_unchanged)
1147 continue;
1148
1149 //
1150 // begin building the output stanza
1151 //
1152 basic_io::stanza st;
1153 st.push_file_pair(syms::path, fp);
1154
1155 if (item.old_node.exists)
1156 {
1157 switch (item.old_node.type)
1158 {
1159 case path::file: st.push_str_pair(syms::old_type, "file"); break;
1160 case path::directory: st.push_str_pair(syms::old_type, "directory"); break;
1161 case path::nonexistent: I(false);
1162 }
1163
1164 if (item.new_path.as_internal().length() > 0)
1165 {
1166 st.push_file_pair(syms::new_path, item.new_path);
1167 }
1168 }
1169
1170 if (item.new_node.exists)
1171 {
1172 switch (item.new_node.type)
1173 {
1174 case path::file: st.push_str_pair(syms::new_type, "file"); break;
1175 case path::directory: st.push_str_pair(syms::new_type, "directory"); break;
1176 case path::nonexistent: I(false);
1177 }
1178
1179 if (item.old_path.as_internal().length() > 0)
1180 {
1181 st.push_file_pair(syms::old_path, item.old_path);
1182 }
1183 }
1184
1185 switch (item.fs_type)
1186 {
1187 case path::file: st.push_str_pair(syms::fs_type, "file"); break;
1188 case path::directory: st.push_str_pair(syms::fs_type, "directory"); break;
1189 case path::nonexistent: st.push_str_pair(syms::fs_type, "none"); break;
1190 }
1191
1192 //
1193 // finally output the previously recorded states and changes
1194 //
1195 if (!birth_revision.inner()().empty())
1196 st.push_binary_pair(syms::birth, birth_revision.inner());
1197
1198 I(!states.empty());
1199 st.push_str_multi(syms::status, states);
1200
1201 if (!changes.empty())
1202 st.push_str_multi(syms::changes, changes);
1203
1204 pr.print_stanza(st);
1205 }
1206
1207 output.write(pr.buf.data(), pr.buf.size());
1208}
1209
1210// Name: get_revision
1211// Arguments:
1212// 1: a revision id
1213// Added in: 1.0
1214// Changed in: 7.0 (REVID argument is now mandatory)
1215
1216// Purpose: Prints change information for the specified revision id.
1217// There are several changes that are described; each of these is
1218// described by a different basic_io stanza. The first string pair
1219// of each stanza indicates the type of change represented.
1220//
1221// All stanzas are formatted by basic_io. Stanzas are separated
1222// by a blank line. Values will be escaped, '\' to '\\' and
1223// '"' to '\"'.
1224//
1225// Possible values of this first value are along with an ordered list of
1226// basic_io formatted stanzas that will be provided are:
1227//
1228// 'format_version'
1229// used in case this format ever needs to change.
1230// format: ('format_version', the string "1")
1231// occurs: exactly once
1232// 'new_manifest'
1233// represents the new manifest associated with the revision.
1234// format: ('new_manifest', manifest id)
1235// occurs: exactly one
1236// 'old_revision'
1237// represents a parent revision.
1238// format: ('old_revision', revision id)
1239// occurs: either one or two times
1240// 'delete
1241// represents a file or directory that was deleted.
1242// format: ('delete', path)
1243// occurs: zero or more times
1244// 'rename'
1245// represents a file or directory that was renamed.
1246// format: ('rename, old filename), ('to', new filename)
1247// occurs: zero or more times
1248// 'add_dir'
1249// represents a directory that was added.
1250// format: ('add_dir, path)
1251// occurs: zero or more times
1252// 'add_file'
1253// represents a file that was added.
1254// format: ('add_file', path), ('content', file id)
1255// occurs: zero or more times
1256// 'patch'
1257// represents a file that was modified.
1258// format: ('patch', filename), ('from', file id), ('to', file id)
1259// occurs: zero or more times
1260// 'clear'
1261// represents an attr that was removed.
1262// format: ('clear', filename), ('attr', attr name)
1263// occurs: zero or more times
1264// 'set'
1265// represents an attr whose value was changed.
1266// format: ('set', filename), ('attr', attr name), ('value', attr value)
1267// occurs: zero or more times
1268//
1269// These stanzas will always occur in the order listed here; stanzas of
1270// the same type will be sorted by the filename they refer to.
1271// Error conditions: If the revision specified is unknown or invalid
1272// prints an error message to stderr and exits with status 1.
1273CMD_AUTOMATE(get_revision, N_("REVID"),
1274 N_("Shows change information for a revision"),
1275 "",
1276 options::opts::none)
1277{
1278 E(args.size() == 1, origin::user,
1279 F("wrong argument count"));
1280
1281 database db(app);
1282
1283 revision_data dat;
1284 revision_id rid(decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user));
1285 E(db.revision_exists(rid), origin::user,
1286 F("no revision %s found in database") % rid);
1287 db.get_revision(rid, dat);
1288
1289 L(FL("dumping revision %s") % rid);
1290 output << dat;
1291}
1292
1293// Name: get_current_revision
1294// Arguments:
1295// 1: zero or more path names
1296// Added in: 7.0
1297// Purpose: Outputs (an optionally restricted) revision based on
1298// changes in the current workspace
1299// Error conditions: If the restriction is invalid, prints an error message
1300// to stderr and exits with status 1. A workspace is required.
1301CMD_AUTOMATE(get_current_revision, N_("[PATHS ...]"),
1302 N_("Shows change information for a workspace"),
1303 "",
1304 options::opts::exclude | options::opts::depth)
1305{
1306 temp_node_id_source nis;
1307 revision_data dat;
1308 revision_id ident;
1309
1310 roster_t new_roster;
1311 parent_map old_rosters;
1312 revision_t rev;
1313 cset excluded;
1314
1315 database db(app);
1316 workspace work(app);
1317 work.get_parent_rosters(db, old_rosters);
1318 work.get_current_roster_shape(db, nis, new_roster);
1319
1320 node_restriction mask(args_to_paths(args),
1321 args_to_paths(app.opts.exclude),
1322 app.opts.depth,
1323 old_rosters, new_roster);
1324
1325 work.update_current_roster_from_filesystem(new_roster, mask);
1326
1327 make_revision(old_rosters, new_roster, rev);
1328 make_restricted_revision(old_rosters, new_roster, mask, rev,
1329 excluded, join_words(execid));
1330 rev.check_sane();
1331
1332 calculate_ident(rev, ident);
1333 write_revision(rev, dat);
1334
1335 L(FL("dumping revision %s") % ident);
1336 output << dat;
1337}
1338
1339
1340// Name: get_base_revision_id
1341// Arguments: none
1342// Added in: 2.0
1343// Purpose: Prints the revision id the current workspace is based
1344// on. This is the value stored in _MTN/revision
1345// Error conditions: If no workspace book keeping _MTN directory is found,
1346// prints an error message to stderr, and exits with status 1.
1347CMD_AUTOMATE(get_base_revision_id, "",
1348 N_("Shows the revision on which the workspace is based"),
1349 "",
1350 options::opts::none)
1351{
1352 E(args.size() == 0, origin::user,
1353 F("no arguments needed"));
1354
1355 workspace work(app);
1356 revision_t rev;
1357
1358 work.get_work_rev(rev);
1359
1360 E(rev.edges.size() == 1, origin::user,
1361 F("this command can only be used in a single-parent workspace"));
1362
1363 output << rev.edges.begin()->first << '\n';
1364}
1365
1366// Name: get_current_revision_id
1367// Arguments: none
1368// Added in: 2.0
1369// Purpose: Prints the revision id of the current workspace. This is the
1370// id of the revision that would be committed by an unrestricted
1371// commit calculated from _MTN/revision, _MTN/work and any edits to
1372// files in the workspace.
1373// Error conditions: If no workspace book keeping _MTN directory is found,
1374// prints an error message to stderr, and exits with status 1.
1375CMD_AUTOMATE(get_current_revision_id, "",
1376 N_("Shows the revision of the current workspace"),
1377 "",
1378 options::opts::none)
1379{
1380 E(args.size() == 0, origin::user,
1381 F("no arguments needed"));
1382
1383 workspace work(app);
1384 database db(app);
1385
1386 parent_map parents;
1387 roster_t new_roster;
1388 revision_id new_revision_id;
1389 revision_t rev;
1390 temp_node_id_source nis;
1391
1392 work.get_current_roster_shape(db, nis, new_roster);
1393 work.update_current_roster_from_filesystem(new_roster);
1394
1395 work.get_parent_rosters(db, parents);
1396 make_revision(parents, new_roster, rev);
1397
1398 calculate_ident(rev, new_revision_id);
1399
1400 output << new_revision_id << '\n';
1401}
1402
1403// Name: get_manifest_of
1404// Arguments:
1405// 1: a revision id (optional, determined from the workspace if not given)
1406// Added in: 2.0
1407// Purpose: Prints the contents of the manifest associated with the
1408// given revision ID.
1409//
1410// Output format:
1411// There is one basic_io stanza for each file or directory in the
1412// manifest.
1413//
1414// All stanzas are formatted by basic_io. Stanzas are separated
1415// by a blank line. Values will be escaped, '\' to '\\' and
1416// '"' to '\"'.
1417//
1418// Possible values of this first value are along with an ordered list of
1419// basic_io formatted stanzas that will be provided are:
1420//
1421// 'format_version'
1422// used in case this format ever needs to change.
1423// format: ('format_version', the string "1")
1424// occurs: exactly once
1425// 'dir':
1426// represents a directory. The path "" (the empty string) is used
1427// to represent the root of the tree.
1428// format: ('dir', pathname)
1429// occurs: one or more times
1430// 'file':
1431// represents a file.
1432// format: ('file', pathname), ('content', file id)
1433// occurs: zero or more times
1434//
1435// In addition, 'dir' and 'file' stanzas may have attr information
1436// included. These are appended to the stanza below the basic
1437// dir/file information, with one line describing each attr. These
1438// lines take the form ('attr', attr name, attr value).
1439//
1440// Stanzas are sorted by the path string.
1441//
1442// Error conditions: If the revision ID specified is unknown or
1443// invalid prints an error message to stderr and exits with status 1.
1444CMD_AUTOMATE(get_manifest_of, N_("[REVID]"),
1445 N_("Shows the manifest associated with a revision"),
1446 "",
1447 options::opts::none)
1448{
1449 database db(app);
1450
1451 E(args.size() < 2, origin::user,
1452 F("wrong argument count"));
1453
1454 manifest_data dat;
1455 manifest_id mid;
1456 roster_t new_roster;
1457
1458 if (args.size() == 0)
1459 {
1460 workspace work(app);
1461
1462 temp_node_id_source nis;
1463
1464 work.get_current_roster_shape(db, nis, new_roster);
1465 work.update_current_roster_from_filesystem(new_roster);
1466 }
1467 else
1468 {
1469 revision_id rid = decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user);
1470 E(db.revision_exists(rid), origin::user,
1471 F("no revision %s found in database") % rid);
1472 db.get_roster(rid, new_roster);
1473 }
1474
1475 calculate_ident(new_roster, mid);
1476 write_manifest_of_roster(new_roster, dat);
1477 L(FL("dumping manifest %s") % mid);
1478 output << dat;
1479}
1480
1481namespace
1482{
1483 namespace syms
1484 {
1485 symbol const dormant_attr("dormant_attr");
1486 symbol const path_mark("path_mark");
1487 symbol const content_mark("content_mark");
1488 symbol const attr_mark("attr_mark");
1489 }
1490};
1491
1492static void
1493print_extended_manifest(roster_t const & roster, marking_map const & mm,
1494 map<file_id, file_size> const & file_sizes, data & dat)
1495{
1496 basic_io::printer pr;
1497
1498 for (dfs_iter i(roster.root(), true); !i.finished(); ++i)
1499 {
1500 const_node_t curr = *i;
1501 basic_io::stanza st;
1502
1503 if (is_dir_t(curr))
1504 {
1505 st.push_str_pair(basic_io::syms::dir, i.path());
1506 }
1507 else
1508 {
1509 const_file_t ftmp = downcast_to_file_t(curr);
1510 st.push_str_pair(basic_io::syms::file, i.path());
1511 st.push_binary_pair(basic_io::syms::content, ftmp->content.inner());
1512
1513 map<file_id, file_size>::const_iterator s = file_sizes.find(ftmp->content);
1514 I(s != file_sizes.end());
1515 st.push_str_pair(basic_io::syms::size,
1516 boost::lexical_cast<string>(s->second));
1517 }
1518
1519 // Push the non-dormant part of the attr map
1520 for (attr_map_t::const_iterator j = curr->attrs.begin();
1521 j != curr->attrs.end(); ++j)
1522 {
1523 if (!j->second.first)
1524 continue;
1525 st.push_str_triple(basic_io::syms::attr, j->first(), j->second.second());
1526 }
1527
1528 // Push the dormant part of the attr map
1529 for (attr_map_t::const_iterator j = curr->attrs.begin();
1530 j != curr->attrs.end(); ++j)
1531 {
1532 if (j->second.first)
1533 continue;
1534
1535 I(j->second.second().empty());
1536 st.push_str_pair(syms::dormant_attr, j->first());
1537 }
1538
1539 const_marking_t mark = mm.get_marking(curr->self);
1540 I(!null_id(mark->birth_revision));
1541
1542 st.push_binary_pair(syms::birth, mark->birth_revision.inner());
1543
1544 for (set<revision_id>::const_iterator j = mark->parent_name.begin();
1545 j != mark->parent_name.end(); ++j)
1546 {
1547 st.push_binary_pair(syms::path_mark, (*j).inner());
1548 }
1549
1550 if (is_file_t(curr))
1551 {
1552 for (set<revision_id>::const_iterator j = mark->file_content.begin();
1553 j != mark->file_content.end(); ++j)
1554 {
1555 st.push_binary_pair(syms::content_mark, (*j).inner());
1556 }
1557 }
1558 else
1559 I(mark->file_content.empty());
1560
1561 for (map<attr_key, set<revision_id> >::const_iterator j = mark->attrs.begin();
1562 j != mark->attrs.end(); ++j)
1563 {
1564 for (set<revision_id>::const_iterator k = j->second.begin();
1565 k != j->second.end(); ++k)
1566 {
1567 st.push_binary_triple(syms::attr_mark, j->first(), (*k).inner());
1568 }
1569 }
1570
1571 pr.print_stanza(st);
1572 }
1573
1574 dat = data(pr.buf.data(), origin::database);
1575}
1576
1577// Name: get_extended_manifest_of
1578// Arguments: none
1579// Added in: 13.0
1580// Purpose: Prints the extended manifest for the given identifier
1581// Output format: basicio
1582// Error conditions: if the revision does not exist, prints an error and exits
1583CMD_AUTOMATE(get_extended_manifest_of, "REVISION",
1584 N_("Prints the extended manifest for the given identifier"),
1585 "",
1586 options::opts::none)
1587{
1588 E(args.size() == 1, origin::user,
1589 F("wrong argument count"));
1590
1591 database db(app);
1592 roster_t roster;
1593 marking_map mm;
1594
1595 revision_id rid = decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user);
1596 E(db.revision_exists(rid), origin::user,
1597 F("no revision %s found in database") % rid);
1598 db.get_roster(rid, roster, mm);
1599
1600 map<file_id, file_size> file_sizes;
1601 db.get_file_sizes(roster, file_sizes);
1602
1603 data dat;
1604 print_extended_manifest(roster, mm, file_sizes, dat);
1605 output << dat;
1606}
1607
1608// Name: packet_for_rdata
1609// Arguments:
1610// 1: a revision id
1611// Added in: 2.0
1612// Purpose: Prints the revision data in packet format
1613//
1614// Output format: revision data in "monotone read" compatible packet
1615// format
1616//
1617// Error conditions: If the revision id specified is unknown or
1618// invalid prints an error message to stderr and exits with status 1.
1619CMD_AUTOMATE(packet_for_rdata, N_("REVID"),
1620 N_("Prints the revision data in packet format"),
1621 "",
1622 options::opts::none)
1623{
1624 E(args.size() == 1, origin::user,
1625 F("wrong argument count"));
1626
1627 database db(app);
1628
1629 packet_writer pw(output);
1630
1631 revision_id r_id(decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user));
1632 revision_data r_data;
1633
1634 E(db.revision_exists(r_id), origin::user,
1635 F("no revision %s found in database") % r_id);
1636 db.get_revision(r_id, r_data);
1637 pw.consume_revision_data(r_id, r_data);
1638}
1639
1640// Name: packets_for_certs
1641// Arguments:
1642// 1: a revision id
1643// Added in: 2.0
1644// Purpose: Prints the certs associated with a revision in packet format
1645//
1646// Output format: certs in "monotone read" compatible packet format
1647//
1648// Error conditions: If the revision id specified is unknown or
1649// invalid prints an error message to stderr and exits with status 1.
1650CMD_AUTOMATE(packets_for_certs, N_("REVID"),
1651 N_("Prints the certs associated with a revision in "
1652 "packet format"),
1653 "",
1654 options::opts::none)
1655{
1656 E(args.size() == 1, origin::user,
1657 F("wrong argument count"));
1658
1659 database db(app);
1660 project_t project(db);
1661 packet_writer pw(output);
1662
1663 revision_id r_id(decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user));
1664 vector<cert> certs;
1665
1666 E(db.revision_exists(r_id), origin::user,
1667 F("no revision %s found in database") % r_id);
1668 project.get_revision_certs(r_id, certs);
1669
1670 for (vector<cert>::const_iterator i = certs.begin();
1671 i != certs.end(); i++)
1672 pw.consume_revision_cert(*i);
1673}
1674
1675// Name: packet_for_fdata
1676// Arguments:
1677// 1: a file id
1678// Added in: 2.0
1679// Purpose: Prints the file data in packet format
1680//
1681// Output format: file data in "monotone read" compatible packet format
1682//
1683// Error conditions: If the file id specified is unknown or invalid
1684// prints an error message to stderr and exits with status 1.
1685CMD_AUTOMATE(packet_for_fdata, N_("FILEID"),
1686 N_("Prints the file data in packet format"),
1687 "",
1688 options::opts::none)
1689{
1690 E(args.size() == 1, origin::user,
1691 F("wrong argument count"));
1692
1693 database db(app);
1694
1695 packet_writer pw(output);
1696
1697 file_id f_id(decode_hexenc_as<file_id>(idx(args, 0)(), origin::user));
1698 file_data f_data;
1699
1700 E(db.file_version_exists(f_id), origin::user,
1701 F("no such file '%s'") % f_id);
1702 db.get_file_version(f_id, f_data);
1703 pw.consume_file_data(f_id, f_data);
1704}
1705
1706// Name: packet_for_fdelta
1707// Arguments:
1708// 1: a file id
1709// 2: a file id
1710// Added in: 2.0
1711// Purpose: Prints the file delta in packet format
1712//
1713// Output format: file delta in "monotone read" compatible packet format
1714//
1715// Error conditions: If any of the file ids specified are unknown or
1716// invalid prints an error message to stderr and exits with status 1.
1717CMD_AUTOMATE(packet_for_fdelta, N_("OLD_FILE NEW_FILE"),
1718 N_("Prints the file delta in packet format"),
1719 "",
1720 options::opts::none)
1721{
1722 E(args.size() == 2, origin::user,
1723 F("wrong argument count"));
1724
1725 database db(app);
1726
1727 packet_writer pw(output);
1728
1729 file_id f_old_id(decode_hexenc_as<file_id>(idx(args, 0)(), origin::user));
1730 file_id f_new_id(decode_hexenc_as<file_id>(idx(args, 1)(), origin::user));
1731 file_data f_old_data, f_new_data;
1732
1733 E(db.file_version_exists(f_old_id), origin::user,
1734 F("no revision %s found in database") % f_old_id);
1735 E(db.file_version_exists(f_new_id), origin::user,
1736 F("no revision %s found in database") % f_new_id);
1737 db.get_file_version(f_old_id, f_old_data);
1738 db.get_file_version(f_new_id, f_new_data);
1739 delta del;
1740 diff(f_old_data.inner(), f_new_data.inner(), del);
1741 pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
1742}
1743
1744// Name: common_ancestors
1745// Arguments:
1746// 1 or more revision ids
1747// Added in: 2.1
1748// Purpose: Prints all revisions which are ancestors of all of the
1749// revisions given as arguments.
1750// Output format: A list of revision ids, in hexadecimal, each
1751// followed by a newline. Revisions are printed in alphabetically
1752// sorted order.
1753// Error conditions: If any of the revisions do not exist, prints
1754// nothing to stdout, prints an error message to stderr, and exits
1755// with status 1.
1756CMD_AUTOMATE(common_ancestors, N_("REV1 [REV2 [REV3 [...]]]"),
1757 N_("Prints revisions that are common ancestors of a list "
1758 "of revisions"),
1759 "",
1760 options::opts::none)
1761{
1762 E(args.size() > 0, origin::user,
1763 F("wrong argument count"));
1764
1765 database db(app);
1766
1767 set<revision_id> revs, common_ancestors;
1768 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i)
1769 {
1770 revision_id rid(decode_hexenc_as<revision_id>((*i)(), origin::user));
1771 E(db.revision_exists(rid), origin::user,
1772 F("no revision %s found in database") % rid);
1773 revs.insert(rid);
1774 }
1775
1776 db.get_common_ancestors(revs, common_ancestors);
1777
1778 for (set<revision_id>::const_iterator i = common_ancestors.begin();
1779 i != common_ancestors.end(); ++i)
1780 output << *i << "\n";
1781}
1782
1783// Name: branches
1784// Arguments:
1785// None
1786// Added in: 2.2
1787// Purpose:
1788// Prints all branch certs present in the revision graph, that are not
1789// excluded by the lua hook 'ignore_branch'.
1790// Output format:
1791// Zero or more lines, each the name of a branch. The lines are printed
1792// in alphabetically sorted order.
1793// Error conditions:
1794// None.
1795CMD_AUTOMATE(branches, "",
1796 N_("Prints all branch certs in the revision graph"),
1797 "",
1798 options::opts::none)
1799{
1800 E(args.size() == 0, origin::user,
1801 F("no arguments needed"));
1802
1803 database db(app);
1804 project_t project(db);
1805 set<branch_name> names;
1806
1807 project.get_branch_list(names, !app.opts.ignore_suspend_certs);
1808
1809 for (set<branch_name>::const_iterator i = names.begin();
1810 i != names.end(); ++i)
1811 if (!app.lua.hook_ignore_branch(*i))
1812 output << (*i) << '\n';
1813}
1814
1815// Name: tags
1816// Arguments:
1817// A branch pattern (optional).
1818// Added in: 2.2
1819// Purpose:
1820// If a branch pattern is given, prints all tags that are attached to
1821// revisions on branches matched by the pattern; otherwise prints all tags
1822// of the revision graph.
1823//
1824// If a branch name is ignored by means of the lua hook 'ignore_branch',
1825// it is neither printed, nor can it be matched by a pattern.
1826// Output format:
1827// There is one basic_io stanza for each tag.
1828//
1829// All stanzas are formatted by basic_io. Stanzas are separated
1830// by a blank line. Values will be escaped, '\' to '\\' and
1831// '"' to '\"'.
1832//
1833// Each stanza has exactly the following four entries:
1834//
1835// 'tag'
1836// the value of the tag cert, i.e. the name of the tag
1837// 'revision'
1838// the hexadecimal id of the revision the tag is attached to
1839// 'signer'
1840// the name of the key used to sign the tag cert
1841// 'branches'
1842// a (possibly empty) list of all branches the tagged revision is on
1843//
1844// Stanzas are printed in arbitrary order.
1845// Error conditions:
1846// A run-time exception is thrown for illegal patterns.
1847CMD_AUTOMATE(tags, N_("[BRANCH_PATTERN]"),
1848 N_("Prints all tags attached to a set of branches"),
1849 "",
1850 options::opts::none)
1851{
1852 E(args.size() < 2, origin::user,
1853 F("wrong argument count"));
1854
1855 database db(app);
1856 project_t project(db);
1857 globish incl("*", origin::internal);
1858 bool filtering(false);
1859
1860 if (args.size() == 1) {
1861 incl = globish(idx(args, 0)(), origin::user);
1862 filtering = true;
1863 }
1864
1865 basic_io::printer prt;
1866
1867 set<tag_t> tags;
1868 project.get_tags(tags);
1869
1870 for (set<tag_t>::const_iterator tag = tags.begin();
1871 tag != tags.end(); ++tag)
1872 {
1873 set<branch_name> branches;
1874 project.get_revision_branches(tag->ident, branches);
1875
1876 bool show(!filtering);
1877 vector<string> branch_names;
1878
1879 for (set<branch_name>::const_iterator branch = branches.begin();
1880 branch != branches.end(); ++branch)
1881 {
1882 // FIXME: again, hook_ignore_branch should probably be in the
1883 // database context...
1884 if (app.lua.hook_ignore_branch(*branch))
1885 continue;
1886
1887 if (!show && incl.matches((*branch)()))
1888 show = true;
1889 branch_names.push_back((*branch)());
1890 }
1891
1892 if (show)
1893 {
1894 basic_io::stanza stz;
1895 stz.push_str_pair(symbol("tag"), tag->name());
1896 stz.push_binary_pair(symbol("revision"), tag->ident.inner());
1897 stz.push_binary_pair(symbol("signer"), tag->key.inner());
1898 stz.push_str_multi(symbol("branches"), branch_names);
1899 prt.print_stanza(stz);
1900 }
1901 }
1902 output.write(prt.buf.data(), prt.buf.size());
1903}
1904
1905namespace
1906{
1907 namespace syms
1908 {
1909 symbol const key("key");
1910 symbol const signature("signature");
1911 symbol const value("value");
1912 symbol const trust("trust");
1913
1914 symbol const domain("domain");
1915 symbol const entry("entry");
1916 }
1917};
1918
1919// Name: get_option
1920// Arguments:
1921// 1: an options name
1922// Added in: 3.1
1923// Purpose: Show the value of the named option in _MTN/options
1924//
1925// Output format: A string
1926//
1927// Sample output (for 'mtn automate get_option branch:
1928// net.venge.monotone
1929//
1930CMD_AUTOMATE(get_option, N_("OPTION"),
1931 N_("Shows the value of an option"),
1932 "",
1933 options::opts::none)
1934{
1935 E(args.size() == 1, origin::user,
1936 F("wrong argument count"));
1937
1938 workspace work(app);
1939 work.print_option(args[0], output);
1940}
1941
1942// Name: get_content_changed
1943// Arguments:
1944// 1: a revision ID
1945// 2: a file name
1946// Added in: 3.1
1947// Purpose: Returns a list of revision IDs in which the content
1948// was most recently changed, relative to the revision ID specified
1949// in argument 1. This equates to a content mark following
1950// the *-merge algorithm.
1951//
1952// Output format: Zero or more basic_io stanzas, each specifying a
1953// revision ID for which a content mark is set.
1954//
1955// Each stanza has exactly one entry:
1956//
1957// 'content_mark'
1958// the hexadecimal id of the revision the content mark is attached to
1959// Sample output (for 'mtn automate get_content_changed 3bccff99d08421df72519b61a4dded16d1139c33 ChangeLog):
1960// content_mark [276264b0b3f1e70fc1835a700e6e61bdbe4c3f2f]
1961//
1962CMD_AUTOMATE(get_content_changed, N_("REV FILE"),
1963 N_("Lists the revisions that changed the content relative "
1964 "to another revision"),
1965 "",
1966 options::opts::none)
1967{
1968 E(args.size() == 2, origin::user,
1969 F("wrong argument count"));
1970
1971 database db(app);
1972
1973 roster_t new_roster;
1974 revision_id ident;
1975 marking_map mm;
1976
1977 ident = decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user);
1978 E(db.revision_exists(ident), origin::user,
1979 F("no revision %s found in database") % ident);
1980 db.get_roster(ident, new_roster, mm);
1981
1982 file_path path = file_path_external(idx(args,1));
1983 E(new_roster.has_node(path), origin::user,
1984 F("file '%s' is unknown for revision %s")
1985 % path % ident);
1986
1987 const_node_t node = new_roster.get_node(path);
1988 const_marking_t const & mark = mm.get_marking(node->self);
1989
1990 basic_io::printer prt;
1991 for (set<revision_id>::const_iterator i = mark->file_content.begin();
1992 i != mark->file_content.end(); ++i)
1993 {
1994 basic_io::stanza st;
1995 st.push_binary_pair(basic_io::syms::content_mark, i->inner());
1996 prt.print_stanza(st);
1997 }
1998 output.write(prt.buf.data(), prt.buf.size());
1999}
2000
2001// Name: get_corresponding_path
2002// Arguments:
2003// 1: a source revision ID
2004// 2: a file name (in the source revision)
2005// 3: a target revision ID
2006// Added in: 3.1
2007// Purpose: Given a the file name in the source revision, a filename
2008// will if possible be returned naming the file in the target revision.
2009// This allows the same file to be matched between revisions, accounting
2010// for renames and other changes.
2011//
2012// Output format: Zero or one basic_io stanzas. Zero stanzas will be
2013// output if the file does not exist within the target revision; this is
2014// not considered an error.
2015// If the file does exist in the target revision, a single stanza with the
2016// following details is output.
2017//
2018// The stanza has exactly one entry:
2019//
2020// 'file'
2021// the file name corresponding to "file name" (arg 2) in the target revision
2022//
2023// Sample output (for automate get_corresponding_path 91f25c8ee830b11b52dd356c925161848d4274d0 foo2 dae0d8e3f944c82a9688bcd6af99f5b837b41968; see automate_get_corresponding_path test)
2024// file "foo"
2025CMD_AUTOMATE(get_corresponding_path, N_("REV1 FILE REV2"),
2026 N_("Prints the name of a file in a target revision relative "
2027 "to a given revision"),
2028 "",
2029 options::opts::none)
2030{
2031 E(args.size() == 3, origin::user,
2032 F("wrong argument count"));
2033
2034 database db(app);
2035
2036 roster_t new_roster, old_roster;
2037 revision_id ident, old_ident;
2038
2039 ident = decode_hexenc_as<revision_id>(idx(args, 0)(), origin::user);
2040 E(db.revision_exists(ident), origin::user,
2041 F("no revision %s found in database") % ident);
2042 db.get_roster(ident, new_roster);
2043
2044 old_ident = decode_hexenc_as<revision_id>(idx(args, 2)(), origin::user);
2045 E(db.revision_exists(old_ident), origin::user,
2046 F("no revision %s found in database") % old_ident);
2047 db.get_roster(old_ident, old_roster);
2048
2049 file_path path = file_path_external(idx(args,1));
2050 E(new_roster.has_node(path), origin::user,
2051 F("file '%s' is unknown for revision %s") % path % ident);
2052
2053 const_node_t node = new_roster.get_node(path);
2054 basic_io::printer prt;
2055 if (old_roster.has_node(node->self))
2056 {
2057 file_path old_path;
2058 basic_io::stanza st;
2059 old_roster.get_name(node->self, old_path);
2060 st.push_file_pair(basic_io::syms::file, old_path);
2061 prt.print_stanza(st);
2062 }
2063 output.write(prt.buf.data(), prt.buf.size());
2064}
2065
2066// Name: put_file
2067// Arguments:
2068// base FILEID (optional)
2069// file contents (binary, intended for automate stdio use)
2070// Added in: 4.1
2071// Purpose:
2072// Store a file in the database.
2073// Optionally encode it as a file_delta
2074// Output format:
2075// The ID of the new file (40 digit hex string)
2076// Error conditions:
2077// a runtime exception is thrown if base revision is not available
2078CMD_AUTOMATE(put_file, N_("[FILEID] CONTENTS"),
2079 N_("Stores a file in the database"),
2080 "",
2081 options::opts::none)
2082{
2083 E(args.size() == 1 || args.size() == 2, origin::user,
2084 F("wrong argument count"));
2085
2086 database db(app);
2087
2088 file_id sha1sum;
2089 transaction_guard tr(db);
2090 if (args.size() == 1)
2091 {
2092 file_data dat = typecast_vocab<file_data>(idx(args, 0));
2093 calculate_ident(dat, sha1sum);
2094
2095 db.put_file(sha1sum, dat);
2096 }
2097 else if (args.size() == 2)
2098 {
2099 file_data dat = typecast_vocab<file_data>(idx(args, 1));
2100 calculate_ident(dat, sha1sum);
2101 file_id base_id(decode_hexenc_as<file_id>(idx(args, 0)(), origin::user));
2102 E(db.file_version_exists(base_id), origin::user,
2103 F("no file version %s found in database") % base_id);
2104
2105 // put_file_version won't do anything if the target ID already exists,
2106 // but we can save the delta calculation by checking here too
2107 if (!db.file_version_exists(sha1sum))
2108 {
2109 file_data olddat;
2110 db.get_file_version(base_id, olddat);
2111 delta del;
2112 diff(olddat.inner(), dat.inner(), del);
2113
2114 db.put_file_version(base_id, sha1sum, file_delta(del));
2115 }
2116 }
2117 else I(false);
2118
2119 tr.commit();
2120 output << sha1sum << '\n';
2121}
2122
2123// Name: put_revision
2124// Arguments:
2125// revision-data
2126// Added in: 4.1
2127// Purpose:
2128// Store a revision into the database.
2129// Output format:
2130// The ID of the new revision
2131// Error conditions:
2132// none
2133CMD_AUTOMATE(put_revision, N_("REVISION-DATA"),
2134 N_("Stores a revision into the database"),
2135 "",
2136 options::opts::none)
2137{
2138 E(args.size() == 1, origin::user,
2139 F("wrong argument count"));
2140
2141 database db(app);
2142
2143 revision_t rev;
2144 read_revision(typecast_vocab<revision_data>(idx(args, 0)), rev);
2145
2146 // recalculate manifest
2147 temp_node_id_source nis;
2148 rev.new_manifest = manifest_id();
2149 for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e)
2150 {
2151 // calculate new manifest
2152 roster_t old_roster;
2153 if (!null_id(e->first)) db.get_roster(e->first, old_roster);
2154 roster_t new_roster = old_roster;
2155 editable_roster_base eros(new_roster, nis);
2156 e->second->apply_to(eros);
2157 if (null_id(rev.new_manifest))
2158 // first edge, initialize manifest
2159 calculate_ident(new_roster, rev.new_manifest);
2160 else
2161 // following edge, make sure that all csets end at the same manifest
2162 {
2163 manifest_id calculated;
2164 calculate_ident(new_roster, calculated);
2165 I(calculated == rev.new_manifest);
2166 }
2167 }
2168
2169 revision_id id;
2170 calculate_ident(rev, id);
2171
2172 // If the database refuses the revision, make sure this is because it's
2173 // already there.
2174 E(db.put_revision(id, rev) || db.revision_exists(id),
2175 origin::user,
2176 F("missing prerequisite for revision %s") % id);
2177
2178 output << id << '\n';
2179}
2180
2181// Name: cert
2182// Arguments:
2183// revision ID
2184// certificate name
2185// certificate value
2186// Added in: 4.1
2187// Purpose:
2188// Add a revision certificate (like mtn cert).
2189// Output format:
2190// nothing
2191// Error conditions:
2192// none
2193CMD_AUTOMATE(cert, N_("REVISION-ID NAME VALUE"),
2194 N_("Adds a revision certificate"),
2195 "",
2196 options::opts::none)
2197{
2198 E(args.size() == 3, origin::user,
2199 F("wrong argument count"));
2200
2201 database db(app);
2202 key_store keys(app);
2203 project_t project(db);
2204
2205 hexenc<id> hrid(idx(args, 0)(), origin::user);
2206 revision_id rid(decode_hexenc_as<revision_id>(hrid(), origin::user));
2207 E(db.revision_exists(rid), origin::user,
2208 F("no revision %s found in database") % hrid);
2209
2210 cache_user_key(app.opts, project, keys, app.lua);
2211
2212 project.put_cert(keys, rid,
2213 typecast_vocab<cert_name>(idx(args, 1)),
2214 typecast_vocab<cert_value>(idx(args, 2)));
2215}
2216
2217// Name: get_db_variables
2218// Arguments:
2219// variable domain
2220// Changes:
2221// 4.1 (added as 'db_get')
2222// 7.0 (changed to 'get_db_variables', output is now basic_io)
2223// Purpose:
2224// Retrieves db variables, optionally filtered by DOMAIN
2225// Output format:
2226// basic_io, see the mtn docs for details
2227// Error conditions:
2228// none
2229CMD_AUTOMATE(get_db_variables, N_("[DOMAIN]"),
2230 N_("Retrieve database variables"),
2231 "",
2232 options::opts::none)
2233{
2234 E(args.size() < 2, origin::user,
2235 F("wrong argument count"));
2236
2237 database db(app);
2238 bool filter_by_domain = false;
2239 var_domain filter;
2240 if (args.size() == 1)
2241 {
2242 filter_by_domain = true;
2243 filter = typecast_vocab<var_domain>(idx(args, 0));
2244 }
2245
2246 map<var_key, var_value> vars;
2247 db.get_vars(vars);
2248
2249 var_domain cur_domain;
2250 basic_io::stanza st;
2251 basic_io::printer pr;
2252 bool found_something = false;
2253
2254 for (map<var_key, var_value>::const_iterator i = vars.begin();
2255 i != vars.end(); ++i)
2256 {
2257 if (filter_by_domain && !(i->first.first == filter))
2258 continue;
2259
2260 found_something = true;
2261
2262 if (cur_domain != i->first.first)
2263 {
2264 // check if we need to print a previous stanza
2265 if (!st.entries.empty())
2266 {
2267 pr.print_stanza(st);
2268 st.entries.clear();
2269 }
2270 cur_domain = i->first.first;
2271 st.push_str_pair(syms::domain, cur_domain());
2272 }
2273
2274 st.push_str_triple(syms::entry, i->first.second(), i->second());
2275 }
2276
2277 E(found_something, origin::user,
2278 F("no variables found or invalid domain specified"));
2279
2280 // print the last stanza
2281 pr.print_stanza(st);
2282 output.write(pr.buf.data(), pr.buf.size());
2283}
2284
2285// Name: set_db_variable
2286// Arguments:
2287// variable domain
2288// variable name
2289// veriable value
2290// Changes:
2291// 4.1 (added as 'db_set')
2292// 7.0 (renamed to 'set_db_variable')
2293// Purpose:
2294// Set a database variable (like mtn database set)
2295// Output format:
2296// nothing
2297// Error conditions:
2298// none
2299CMD_AUTOMATE(set_db_variable, N_("DOMAIN NAME VALUE"),
2300 N_("Sets a database variable"),
2301 "",
2302 options::opts::none)
2303{
2304 E(args.size() == 3, origin::user,
2305 F("wrong argument count"));
2306
2307 database db(app);
2308
2309 var_domain domain = typecast_vocab<var_domain>(idx(args, 0));
2310 utf8 name = idx(args, 1);
2311 utf8 value = idx(args, 2);
2312 var_key key(domain, typecast_vocab<var_name>(name));
2313 db.set_var(key, typecast_vocab<var_value>(value));
2314}
2315
2316// Name: drop_db_variables
2317// Arguments:
2318// variable domain
2319// variable name
2320// Changes:
2321// 7.0 (added)
2322// Purpose:
2323// Drops a database variable (like mtn unset DOMAIN NAME) or all variables
2324// within a domain
2325// Output format:
2326// none
2327// Error conditions:
2328// a runtime exception is thrown if the variable was not found
2329CMD_AUTOMATE(drop_db_variables, N_("DOMAIN [NAME]"),
2330 N_("Drops a database variable"),
2331 "",
2332 options::opts::none)
2333{
2334 E(args.size() == 1 || args.size() == 2, origin::user,
2335 F("wrong argument count"));
2336
2337 database db(app);
2338
2339 var_domain domain = typecast_vocab<var_domain>(idx(args, 0));
2340
2341 if (args.size() == 2)
2342 {
2343 var_name name = typecast_vocab<var_name>(idx(args, 1));
2344 var_key key(domain, name);
2345 E(db.var_exists(key), origin::user,
2346 F("no var with name '%s' in domain '%s'") % name % domain);
2347 db.clear_var(key);
2348 }
2349 else
2350 {
2351 map<var_key, var_value> vars;
2352 db.get_vars(vars);
2353 bool found_something = false;
2354
2355 for (map<var_key, var_value>::const_iterator i = vars.begin();
2356 i != vars.end(); ++i)
2357 {
2358 if (i->first.first == domain)
2359 {
2360 found_something = true;
2361 db.clear_var(i->first);
2362 }
2363 }
2364
2365 E(found_something, origin::user,
2366 F("no variables found in domain '%s'") % domain);
2367 }
2368}
2369
2370// Name: drop_db_variables
2371// Arguments:
2372// none
2373// Changes:
2374// 8.0 (added)
2375// Purpose:
2376// To show the path of the workspace root for the current directory.
2377// Output format:
2378// A path
2379// Error conditions:
2380// a runtime exception is thrown if the current directory isn't part
2381// of a workspace.
2382CMD_AUTOMATE(get_workspace_root, "",
2383 N_("Prints the workspace root for the current directory"),
2384 "",
2385 options::opts::none)
2386{
2387 workspace work(app);
2388 output << get_current_working_dir() << '\n';
2389}
2390
2391// Name: lua
2392// Arguments:
2393// A lua function name
2394// Zero or more function arguments
2395// Changes:
2396// 9.0 (added)
2397// Purpose:
2398// Execute lua functions and return their results.
2399// Output format:
2400// Lua parsable output.
2401// Error conditions:
2402// a runtime exception is thrown if the function does not exists, the arguments cannot be parsed
2403// or the function cannot be executed for some other reason.
2404
2405CMD_AUTOMATE(lua, "LUA_FUNCTION [ARG1 [ARG2 [...]]]",
2406 N_("Executes the given lua function and returns the result"),
2407 "",
2408 options::opts::none)
2409{
2410 E(args.size() >= 1, origin::user,
2411 F("wrong argument count"));
2412
2413 std::string func = idx(args, 0)();
2414
2415 E(app.lua.hook_exists(func), origin::user,
2416 F("lua function '%s' does not exist") % func);
2417
2418 std::vector<std::string> func_args;
2419 if (args.size() > 1)
2420 {
2421 for (unsigned int i=1; i<args.size(); i++)
2422 {
2423 func_args.push_back(idx(args, i)());
2424 }
2425 }
2426
2427 std::string out;
2428 E(app.lua.hook_hook_wrapper(func, func_args, out), origin::user,
2429 F("lua call '%s' failed") % func);
2430
2431 // the output already contains a trailing newline, so we don't add
2432 // another one here
2433 output << out;
2434}
2435
2436CMD_FWD_DECL(automate);
2437
2438void automate_stdio_helpers::
2439automate_stdio_shared_setup(app_state & app,
2440 vector<string> const & cmdline,
2441 vector<pair<string, string> > const * const params,
2442 commands::command_id & id,
2443 commands::automate const * & acmd,
2444 force_ticker_t ft)
2445{
2446 using commands::command_id;
2447 using commands::command;
2448 using commands::automate;
2449
2450 args_vector args;
2451 for (vector<string>::const_iterator i = cmdline.begin();
2452 i != cmdline.end(); ++i)
2453 {
2454 args.push_back(arg_type(*i, origin::user));
2455 id.push_back(utf8(*i, origin::user));
2456 }
2457
2458 set< command_id > matches =
2459 CMD_REF(automate)->complete_command(id);
2460
2461 if (matches.empty())
2462 {
2463 E(false, origin::network,
2464 F("no completions for this command"));
2465 }
2466 else if (matches.size() > 1)
2467 {
2468 E(false, origin::network,
2469 F("multiple completions possible for this command"));
2470 }
2471
2472 id = *matches.begin();
2473
2474 command const * cmd = CMD_REF(automate)->find_command(id);
2475 I(cmd != NULL);
2476
2477 acmd = dynamic_cast< automate const * >(cmd);
2478 I(acmd != NULL);
2479
2480 E(acmd->can_run_from_stdio(), origin::network,
2481 F("sorry, that can't be run remotely or over stdio"));
2482
2483
2484 commands::command_id my_id_for_hook = id;
2485 my_id_for_hook.insert(my_id_for_hook.begin(), utf8("automate", origin::internal));
2486 // group name
2487 my_id_for_hook.insert(my_id_for_hook.begin(), utf8("automation", origin::internal));
2488 commands::reapply_options(app,
2489 app.reset_info.cmd,
2490 commands::command_id() /* doesn't matter */,
2491 cmd, my_id_for_hook, 2,
2492 args,
2493 params);
2494
2495 // disable user prompts, f.e. for password decryption
2496 app.opts.non_interactive = true;
2497
2498
2499 // set a fixed ticker type regardless what the user wants to
2500 // see, because anything else would screw the stdio-encoded output
2501 if (ft == force_stdio_ticker)
2502 app.opts.ticker.unchecked_set("stdio");
2503
2504 // clear path globals set by previous commands
2505 reset_std_paths();
2506
2507}
2508
2509std::pair<int, string> automate_stdio_helpers::
2510automate_stdio_shared_body(app_state & app,
2511 std::vector<std::string> const & cmdline,
2512 std::vector<std::pair<std::string,std::string> >
2513 const & params,
2514 std::ostream & os,
2515 boost::function<void()> init_fn,
2516 boost::function<void(commands::command_id const &)> pre_exec_fn)
2517{
2518 using commands::command_id;
2519 using commands::command;
2520 using commands::automate;
2521
2522 options original_opts = app.opts;
2523 automate const * acmd = 0;
2524 command_id id;
2525 try
2526 {
2527 if (init_fn)
2528 init_fn();
2529 automate_stdio_shared_setup(app, cmdline, &params, id, acmd);
2530 }
2531 catch (option::option_error & e)
2532 {
2533 return make_pair(1, e.what());
2534 }
2535 catch (recoverable_failure & f)
2536 {
2537 return make_pair(1, f.what());
2538 }
2539 if (pre_exec_fn)
2540 pre_exec_fn(id);
2541 try
2542 {
2543 options_applicator oa(app.opts, options_applicator::for_automate_subcmd);
2544 // as soon as a command requires a workspace, this is set to true
2545 workspace::used = false;
2546
2547 acmd->exec_from_automate(app, id, app.opts.args, os);
2548
2549 // usually, if a command succeeds, any of its workspace-relevant
2550 // options are saved back to _MTN/options, this shouldn't be
2551 // any different here
2552 workspace::maybe_set_options(app.opts, app.lua);
2553 }
2554 catch (recoverable_failure & f)
2555 {
2556 return make_pair(2, f.what());
2557 }
2558 app.opts = original_opts;
2559 return make_pair(0, string());
2560}
2561
2562// Local Variables:
2563// mode: C++
2564// fill-column: 76
2565// c-file-style: "gnu"
2566// indent-tabs-mode: nil
2567// End:
2568// 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