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