monotone

monotone Mtn Source Tree

Root/automate.cc

1// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
2// copyright (C) 2004 nathaniel smith <njs@pobox.com>
3// all rights reserved.
4// licensed to the public under the terms of the GNU GPL (>= 2)
5// see the file COPYING for details
6
7#include <string>
8#include <iostream>
9#include <iterator>
10#include <vector>
11#include <algorithm>
12#include <sstream>
13#include <unistd.h>
14
15#include <boost/bind.hpp>
16#include <boost/function.hpp>
17#include <boost/tuple/tuple.hpp>
18
19#include "app_state.hh"
20#include "basic_io.hh"
21#include "commands.hh"
22#include "constants.hh"
23#include "restrictions.hh"
24#include "revision.hh"
25#include "transforms.hh"
26#include "vocab.hh"
27#include "keys.hh"
28
29static std::string const interface_version = "1.1";
30
31// Name: interface_version
32// Arguments: none
33// Added in: 0.0
34// Purpose: Prints version of automation interface. Major number increments
35// whenever a backwards incompatible change is made; minor number increments
36// whenever any change is made (but is reset when major number increments).
37// Output format: "<decimal number>.<decimal number>\n". Always matches
38// "[0-9]+\.[0-9]+\n".
39// Error conditions: None.
40static void
41automate_interface_version(std::vector<utf8> args,
42 std::string const & help_name,
43 app_state & app,
44 std::ostream & output)
45{
46 if (args.size() != 0)
47 throw usage(help_name);
48
49 output << interface_version << std::endl;
50}
51
52// Name: heads
53// Arguments:
54// 1: branch name (optional, default branch is used if non-existant)
55// Added in: 0.0
56// Purpose: Prints the heads of the given branch.
57// Output format: A list of revision ids, in hexadecimal, each followed by a
58// newline. Revision ids are printed in alphabetically sorted order.
59// Error conditions: If the branch does not exist, prints nothing. (There are
60// no heads.)
61static void
62automate_heads(std::vector<utf8> args,
63 std::string const & help_name,
64 app_state & app,
65 std::ostream & output)
66{
67 if (args.size() > 1)
68 throw usage(help_name);
69
70 if (args.size() ==1 ) {
71 // branchname was explicitly given, use that
72 app.set_branch(idx(args, 0));
73 }
74 std::set<revision_id> heads;
75 get_branch_heads(app.branch_name(), app, heads);
76 for (std::set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
77 output << (*i).inner()() << std::endl;
78}
79
80// Name: ancestors
81// Arguments:
82// 1 or more: revision ids
83// Added in: 0.2
84// Purpose: Prints the ancestors (exclusive) of the given revisions
85// Output format: A list of revision ids, in hexadecimal, each followed by a
86// newline. Revision ids are printed in alphabetically sorted order.
87// Error conditions: If any of the revisions do not exist, prints nothing to
88// stdout, prints an error message to stderr, and exits with status 1.
89static void
90automate_ancestors(std::vector<utf8> args,
91 std::string const & help_name,
92 app_state & app,
93 std::ostream & output)
94{
95 if (args.size() == 0)
96 throw usage(help_name);
97
98 std::set<revision_id> ancestors;
99 std::vector<revision_id> frontier;
100 for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
101 {
102 revision_id rid((*i)());
103 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
104 frontier.push_back(rid);
105 }
106 while (!frontier.empty())
107 {
108 revision_id rid = frontier.back();
109 frontier.pop_back();
110 if(!null_id(rid)) {
111 std::set<revision_id> parents;
112 app.db.get_revision_parents(rid, parents);
113 for (std::set<revision_id>::const_iterator i = parents.begin();
114 i != parents.end(); ++i)
115 {
116 if (ancestors.find(*i) == ancestors.end())
117 {
118 frontier.push_back(*i);
119 ancestors.insert(*i);
120 }
121 }
122 }
123 }
124 for (std::set<revision_id>::const_iterator i = ancestors.begin();
125 i != ancestors.end(); ++i)
126 if (!null_id(*i))
127 output << (*i).inner()() << std::endl;
128}
129
130
131// Name: descendents
132// Arguments:
133// 1 or more: revision ids
134// Added in: 0.1
135// Purpose: Prints the descendents (exclusive) of the given revisions
136// Output format: A list of revision ids, in hexadecimal, each followed by a
137// newline. Revision ids are printed in alphabetically sorted order.
138// Error conditions: If any of the revisions do not exist, prints nothing to
139// stdout, prints an error message to stderr, and exits with status 1.
140static void
141automate_descendents(std::vector<utf8> args,
142 std::string const & help_name,
143 app_state & app,
144 std::ostream & output)
145{
146 if (args.size() == 0)
147 throw usage(help_name);
148
149 std::set<revision_id> descendents;
150 std::vector<revision_id> frontier;
151 for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
152 {
153 revision_id rid((*i)());
154 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
155 frontier.push_back(rid);
156 }
157 while (!frontier.empty())
158 {
159 revision_id rid = frontier.back();
160 frontier.pop_back();
161 std::set<revision_id> children;
162 app.db.get_revision_children(rid, children);
163 for (std::set<revision_id>::const_iterator i = children.begin();
164 i != children.end(); ++i)
165 {
166 if (descendents.find(*i) == descendents.end())
167 {
168 frontier.push_back(*i);
169 descendents.insert(*i);
170 }
171 }
172 }
173 for (std::set<revision_id>::const_iterator i = descendents.begin();
174 i != descendents.end(); ++i)
175 output << (*i).inner()() << std::endl;
176}
177
178
179// Name: erase_ancestors
180// Arguments:
181// 0 or more: revision ids
182// Added in: 0.1
183// Purpose: Prints all arguments, except those that are an ancestor of some
184// other argument. One way to think about this is that it prints the
185// minimal elements of the given set, under the ordering imposed by the
186// "child of" relation. Another way to think of it is if the arguments were
187// a branch, then we print the heads of that branch.
188// Output format: A list of revision ids, in hexadecimal, each followed by a
189// newline. Revision ids are printed in alphabetically sorted order.
190// Error conditions: If any of the revisions do not exist, prints nothing to
191// stdout, prints an error message to stderr, and exits with status 1.
192static void
193automate_erase_ancestors(std::vector<utf8> args,
194 std::string const & help_name,
195 app_state & app,
196 std::ostream & output)
197{
198 std::set<revision_id> revs;
199 for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
200 {
201 revision_id rid((*i)());
202 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
203 revs.insert(rid);
204 }
205 erase_ancestors(revs, app);
206 for (std::set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
207 output << (*i).inner()() << std::endl;
208}
209
210// Name: attributes
211// Arguments:
212// 1: file name (optional, if non-existant prints all files with attributes)
213// Added in: 1.0
214// Purpose: Prints all attributes for a file, or all all files with attributes
215// if a file name provided.
216// Output format: A list of file names in alphabetically sorted order,
217// or a list of attributes if a file name provided.
218// Error conditions: If the file name has no attributes, prints nothing.
219static void
220automate_attributes(std::vector<utf8> args,
221 std::string const & help_name,
222 app_state & app,
223 std::ostream & output)
224{
225 if (args.size() > 1)
226 throw usage(help_name);
227
228 // is there an .mt-attrs?
229 file_path attr_path;
230 get_attr_path(attr_path);
231 if (!file_exists(attr_path)) return;
232
233 // read attribute map
234 data attr_data;
235 attr_map attrs;
236
237 read_data(attr_path, attr_data);
238 read_attr_map(attr_data, attrs);
239
240 if (args.size() == 1) {
241 // a filename was given, if it has attributes, print them
242 file_path path = file_path_external(idx(args,0));
243 attr_map::const_iterator i = attrs.find(path);
244 if (i == attrs.end()) return;
245
246 for (std::map<std::string, std::string>::const_iterator j = i->second.begin();
247 j != i->second.end(); ++j)
248 output << j->first << std::endl;
249 }
250 else {
251 for (attr_map::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
252 {
253 output << (*i).first << std::endl;
254 }
255 }
256}
257
258// Name: toposort
259// Arguments:
260// 0 or more: revision ids
261// Added in: 0.1
262// Purpose: Prints all arguments, topologically sorted. I.e., if A is an
263// ancestor of B, then A will appear before B in the output list.
264// Output format: A list of revision ids, in hexadecimal, each followed by a
265// newline. Revisions are printed in topologically sorted order.
266// Error conditions: If any of the revisions do not exist, prints nothing to
267// stdout, prints an error message to stderr, and exits with status 1.
268static void
269automate_toposort(std::vector<utf8> args,
270 std::string const & help_name,
271 app_state & app,
272 std::ostream & output)
273{
274 std::set<revision_id> revs;
275 for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
276 {
277 revision_id rid((*i)());
278 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
279 revs.insert(rid);
280 }
281 std::vector<revision_id> sorted;
282 toposort(revs, sorted, app);
283 for (std::vector<revision_id>::const_iterator i = sorted.begin();
284 i != sorted.end(); ++i)
285 output << (*i).inner()() << std::endl;
286}
287
288// Name: ancestry_difference
289// Arguments:
290// 1: a revision id
291// 0 or more further arguments: also revision ids
292// Added in: 0.1
293// Purpose: Prints all ancestors of the first revision A, that are not also
294// ancestors of the other revision ids, the "Bs". For purposes of this
295// command, "ancestor" is an inclusive term; that is, A is an ancestor of
296// one of the Bs, it will not be printed, but otherwise, it will be; and
297// none of the Bs will ever be printed. If A is a new revision, and Bs are
298// revisions that you have processed before, then this command tells you
299// which revisions are new since then.
300// Output format: A list of revision ids, in hexadecimal, each followed by a
301// newline. Revisions are printed in topologically sorted order.
302// Error conditions: If any of the revisions do not exist, prints nothing to
303// stdout, prints an error message to stderr, and exits with status 1.
304static void
305automate_ancestry_difference(std::vector<utf8> args,
306 std::string const & help_name,
307 app_state & app,
308 std::ostream & output)
309{
310 if (args.size() == 0)
311 throw usage(help_name);
312
313 revision_id a;
314 std::set<revision_id> bs;
315 std::vector<utf8>::const_iterator i = args.begin();
316 a = revision_id((*i)());
317 N(app.db.revision_exists(a), F("No such revision %s") % a);
318 for (++i; i != args.end(); ++i)
319 {
320 revision_id b((*i)());
321 N(app.db.revision_exists(b), F("No such revision %s") % b);
322 bs.insert(b);
323 }
324 std::set<revision_id> ancestors;
325 ancestry_difference(a, bs, ancestors, app);
326
327 std::vector<revision_id> sorted;
328 toposort(ancestors, sorted, app);
329 for (std::vector<revision_id>::const_iterator i = sorted.begin();
330 i != sorted.end(); ++i)
331 output << (*i).inner()() << std::endl;
332}
333
334// Name: leaves
335// Arguments:
336// None
337// Added in: 0.1
338// Purpose: Prints the leaves of the revision graph, i.e., all revisions that
339// have no children. This is similar, but not identical to the
340// functionality of 'heads', which prints every revision in a branch, that
341// has no descendents in that branch. If every revision in the database was
342// in the same branch, then they would be identical. Generally, every leaf
343// is the head of some branch, but not every branch head is a leaf.
344// Output format: A list of revision ids, in hexadecimal, each followed by a
345// newline. Revision ids are printed in alphabetically sorted order.
346// Error conditions: None.
347static void
348automate_leaves(std::vector<utf8> args,
349 std::string const & help_name,
350 app_state & app,
351 std::ostream & output)
352{
353 if (args.size() != 0)
354 throw usage(help_name);
355
356 // this might be more efficient in SQL, but for now who cares.
357 std::set<revision_id> leaves;
358 app.db.get_revision_ids(leaves);
359 std::multimap<revision_id, revision_id> graph;
360 app.db.get_revision_ancestry(graph);
361 for (std::multimap<revision_id, revision_id>::const_iterator i = graph.begin();
362 i != graph.end(); ++i)
363 leaves.erase(i->first);
364 for (std::set<revision_id>::const_iterator i = leaves.begin(); i != leaves.end(); ++i)
365 output << (*i).inner()() << std::endl;
366}
367
368// Name: parents
369// Arguments:
370// 1: a revision id
371// Added in: 0.2
372// Purpose: Prints the immediate ancestors of the given revision, i.e., the
373// parents.
374// Output format: A list of revision ids, in hexadecimal, each followed by a
375// newline. Revision ids are printed in alphabetically sorted order.
376// Error conditions: If the revision does not exist, prints nothing to stdout,
377// prints an error message to stderr, and exits with status 1.
378static void
379automate_parents(std::vector<utf8> args,
380 std::string const & help_name,
381 app_state & app,
382 std::ostream & output)
383{
384 if (args.size() != 1)
385 throw usage(help_name);
386 revision_id rid(idx(args, 0)());
387 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
388 std::set<revision_id> parents;
389 app.db.get_revision_parents(rid, parents);
390 for (std::set<revision_id>::const_iterator i = parents.begin();
391 i != parents.end(); ++i)
392 if (!null_id(*i))
393 output << (*i).inner()() << std::endl;
394}
395
396// Name: children
397// Arguments:
398// 1: a revision id
399// Added in: 0.2
400// Purpose: Prints the immediate descendents of the given revision, i.e., the
401// children.
402// Output format: A list of revision ids, in hexadecimal, each followed by a
403// newline. Revision ids are printed in alphabetically sorted order.
404// Error conditions: If the revision does not exist, prints nothing to stdout,
405// prints an error message to stderr, and exits with status 1.
406static void
407automate_children(std::vector<utf8> args,
408 std::string const & help_name,
409 app_state & app,
410 std::ostream & output)
411{
412 if (args.size() != 1)
413 throw usage(help_name);
414 revision_id rid(idx(args, 0)());
415 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
416 std::set<revision_id> children;
417 app.db.get_revision_children(rid, children);
418 for (std::set<revision_id>::const_iterator i = children.begin();
419 i != children.end(); ++i)
420 if (!null_id(*i))
421 output << (*i).inner()() << std::endl;
422}
423
424// Name: graph
425// Arguments:
426// None
427// Added in: 0.2
428// Purpose: Prints out the complete ancestry graph of this database.
429// Output format:
430// Each line begins with a revision id. Following this are zero or more
431// space-prefixed revision ids. Each revision id after the first is a
432// parent (in the sense of 'automate parents') of the first. For instance,
433// the following are valid lines:
434// 07804171823d963f78d6a0ff1763d694dd74ff40
435// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01
436// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01 a02e7a1390e3e4745c31be922f03f56450c13dce
437// The first would indicate that 07804171823d963f78d6a0ff1763d694dd74ff40
438// was a root node; the second would indicate that it had one parent, and
439// the third would indicate that it had two parents, i.e., was a merge.
440//
441// The output as a whole is alphabetically sorted; additionally, the parents
442// within each line are alphabetically sorted.
443// Error conditions: None.
444static void
445automate_graph(std::vector<utf8> args,
446 std::string const & help_name,
447 app_state & app,
448 std::ostream & output)
449{
450 if (args.size() != 0)
451 throw usage(help_name);
452
453 std::multimap<revision_id, revision_id> edges_mmap;
454 std::map<revision_id, std::set<revision_id> > child_to_parents;
455
456 app.db.get_revision_ancestry(edges_mmap);
457
458 for (std::multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
459 i != edges_mmap.end(); ++i)
460 {
461 if (child_to_parents.find(i->second) == child_to_parents.end())
462 child_to_parents.insert(std::make_pair(i->second, std::set<revision_id>()));
463 if (null_id(i->first))
464 continue;
465 std::map<revision_id, std::set<revision_id> >::iterator
466 j = child_to_parents.find(i->second);
467 I(j->first == i->second);
468 j->second.insert(i->first);
469 }
470
471 for (std::map<revision_id, std::set<revision_id> >::const_iterator i = child_to_parents.begin();
472 i != child_to_parents.end(); ++i)
473 {
474 output << (i->first).inner()();
475 for (std::set<revision_id>::const_iterator j = i->second.begin();
476 j != i->second.end(); ++j)
477 output << " " << (*j).inner()();
478 output << std::endl;
479 }
480}
481
482// Name: select
483// Arguments:
484// 1: selector
485// Added in: 0.2
486// Purpose: Prints all the revisions that match the given selector.
487// Output format: A list of revision ids, in hexadecimal, each followed by a
488// newline. Revision ids are printed in alphabetically sorted order.
489// Error conditions: None.
490static void
491automate_select(std::vector<utf8> args,
492 std::string const & help_name,
493 app_state & app,
494 std::ostream & output)
495{
496 if (args.size() != 1)
497 throw usage(help_name);
498
499 std::vector<std::pair<selectors::selector_type, std::string> >
500 sels(selectors::parse_selector(args[0](), app));
501
502 // we jam through an "empty" selection on sel_ident type
503 std::set<std::string> completions;
504 selectors::selector_type ty = selectors::sel_ident;
505 selectors::complete_selector("", sels, ty, completions, app);
506
507 for (std::set<std::string>::const_iterator i = completions.begin();
508 i != completions.end(); ++i)
509 output << *i << std::endl;
510}
511
512// consider a changeset with the following
513//
514// deletions
515// renames from to
516// additions
517//
518// pre-state corresponds to deletions and the "from" side of renames
519// post-state corresponds to the "to" side of renames and additions
520// file-state corresponds to the state of the file with the given name
521//
522// pre and post state are related to the path rearrangement specified in MT/work
523// file state is related to the details of the resulting file
524
525struct inventory_item
526{
527 enum pstate
528 { KNOWN_PATH, ADDED_PATH, DROPPED_PATH, RENAMED_PATH }
529 pre_state, post_state;
530
531 enum fstate
532 { KNOWN_FILE, PATCHED_FILE, MISSING_FILE, UNKNOWN_FILE, IGNORED_FILE }
533 file_state;
534
535 enum ptype
536 { FILE, DIRECTORY }
537 path_type;
538
539 size_t pre_id, post_id;
540
541 inventory_item():
542 pre_state(KNOWN_PATH), post_state(KNOWN_PATH),
543 file_state(KNOWN_FILE),
544 path_type(FILE),
545 pre_id(0), post_id(0) {}
546};
547
548typedef std::map<file_path, inventory_item> inventory_map;
549
550static void
551inventory_pre_state(inventory_map & inventory,
552 path_set const & paths,
553 inventory_item::pstate pre_state,
554 size_t id = 0,
555 inventory_item::ptype path_type = inventory_item::FILE)
556{
557 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
558 {
559 L(F("%d %d %s\n") % inventory[*i].pre_state % pre_state % *i);
560 I(inventory[*i].pre_state == inventory_item::KNOWN_PATH);
561 inventory[*i].pre_state = pre_state;
562 inventory[*i].path_type = path_type;
563 if (id != 0)
564 {
565 I(inventory[*i].pre_id == 0);
566 inventory[*i].pre_id = id;
567 }
568 }
569}
570
571static void
572inventory_post_state(inventory_map & inventory,
573 path_set const & paths,
574 inventory_item::pstate post_state,
575 size_t id = 0,
576 inventory_item::ptype path_type = inventory_item::FILE)
577{
578 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
579 {
580 L(F("%d %d %s\n") % inventory[*i].post_state % post_state % *i);
581 I(inventory[*i].post_state == inventory_item::KNOWN_PATH);
582 inventory[*i].post_state = post_state;
583 inventory[*i].path_type = path_type;
584 if (id != 0)
585 {
586 I(inventory[*i].post_id == 0);
587 inventory[*i].post_id = id;
588 }
589 }
590}
591
592static void
593inventory_file_state(inventory_map & inventory,
594 path_set const & paths,
595 inventory_item::fstate file_state)
596{
597 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
598 {
599 L(F("%d %d %s\n") % inventory[*i].file_state % file_state % *i);
600 I(inventory[*i].file_state == inventory_item::KNOWN_FILE);
601 inventory[*i].file_state = file_state;
602 }
603}
604
605static void
606inventory_renames(inventory_map & inventory,
607 std::map<file_path,file_path> const & renames,
608 inventory_item::ptype path_type = inventory_item::FILE)
609{
610 path_set old_name;
611 path_set new_name;
612
613 static size_t id = 1;
614
615 for (std::map<file_path,file_path>::const_iterator i = renames.begin();
616 i != renames.end(); i++)
617 {
618 old_name.insert(i->first);
619 new_name.insert(i->second);
620
621 inventory_pre_state(inventory, old_name, inventory_item::RENAMED_PATH, id, path_type);
622 inventory_post_state(inventory, new_name, inventory_item::RENAMED_PATH, id, path_type);
623
624 id++;
625
626 old_name.clear();
627 new_name.clear();
628 }
629}
630
631// Name: inventory
632// Arguments: none
633// Added in: 1.0
634// Purpose: Prints a summary of every file found in the working copy or its
635// associated base manifest. Each unique path is listed on a line prefixed by
636// three status characters and two numeric values used for identifying
637// renames. The three status characters are as follows.
638//
639// column 1 pre-state
640// ' ' the path was unchanged in the pre-state
641// 'D' the path was deleted from the pre-state
642// 'R' the path was renamed from the pre-state name
643// column 2 post-state
644// ' ' the path was unchanged in the post-state
645// 'R' the path was renamed to the post-state name
646// 'A' the path was added to the post-state
647// column 3 file-state
648// ' ' the file is known and unchanged from the current manifest version
649// 'P' the file is patched to a new version
650// 'U' the file is unknown and not included in the current manifest
651// 'I' the file is ignored and not included in the current manifest
652// 'M' the file is missing but is included in the current manifest
653//
654// Output format: Each path is printed on its own line, prefixed by three status
655// characters as described above. The status is followed by a single space and
656// two numbers, each separated by a single space, used for identifying renames.
657// The numbers are followed by a single space and then the pathname, which
658// includes the rest of the line. Directory paths are identified as ending with
659// the "/" character, file paths do not end in this character.
660//
661// Error conditions: If no working copy book keeping MT directory is found,
662// prints an error message to stderr, and exits with status 1.
663
664static void
665automate_inventory(std::vector<utf8> args,
666 std::string const & help_name,
667 app_state & app,
668 std::ostream & output)
669{
670 if (args.size() != 0)
671 throw usage(help_name);
672
673 manifest_id old_manifest_id;
674 revision_id old_revision_id;
675 manifest_map m_old, m_new;
676 path_set old_paths, new_paths, empty;
677 change_set::path_rearrangement included, excluded;
678 change_set cs;
679 path_set missing, changed, unchanged, unknown, ignored;
680 inventory_map inventory;
681 app.require_working_copy();
682
683 calculate_restricted_rearrangement(app, args,
684 old_manifest_id, old_revision_id,
685 m_old, old_paths, new_paths,
686 included, excluded);
687
688 // this is a bit screwey. we need to rearrange the old manifest
689 // according to the included rearrangement and for that we need
690 // a complete changeset, which is normally obtained from both
691 // the old and the new manifest. we can't do that because there
692 // may be missing files, so instead we add our own set of deltas
693 // below.
694
695 // we have the rearrangement of the changeset from above
696 // now we need to build up the deltas for the added files
697
698 cs.rearrangement = included;
699
700 hexenc<id> null_ident;
701
702 for (path_set::const_iterator
703 i = included.added_files.begin();
704 i != included.added_files.end(); ++i)
705 {
706 if (path_exists(*i))
707 {
708 // add path from [] to [xxx]
709 hexenc<id> ident;
710 calculate_ident(*i, ident, app.lua);
711 cs.deltas.insert(std::make_pair(*i,std::make_pair(null_ident, ident)));
712 }
713 else
714 {
715 // remove missing files from the added list since they have not deltas
716 missing.insert(*i);
717 cs.rearrangement.added_files.erase(*i);
718 }
719 }
720
721 apply_change_set(m_old, cs, m_new);
722
723 classify_manifest_paths(app, m_new, missing, changed, unchanged);
724
725 // remove the remaining added files from the unchanged set since they have been
726 // changed in the deltas construction above. also, only consider the file as
727 // changed if its not missing
728
729 for (path_set::const_iterator
730 i = included.added_files.begin();
731 i != included.added_files.end(); ++i)
732 {
733 unchanged.erase(*i);
734 if (missing.find(*i) == missing.end())
735 changed.insert(*i);
736 }
737
738 file_itemizer u(app, new_paths, unknown, ignored);
739 walk_tree(file_path(), u);
740
741 inventory_file_state(inventory, missing, inventory_item::MISSING_FILE);
742
743 inventory_pre_state(inventory, included.deleted_files, inventory_item::DROPPED_PATH);
744 inventory_pre_state(inventory, included.deleted_dirs,
745 inventory_item::DROPPED_PATH, inventory_item::DIRECTORY);
746
747 inventory_renames(inventory, included.renamed_files);
748 inventory_renames(inventory, included.renamed_dirs, inventory_item::DIRECTORY);
749
750 inventory_post_state(inventory, included.added_files, inventory_item::ADDED_PATH);
751
752 inventory_file_state(inventory, changed, inventory_item::PATCHED_FILE);
753 inventory_file_state(inventory, unchanged, inventory_item::KNOWN_FILE);
754 inventory_file_state(inventory, unknown, inventory_item::UNKNOWN_FILE);
755 inventory_file_state(inventory, ignored, inventory_item::IGNORED_FILE);
756
757 for (inventory_map::const_iterator i = inventory.begin(); i != inventory.end(); ++i)
758 {
759 switch (inventory[i->first].pre_state)
760 {
761 case inventory_item::KNOWN_PATH: output << " "; break;
762 case inventory_item::DROPPED_PATH: output << "D"; break;
763 case inventory_item::RENAMED_PATH: output << "R"; break;
764 default: I(false); // invalid pre_state
765 }
766
767 switch (inventory[i->first].post_state)
768 {
769 case inventory_item::KNOWN_PATH: output << " "; break;
770 case inventory_item::RENAMED_PATH: output << "R"; break;
771 case inventory_item::ADDED_PATH: output << "A"; break;
772 default: I(false); // invalid post_state
773 }
774
775 switch (inventory[i->first].file_state)
776 {
777 case inventory_item::KNOWN_FILE: output << " "; break;
778 case inventory_item::PATCHED_FILE: output << "P"; break;
779 case inventory_item::UNKNOWN_FILE: output << "U"; break;
780 case inventory_item::IGNORED_FILE: output << "I"; break;
781 case inventory_item::MISSING_FILE: output << "M"; break;
782 }
783
784 // need directory indicators
785
786 output << " " << inventory[i->first].pre_id
787 << " " << inventory[i->first].post_id
788 << " " << i->first;
789
790 if (inventory[i->first].path_type == inventory_item::DIRECTORY)
791 output << "/";
792
793 output << std::endl;
794 }
795
796}
797
798// Name: certs
799// Arguments:
800// 1: a revision id
801// Added in: 1.0
802// Purpose: Prints all certificates associated with the given revision ID.
803// Each certificate is contained in a basic IO stanza. For each certificate,
804// the following values are provided:
805//
806// 'key' : a string indicating the key used to sign this certificate.
807// 'signature': a string indicating the status of the signature. Possible
808// values of this string are:
809// 'ok' : the signature is correct
810// 'bad' : the signature is invalid
811// 'unknown' : signature was made with an unknown key
812// 'name' : the name of this certificate
813// 'value' : the value of this certificate
814// 'trust' : is this certificate trusted by the defined trust metric
815// Possible values of this string are:
816// 'trusted' : this certificate is trusted
817// 'untrusted' : this certificate is not trusted
818//
819// Output format: All stanzas are formatted by basic_io. Stanzas are seperated
820// by a blank line. Values will be escaped, '\' -> '\\' and '"' -> '\"'.
821//
822// Error conditions: If a certificate is signed with an unknown public key, a
823// warning message is printed to stderr. If the revision specified is unknown
824// or invalid prints an error message to stderr and exits with status 1.
825static void
826automate_certs(std::vector<utf8> args,
827 std::string const & help_name,
828 app_state & app,
829 std::ostream & output)
830{
831 if (args.size() != 1)
832 throw usage(help_name);
833
834 std::vector<cert> certs;
835
836 transaction_guard guard(app.db, false);
837
838 revision_id rid(idx(args, 0)());
839 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
840 hexenc<id> ident(rid.inner());
841
842 std::vector< revision<cert> > ts;
843 app.db.get_revision_certs(rid, ts);
844 for (size_t i = 0; i < ts.size(); ++i)
845 certs.push_back(idx(ts, i).inner());
846
847 {
848 std::set<rsa_keypair_id> checked;
849 for (size_t i = 0; i < certs.size(); ++i)
850 {
851 if (checked.find(idx(certs, i).key) == checked.end() &&
852 !app.db.public_key_exists(idx(certs, i).key))
853 P(F("warning: no public key '%s' found in database\n")
854 % idx(certs, i).key);
855 checked.insert(idx(certs, i).key);
856 }
857 }
858
859 // Make the output deterministic; this is useful for the test suite, in
860 // particular.
861 std::sort(certs.begin(), certs.end());
862
863 basic_io::printer pr(output);
864
865 for (size_t i = 0; i < certs.size(); ++i)
866 {
867 basic_io::stanza st;
868 cert_status status = check_cert(app, idx(certs, i));
869 cert_value tv;
870 cert_name name = idx(certs, i).name();
871 std::set<rsa_keypair_id> signers;
872
873 decode_base64(idx(certs, i).value, tv);
874
875 rsa_keypair_id keyid = idx(certs, i).key();
876 signers.insert(keyid);
877
878 bool trusted = app.lua.hook_get_revision_cert_trust(signers, ident,
879 name, tv);
880
881 st.push_str_pair("key", keyid());
882
883 std::string stat;
884 switch (status)
885 {
886 case cert_ok:
887 stat = "ok";
888 break;
889 case cert_bad:
890 stat = "bad";
891 break;
892 case cert_unknown:
893 stat = "unknown";
894 break;
895 }
896 st.push_str_pair("signature", stat);
897
898 st.push_str_pair("name", name());
899 st.push_str_pair("value", tv());
900 st.push_str_pair("trust", (trusted ? "trusted" : "untrusted"));
901
902 pr.print_stanza(st);
903 }
904
905 guard.commit();
906}
907
908// Name: get_revision
909// Arguments:
910// 1: a revision id (optional, determined from working directory if non-existant)
911// Added in: 1.0
912// Purpose: Prints changeset information for the specified revision id.
913//
914// There are several changes that are described; each of these is described by
915// a different basic_io stanza. The first string pair of each stanza indicates the
916// type of change represented.
917//
918// Possible values of this first value are along with an ordered list of
919// basic_io formatted string pairs that will be provided are:
920//
921// 'old_revision' : represents a parent revision.
922// format: ('old_revision', revision id)
923// 'new_manifest' : represents the new manifest associated with the revision.
924// format: ('new_manifest', manifest id)
925// 'old_manifest' : represents a manifest associated with a parent revision.
926// format: ('old_manifest', manifest id)
927// 'patch' : represents a file that was modified.
928// format: ('patch', filename), ('from', file id), ('to', file id)
929// 'add_file' : represents a file that was added.
930// format: ('add_file', filename)
931// 'delete_file' : represents a file that was deleted.
932// format: ('delete_file', filename)
933// 'delete_dir' : represents a directory that was deleted.
934// format: ('delete_dir', filename)
935// 'rename_file' : represents a file that was renamed.
936// format: ('rename_file', old filename), ('to', new filename)
937// 'rename_dir' : represents a directory that was renamed.
938// format: ('rename_dir', old filename), ('to', new filename)
939//
940// Output format: All stanzas are formatted by basic_io. Stanzas are seperated
941// by a blank line. Values will be escaped, '\' -> '\\' and '"' -> '\"'.
942//
943// Error conditions: If the revision specified is unknown or invalid prints an
944// error message to stderr and exits with status 1.
945static void
946automate_get_revision(std::vector<utf8> args,
947 std::string const & help_name,
948 app_state & app,
949 std::ostream & output)
950{
951 if (args.size() > 1)
952 throw usage(help_name);
953
954 revision_data dat;
955 revision_id ident;
956
957 if (args.size() == 0)
958 {
959 revision_set rev;
960 manifest_map m_old, m_new;
961
962 app.require_working_copy();
963 calculate_unrestricted_revision(app, rev, m_old, m_new);
964 calculate_ident(rev, ident);
965 write_revision_set(rev, dat);
966 }
967 else
968 {
969 ident = revision_id(idx(args, 0)());
970 N(app.db.revision_exists(ident),
971 F("no revision %s found in database") % ident);
972 app.db.get_revision(ident, dat);
973 }
974
975 L(F("dumping revision %s\n") % ident);
976 output.write(dat.inner()().data(), dat.inner()().size());
977}
978
979// Name: get_manifest
980// Arguments:
981// 1: a manifest id (optional, determined from working directory if non-existant)
982// Added in: 1.0
983// Purpose: Prints the contents of the manifest associated with the given manifest ID.
984//
985// Output format: One line for each file in the manifest. Each line begins with a
986// 40 character file ID, followed by two space characters (' ') and then the filename.
987// eg:
988// 22382ac1bdffec21170a88ff2580fe39b508243f vocab.hh
989//
990// Error conditions: If the manifest ID specified is unknown or invalid prints an
991// error message to stderr and exits with status 1.
992static void
993automate_get_manifest(std::vector<utf8> args,
994 std::string const & help_name,
995 app_state & app,
996 std::ostream & output)
997{
998 if (args.size() > 1)
999 throw usage(help_name);
1000
1001 manifest_data dat;
1002 manifest_id ident;
1003
1004 if (args.size() == 0)
1005 {
1006 revision_set rev;
1007 manifest_map m_old, m_new;
1008
1009 app.require_working_copy();
1010 calculate_unrestricted_revision(app, rev, m_old, m_new);
1011
1012 calculate_ident(m_new, ident);
1013 write_manifest_map(m_new, dat);
1014 }
1015 else
1016 {
1017 ident = manifest_id(idx(args, 0)());
1018 N(app.db.manifest_version_exists(ident),
1019 F("no manifest version %s found in database") % ident);
1020 app.db.get_manifest_version(ident, dat);
1021 }
1022
1023 L(F("dumping manifest %s\n") % ident);
1024 output.write(dat.inner()().data(), dat.inner()().size());
1025}
1026
1027// Name: get_file
1028// Arguments:
1029// 1: a file id
1030// Added in: 1.0
1031// Purpose: Prints the contents of the specified file.
1032//
1033// Output format: The file contents are output without modification.
1034//
1035// Error conditions: If the file id specified is unknown or invalid prints
1036// an error message to stderr and exits with status 1.
1037static void
1038automate_get_file(std::vector<utf8> args,
1039 std::string const & help_name,
1040 app_state & app,
1041 std::ostream & output)
1042{
1043 if (args.size() != 1)
1044 throw usage(help_name);
1045
1046 file_id ident(idx(args, 0)());
1047 N(app.db.file_version_exists(ident),
1048 F("no file version %s found in database") % ident);
1049
1050 file_data dat;
1051 L(F("dumping file %s\n") % ident);
1052 app.db.get_file_version(ident, dat);
1053 output.write(dat.inner()().data(), dat.inner()().size());
1054}
1055
1056void
1057automate_command(utf8 cmd, std::vector<utf8> args,
1058 std::string const & root_cmd_name,
1059 app_state & app,
1060 std::ostream & output);
1061
1062// Name: stdio
1063// Arguments: none
1064// Added in: 1.0
1065// Purpose: Allow multiple automate commands to be run from one instance
1066// of monotone.
1067//
1068// Input format: The input is a series of lines of the form
1069// 'l'<size>':'<string>[<size>':'<string>...]'e', with characters
1070// after the 'e' of one command, but before the 'l' of the next ignored.
1071// This space is reserved, and should not contain characters other
1072// than '\n'.
1073// Example:
1074// l6:leavese
1075// l7:parents40:0e3171212f34839c2e3263e7282cdeea22fc5378e
1076//
1077// Output format: <command number>:<err code>:<last?>:<size>:<output>
1078// <command number> is a decimal number specifying which command
1079// this output is from. It is 0 for the first command, and increases
1080// by one each time.
1081// <err code> is 0 for success, 1 for a syntax error, and 2 for any
1082// other error.
1083// <last?> is 'l' if this is the last piece of output for this command,
1084// and 'm' if there is more output to come.
1085// <size> is the number of bytes in the output.
1086// <output> is the output of the command.
1087// Example:
1088// 0:0:l:205:0e3171212f34839c2e3263e7282cdeea22fc5378
1089// 1f4ef73c3e056883c6a5ff66728dd764557db5e6
1090// 2133c52680aa2492b18ed902bdef7e083464c0b8
1091// 23501f8afd1f9ee037019765309b0f8428567f8a
1092// 2c295fcf5fe20301557b9b3a5b4d437b5ab8ec8c
1093// 1:0:l:41:7706a422ccad41621c958affa999b1a1dd644e79
1094//
1095// Error conditions: Errors encountered by the commands run only set the error
1096// code in the output for that command. Malformed input results in exit with
1097// a non-zero return value and an error message.
1098
1099//We use our own stringbuf class so we can put in a callback on write.
1100//This lets us dump output at a set length, rather than waiting until
1101//we have all of the output.
1102typedef std::basic_stringbuf<char,
1103 std::char_traits<char>,
1104 std::allocator<char> > char_stringbuf;
1105struct my_stringbuf : public char_stringbuf
1106{
1107private:
1108 std::streamsize written;
1109 boost::function1<void, int> on_write;
1110 std::streamsize last_call;
1111 std::streamsize call_every;
1112 bool clear;
1113public:
1114 my_stringbuf() : char_stringbuf(),
1115 written(0),
1116 last_call(0),
1117 call_every(constants::automate_stdio_size)
1118 {}
1119 virtual std::streamsize
1120 xsputn(const char_stringbuf::char_type* __s, std::streamsize __n)
1121 {
1122 std::streamsize ret=char_stringbuf::xsputn(__s, __n);
1123 written+=__n;
1124 while(written>=last_call+call_every)
1125 {
1126 if(on_write)
1127 on_write(call_every);
1128 last_call+=call_every;
1129 }
1130 return ret;
1131 }
1132 virtual int sync()
1133 {
1134 int ret=char_stringbuf::sync();
1135 if(on_write)
1136 on_write(-1);
1137 last_call=written;
1138 return ret;
1139 }
1140 void set_on_write(boost::function1<void, int> x)
1141 {
1142 on_write = x;
1143 }
1144};
1145
1146void print_some_output(int cmdnum,
1147 int err,
1148 bool last,
1149 std::string const & text,
1150 std::ostream & s,
1151 int & pos,
1152 int size)
1153{
1154 if(size==-1)
1155 {
1156 while(text.size()-pos > constants::automate_stdio_size)
1157 {
1158 s<<cmdnum<<':'<<err<<':'<<'m'<<':';
1159 s<<constants::automate_stdio_size<<':'
1160 <<text.substr(pos, constants::automate_stdio_size);
1161 pos+=constants::automate_stdio_size;
1162 s.flush();
1163 }
1164 s<<cmdnum<<':'<<err<<':'<<(last?'l':'m')<<':';
1165 s<<(text.size()-pos)<<':'<<text.substr(pos);
1166 pos=text.size();
1167 }
1168 else
1169 {
1170 I((unsigned int)(size) <= constants::automate_stdio_size);
1171 s<<cmdnum<<':'<<err<<':'<<(last?'l':'m')<<':';
1172 s<<size<<':'<<text.substr(pos, size);
1173 pos+=size;
1174 }
1175 s.flush();
1176}
1177
1178static ssize_t
1179automate_stdio_read(int d, void *buf, size_t nbytes)
1180{
1181 ssize_t rv;
1182
1183 rv = read(d, buf, nbytes);
1184
1185 E(rv >= 0, F("read from client failed with error code: %d") % rv);
1186 return rv;
1187}
1188
1189static void
1190automate_stdio(std::vector<utf8> args,
1191 std::string const & help_name,
1192 app_state & app,
1193 std::ostream & output)
1194{
1195 if (args.size() != 0)
1196 throw usage(help_name);
1197 int cmdnum = 0;
1198 char c;
1199 ssize_t n=1;
1200 while(n)//while(!EOF)
1201 {
1202 std::string x;
1203 utf8 cmd;
1204 args.clear();
1205 bool first=true;
1206 int toklen=0;
1207 bool firstchar=true;
1208 for(n=automate_stdio_read(0, &c, 1); c != 'l' && n; n=automate_stdio_read(0, &c, 1))
1209 ;
1210 for(n=automate_stdio_read(0, &c, 1); c!='e' && n; n=automate_stdio_read(0, &c, 1))
1211 {
1212 if(c<='9' && c>='0')
1213 {
1214 toklen=(toklen*10)+(c-'0');
1215 }
1216 else if(c == ':')
1217 {
1218 char *tok=new char[toklen];
1219 int count=0;
1220 while(count<toklen)
1221 count+=automate_stdio_read(0, tok+count, toklen-count);
1222 if(first)
1223 cmd=utf8(std::string(tok, toklen));
1224 else
1225 args.push_back(utf8(std::string(tok, toklen)));
1226 toklen=0;
1227 delete[] tok;
1228 first=false;
1229 }
1230 else
1231 {
1232 N(false, F("Bad input to automate stdio"));
1233 }
1234 firstchar=false;
1235 }
1236 if(cmd() != "")
1237 {
1238 int outpos=0;
1239 int err;
1240 std::ostringstream s;
1241 my_stringbuf sb;
1242 sb.set_on_write(boost::bind(print_some_output,
1243 cmdnum,
1244 boost::ref(err),
1245 false,
1246 boost::bind(&my_stringbuf::str, &sb),
1247 boost::ref(output),
1248 boost::ref(outpos),
1249 _1));
1250 s.std::basic_ios<char, std::char_traits<char> >::rdbuf(&sb);
1251 try
1252 {
1253 err=0;
1254 automate_command(cmd, args, help_name, app, s);
1255 }
1256 catch(usage & u)
1257 {
1258 if(sb.str().size())
1259 s.flush();
1260 err=1;
1261 commands::explain_usage(help_name, s);
1262 }
1263 catch(informative_failure & f)
1264 {
1265 if(sb.str().size())
1266 s.flush();
1267 err=2;
1268 //Do this instead of printing f.what directly so the output
1269 //will be split into properly-sized blocks automatically.
1270 s<<f.what;
1271 }
1272 print_some_output(cmdnum, err, true, sb.str(),
1273 output, outpos, -1);
1274 }
1275 cmdnum++;
1276 }
1277}
1278
1279// Name: keys
1280// Arguments: none
1281// Added in: 1.1
1282// Purpose: Prints all keys in the keystore, and if a database is given
1283// also all keys in the database, in basic_io format.
1284// Output format: For each key, a basic_io stanza is printed. The items in
1285// the stanza are:
1286// name - the key identifier
1287// public_hash - the hash of the public half of the key
1288// private_hash - the hash of the private half of the key
1289// public_location - where the public half of the key is stored
1290// private_location - where the private half of the key is stored
1291// The *_location items may have multiple values, as shown below
1292// for public_location.
1293// If the private key does not exist, then the private_hash and
1294// private_location items will be absent.
1295//
1296// Sample output:
1297// name "tbrownaw@gmail.com"
1298// public_hash [475055ec71ad48f5dfaf875b0fea597b5cbbee64]
1299// private_hash [7f76dae3f91bb48f80f1871856d9d519770b7f8a]
1300// public_location "database" "keystore"
1301// private_location "keystore"
1302//
1303// name "njs@pobox.com"
1304// public_hash [de84b575d5e47254393eba49dce9dc4db98ed42d]
1305// public_location "database"
1306//
1307// name "foo@bar.com"
1308// public_hash [7b6ce0bd83240438e7a8c7c207d8654881b763f6]
1309// private_hash [bfc3263e3257087f531168850801ccefc668312d]
1310// public_location "keystore"
1311// private_location "keystore"
1312//
1313// Error conditions: None.
1314static void
1315automate_keys(std::vector<utf8> args, std::string const & help_name,
1316 app_state & app, std::ostream & output)
1317{
1318 if (args.size() != 0)
1319 throw usage(help_name);
1320 std::vector<rsa_keypair_id> dbkeys;
1321 std::vector<rsa_keypair_id> kskeys;
1322 // public_hash, private_hash, public_location, private_location
1323 std::map<std::string, boost::tuple<hexenc<id>, hexenc<id>,
1324 std::vector<std::string>,
1325 std::vector<std::string> > > items;
1326 if (app.db.database_specified())
1327 {
1328 transaction_guard guard(app.db, false);
1329 app.db.get_key_ids("", dbkeys);
1330 guard.commit();
1331 }
1332 app.keys.get_key_ids("", kskeys);
1333
1334 for (std::vector<rsa_keypair_id>::iterator i = dbkeys.begin();
1335 i != dbkeys.end(); i++)
1336 {
1337 base64<rsa_pub_key> pub_encoded;
1338 hexenc<id> hash_code;
1339
1340 app.db.get_key(*i, pub_encoded);
1341 key_hash_code(*i, pub_encoded, hash_code);
1342 items[(*i)()].get<0>() = hash_code;
1343 items[(*i)()].get<2>().push_back("database");
1344 }
1345
1346 for (std::vector<rsa_keypair_id>::iterator i = kskeys.begin();
1347 i != kskeys.end(); i++)
1348 {
1349 keypair kp;
1350 hexenc<id> privhash, pubhash;
1351 app.keys.get_key_pair(*i, kp);
1352 key_hash_code(*i, kp.pub, pubhash);
1353 key_hash_code(*i, kp.priv, privhash);
1354 items[(*i)()].get<0>() = pubhash;
1355 items[(*i)()].get<1>() = privhash;
1356 items[(*i)()].get<2>().push_back("keystore");
1357 items[(*i)()].get<3>().push_back("keystore");
1358 }
1359 basic_io::printer prt(output);
1360 for (std::map<std::string, boost::tuple<hexenc<id>, hexenc<id>,
1361 std::vector<std::string>,
1362 std::vector<std::string> > >::iterator
1363 i = items.begin(); i != items.end(); ++i)
1364 {
1365 basic_io::stanza stz;
1366 stz.push_str_pair("name", i->first);
1367 stz.push_hex_pair("public_hash", i->second.get<0>()());
1368 if (!i->second.get<1>()().empty())
1369 stz.push_hex_pair("private_hash", i->second.get<1>()());
1370 stz.push_str_multi("public_location", i->second.get<2>());
1371 if (!i->second.get<3>().empty())
1372 stz.push_str_multi("private_location", i->second.get<3>());
1373 prt.print_stanza(stz);
1374 }
1375}
1376
1377
1378void
1379automate_command(utf8 cmd, std::vector<utf8> args,
1380 std::string const & root_cmd_name,
1381 app_state & app,
1382 std::ostream & output)
1383{
1384 if (cmd() == "interface_version")
1385 automate_interface_version(args, root_cmd_name, app, output);
1386 else if (cmd() == "heads")
1387 automate_heads(args, root_cmd_name, app, output);
1388 else if (cmd() == "ancestors")
1389 automate_ancestors(args, root_cmd_name, app, output);
1390 else if (cmd() == "descendents")
1391 automate_descendents(args, root_cmd_name, app, output);
1392 else if (cmd() == "erase_ancestors")
1393 automate_erase_ancestors(args, root_cmd_name, app, output);
1394 else if (cmd() == "toposort")
1395 automate_toposort(args, root_cmd_name, app, output);
1396 else if (cmd() == "ancestry_difference")
1397 automate_ancestry_difference(args, root_cmd_name, app, output);
1398 else if (cmd() == "leaves")
1399 automate_leaves(args, root_cmd_name, app, output);
1400 else if (cmd() == "parents")
1401 automate_parents(args, root_cmd_name, app, output);
1402 else if (cmd() == "children")
1403 automate_children(args, root_cmd_name, app, output);
1404 else if (cmd() == "graph")
1405 automate_graph(args, root_cmd_name, app, output);
1406 else if (cmd() == "select")
1407 automate_select(args, root_cmd_name, app, output);
1408 else if (cmd() == "inventory")
1409 automate_inventory(args, root_cmd_name, app, output);
1410 else if (cmd() == "attributes")
1411 automate_attributes(args, root_cmd_name, app, output);
1412 else if (cmd() == "stdio")
1413 automate_stdio(args, root_cmd_name, app, output);
1414 else if (cmd() == "certs")
1415 automate_certs(args, root_cmd_name, app, output);
1416 else if (cmd() == "get_revision")
1417 automate_get_revision(args, root_cmd_name, app, output);
1418 else if (cmd() == "get_manifest")
1419 automate_get_manifest(args, root_cmd_name, app, output);
1420 else if (cmd() == "get_file")
1421 automate_get_file(args, root_cmd_name, app, output);
1422 else if (cmd() == "keys")
1423 automate_keys(args, root_cmd_name, app, output);
1424 else
1425 throw usage(root_cmd_name);
1426}

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status