monotone

monotone Mtn Source Tree

Root/automate.cc

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

Archive Download this file

Branches

Tags

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