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