monotone

monotone Mtn Source Tree

Root/automate.cc

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