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

Archive Download this file

Branches

Tags

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