monotone

monotone Mtn Source Tree

Root/automate.cc

1// Copyright (C) 2004 Nathaniel Smith <njs@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <algorithm>
11#include <iostream>
12#include <iterator>
13#include <sstream>
14#include <string>
15#include <unistd.h>
16#include <vector>
17
18#include <boost/bind.hpp>
19#include <boost/function.hpp>
20#include <boost/tuple/tuple.hpp>
21
22#include "app_state.hh"
23#include "basic_io.hh"
24#include "cert.hh"
25#include "cmd.hh"
26#include "commands.hh"
27#include "constants.hh"
28#include "keys.hh"
29#include "packet.hh"
30#include "restrictions.hh"
31#include "revision.hh"
32#include "transforms.hh"
33#include "vocab.hh"
34#include "globish.hh"
35
36using std::allocator;
37using std::basic_ios;
38using std::basic_stringbuf;
39using std::char_traits;
40using std::endl;
41using std::inserter;
42using std::make_pair;
43using std::map;
44using std::multimap;
45using std::ostream;
46using std::ostringstream;
47using std::pair;
48using std::set;
49using std::sort;
50using std::streamsize;
51using std::string;
52using std::vector;
53
54
55// Name: heads
56// Arguments:
57// 1: branch name (optional, default branch is used if non-existant)
58// Added in: 0.0
59// Purpose: Prints the heads of the given branch.
60// Output format: A list of revision ids, in hexadecimal, each followed by a
61// newline. Revision ids are printed in alphabetically sorted order.
62// Error conditions: If the branch does not exist, prints nothing. (There are
63// no heads.)
64AUTOMATE(heads, N_("[BRANCH]"))
65{
66 if (args.size() > 1)
67 throw usage(help_name);
68
69 if (args.size() ==1 ) {
70 // branchname was explicitly given, use that
71 app.set_branch(idx(args, 0));
72 }
73 set<revision_id> heads;
74 get_branch_heads(app.branch_name(), app, heads);
75 for (set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
76 output << (*i).inner()() << endl;
77}
78
79// Name: ancestors
80// Arguments:
81// 1 or more: revision ids
82// Added in: 0.2
83// Purpose: Prints the ancestors (exclusive) of the given revisions
84// Output format: A list of revision ids, in hexadecimal, each followed by a
85// newline. Revision ids are printed in alphabetically sorted order.
86// Error conditions: If any of the revisions do not exist, prints nothing to
87// stdout, prints an error message to stderr, and exits with status 1.
88AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 [...]]]"))
89{
90 if (args.size() == 0)
91 throw usage(help_name);
92
93 set<revision_id> ancestors;
94 vector<revision_id> frontier;
95 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
96 {
97 revision_id rid((*i)());
98 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
99 frontier.push_back(rid);
100 }
101 while (!frontier.empty())
102 {
103 revision_id rid = frontier.back();
104 frontier.pop_back();
105 if(!null_id(rid)) {
106 set<revision_id> parents;
107 app.db.get_revision_parents(rid, parents);
108 for (set<revision_id>::const_iterator i = parents.begin();
109 i != parents.end(); ++i)
110 {
111 if (ancestors.find(*i) == ancestors.end())
112 {
113 frontier.push_back(*i);
114 ancestors.insert(*i);
115 }
116 }
117 }
118 }
119 for (set<revision_id>::const_iterator i = ancestors.begin();
120 i != ancestors.end(); ++i)
121 if (!null_id(*i))
122 output << (*i).inner()() << endl;
123}
124
125
126// Name: descendents
127// Arguments:
128// 1 or more: revision ids
129// Added in: 0.1
130// Purpose: Prints the descendents (exclusive) of the given revisions
131// Output format: A list of revision ids, in hexadecimal, each followed by a
132// newline. Revision ids are printed in alphabetically sorted order.
133// Error conditions: If any of the revisions do not exist, prints nothing to
134// stdout, prints an error message to stderr, and exits with status 1.
135AUTOMATE(descendents, N_("REV1 [REV2 [REV3 [...]]]"))
136{
137 if (args.size() == 0)
138 throw usage(help_name);
139
140 set<revision_id> descendents;
141 vector<revision_id> frontier;
142 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
143 {
144 revision_id rid((*i)());
145 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
146 frontier.push_back(rid);
147 }
148 while (!frontier.empty())
149 {
150 revision_id rid = frontier.back();
151 frontier.pop_back();
152 set<revision_id> children;
153 app.db.get_revision_children(rid, children);
154 for (set<revision_id>::const_iterator i = children.begin();
155 i != children.end(); ++i)
156 {
157 if (descendents.find(*i) == descendents.end())
158 {
159 frontier.push_back(*i);
160 descendents.insert(*i);
161 }
162 }
163 }
164 for (set<revision_id>::const_iterator i = descendents.begin();
165 i != descendents.end(); ++i)
166 output << (*i).inner()() << endl;
167}
168
169
170// Name: erase_ancestors
171// Arguments:
172// 0 or more: revision ids
173// Added in: 0.1
174// Purpose: Prints all arguments, except those that are an ancestor of some
175// other argument. One way to think about this is that it prints the
176// minimal elements of the given set, under the ordering imposed by the
177// "child of" relation. Another way to think of it is if the arguments were
178// a branch, then we print the heads of that branch.
179// Output format: A list of revision ids, in hexadecimal, each followed by a
180// newline. Revision ids are printed in alphabetically sorted order.
181// Error conditions: If any of the revisions do not exist, prints nothing to
182// stdout, prints an error message to stderr, and exits with status 1.
183AUTOMATE(erase_ancestors, N_("[REV1 [REV2 [REV3 [...]]]]"))
184{
185 set<revision_id> revs;
186 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
187 {
188 revision_id rid((*i)());
189 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
190 revs.insert(rid);
191 }
192 erase_ancestors(revs, app);
193 for (set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
194 output << (*i).inner()() << endl;
195}
196
197// Name: attributes
198// Arguments:
199// 1: file name (optional, if non-existant prints all files with attributes)
200// Added in: 1.0
201// Purpose: Prints all attributes for a file, or all all files with attributes
202// if a file name provided.
203// Output format: A list of file names in alphabetically sorted order,
204// or a list of attributes if a file name provided.
205// Error conditions: If the file name has no attributes, prints nothing.
206AUTOMATE(attributes, N_("[FILE]"))
207{
208 if (args.size() > 1)
209 throw usage(help_name);
210
211 roster_t base, current;
212 temp_node_id_source nis;
213
214 get_base_and_current_roster_shape(base, current, nis, app);
215
216 if (args.size() == 1)
217 {
218 // a filename was given, if it has attributes, print them
219 split_path path;
220 file_path_external(idx(args,0)).split(path);
221
222 if (current.has_node(path))
223 {
224 node_t n = current.get_node(path);
225 for (full_attr_map_t::const_iterator i = n->attrs.begin();
226 i != n->attrs.end(); ++i)
227 if (i->second.first)
228 output << i->first << endl;
229 }
230 }
231 else
232 {
233 for (node_map::const_iterator i = current.all_nodes().begin();
234 i != current.all_nodes().end(); ++i)
235 {
236 if (!i->second->attrs.empty())
237 {
238 split_path path;
239 current.get_name(i->first, path);
240 output << file_path(path) << endl;
241 }
242 }
243 }
244}
245
246// Name: toposort
247// Arguments:
248// 0 or more: revision ids
249// Added in: 0.1
250// Purpose: Prints all arguments, topologically sorted. I.e., if A is an
251// ancestor of B, then A will appear before B in the output list.
252// Output format: A list of revision ids, in hexadecimal, each followed by a
253// newline. Revisions are printed in topologically sorted order.
254// Error conditions: If any of the revisions do not exist, prints nothing to
255// stdout, prints an error message to stderr, and exits with status 1.
256AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 [...]]]]"))
257{
258 set<revision_id> revs;
259 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
260 {
261 revision_id rid((*i)());
262 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
263 revs.insert(rid);
264 }
265 vector<revision_id> sorted;
266 toposort(revs, sorted, app);
267 for (vector<revision_id>::const_iterator i = sorted.begin();
268 i != sorted.end(); ++i)
269 output << (*i).inner()() << endl;
270}
271
272// Name: ancestry_difference
273// Arguments:
274// 1: a revision id
275// 0 or more further arguments: also revision ids
276// Added in: 0.1
277// Purpose: Prints all ancestors of the first revision A, that are not also
278// ancestors of the other revision ids, the "Bs". For purposes of this
279// command, "ancestor" is an inclusive term; that is, A is an ancestor of
280// one of the Bs, it will not be printed, but otherwise, it will be; and
281// none of the Bs will ever be printed. If A is a new revision, and Bs are
282// revisions that you have processed before, then this command tells you
283// which revisions are new since then.
284// Output format: A list of revision ids, in hexadecimal, each followed by a
285// newline. Revisions are printed in topologically sorted order.
286// Error conditions: If any of the revisions do not exist, prints nothing to
287// stdout, prints an error message to stderr, and exits with status 1.
288AUTOMATE(ancestry_difference, N_("NEW_REV [OLD_REV1 [OLD_REV2 [...]]]"))
289{
290 if (args.size() == 0)
291 throw usage(help_name);
292
293 revision_id a;
294 set<revision_id> bs;
295 vector<utf8>::const_iterator i = args.begin();
296 a = revision_id((*i)());
297 N(app.db.revision_exists(a), F("No such revision %s") % a);
298 for (++i; i != args.end(); ++i)
299 {
300 revision_id b((*i)());
301 N(app.db.revision_exists(b), F("No such revision %s") % b);
302 bs.insert(b);
303 }
304 set<revision_id> ancestors;
305 ancestry_difference(a, bs, ancestors, app);
306
307 vector<revision_id> sorted;
308 toposort(ancestors, sorted, app);
309 for (vector<revision_id>::const_iterator i = sorted.begin();
310 i != sorted.end(); ++i)
311 output << (*i).inner()() << endl;
312}
313
314// Name: leaves
315// Arguments:
316// None
317// Added in: 0.1
318// Purpose: Prints the leaves of the revision graph, i.e., all revisions that
319// have no children. This is similar, but not identical to the
320// functionality of 'heads', which prints every revision in a branch, that
321// has no descendents in that branch. If every revision in the database was
322// in the same branch, then they would be identical. Generally, every leaf
323// is the head of some branch, but not every branch head is a leaf.
324// Output format: A list of revision ids, in hexadecimal, each followed by a
325// newline. Revision ids are printed in alphabetically sorted order.
326// Error conditions: None.
327AUTOMATE(leaves, N_(""))
328{
329 if (args.size() != 0)
330 throw usage(help_name);
331
332 // this might be more efficient in SQL, but for now who cares.
333 set<revision_id> leaves;
334 app.db.get_revision_ids(leaves);
335 multimap<revision_id, revision_id> graph;
336 app.db.get_revision_ancestry(graph);
337 for (multimap<revision_id, revision_id>::const_iterator
338 i = graph.begin(); i != graph.end(); ++i)
339 leaves.erase(i->first);
340 for (set<revision_id>::const_iterator i = leaves.begin();
341 i != leaves.end(); ++i)
342 output << (*i).inner()() << endl;
343}
344
345// Name: parents
346// Arguments:
347// 1: a revision id
348// Added in: 0.2
349// Purpose: Prints the immediate ancestors of the given revision, i.e., the
350// parents.
351// Output format: A list of revision ids, in hexadecimal, each followed by a
352// newline. Revision ids are printed in alphabetically sorted order.
353// Error conditions: If the revision does not exist, prints nothing to stdout,
354// prints an error message to stderr, and exits with status 1.
355AUTOMATE(parents, N_("REV"))
356{
357 if (args.size() != 1)
358 throw usage(help_name);
359 revision_id rid(idx(args, 0)());
360 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
361 set<revision_id> parents;
362 app.db.get_revision_parents(rid, parents);
363 for (set<revision_id>::const_iterator i = parents.begin();
364 i != parents.end(); ++i)
365 if (!null_id(*i))
366 output << (*i).inner()() << endl;
367}
368
369// Name: children
370// Arguments:
371// 1: a revision id
372// Added in: 0.2
373// Purpose: Prints the immediate descendents of the given revision, i.e., the
374// children.
375// Output format: A list of revision ids, in hexadecimal, each followed by a
376// newline. Revision ids are printed in alphabetically sorted order.
377// Error conditions: If the revision does not exist, prints nothing to stdout,
378// prints an error message to stderr, and exits with status 1.
379AUTOMATE(children, N_("REV"))
380{
381 if (args.size() != 1)
382 throw usage(help_name);
383 revision_id rid(idx(args, 0)());
384 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
385 set<revision_id> children;
386 app.db.get_revision_children(rid, children);
387 for (set<revision_id>::const_iterator i = children.begin();
388 i != children.end(); ++i)
389 if (!null_id(*i))
390 output << (*i).inner()() << endl;
391}
392
393// Name: graph
394// Arguments:
395// None
396// Added in: 0.2
397// Purpose: Prints out the complete ancestry graph of this database.
398// Output format:
399// Each line begins with a revision id. Following this are zero or more
400// space-prefixed revision ids. Each revision id after the first is a
401// parent (in the sense of 'automate parents') of the first. For instance,
402// the following are valid lines:
403// 07804171823d963f78d6a0ff1763d694dd74ff40
404// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01
405// 07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01 a02e7a1390e3e4745c31be922f03f56450c13dce
406// The first would indicate that 07804171823d963f78d6a0ff1763d694dd74ff40
407// was a root node; the second would indicate that it had one parent, and
408// the third would indicate that it had two parents, i.e., was a merge.
409//
410// The output as a whole is alphabetically sorted; additionally, the parents
411// within each line are alphabetically sorted.
412// Error conditions: None.
413AUTOMATE(graph, N_(""))
414{
415 if (args.size() != 0)
416 throw usage(help_name);
417
418 multimap<revision_id, revision_id> edges_mmap;
419 map<revision_id, set<revision_id> > child_to_parents;
420
421 app.db.get_revision_ancestry(edges_mmap);
422
423 for (multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
424 i != edges_mmap.end(); ++i)
425 {
426 if (child_to_parents.find(i->second) == child_to_parents.end())
427 child_to_parents.insert(make_pair(i->second, set<revision_id>()));
428 if (null_id(i->first))
429 continue;
430 map<revision_id, set<revision_id> >::iterator
431 j = child_to_parents.find(i->second);
432 I(j->first == i->second);
433 j->second.insert(i->first);
434 }
435
436 for (map<revision_id, set<revision_id> >::const_iterator
437 i = child_to_parents.begin();
438 i != child_to_parents.end(); ++i)
439 {
440 output << (i->first).inner()();
441 for (set<revision_id>::const_iterator j = i->second.begin();
442 j != i->second.end(); ++j)
443 output << " " << (*j).inner()();
444 output << endl;
445 }
446}
447
448// Name: select
449// Arguments:
450// 1: selector
451// Added in: 0.2
452// Purpose: Prints all the revisions that match the given selector.
453// Output format: A list of revision ids, in hexadecimal, each followed by a
454// newline. Revision ids are printed in alphabetically sorted order.
455// Error conditions: None.
456AUTOMATE(select, N_("SELECTOR"))
457{
458 if (args.size() != 1)
459 throw usage(help_name);
460
461 vector<pair<selectors::selector_type, string> >
462 sels(selectors::parse_selector(args[0](), app));
463
464 // we jam through an "empty" selection on sel_ident type
465 set<string> completions;
466 selectors::selector_type ty = selectors::sel_ident;
467 selectors::complete_selector("", sels, ty, completions, app);
468
469 for (set<string>::const_iterator i = completions.begin();
470 i != completions.end(); ++i)
471 output << *i << endl;
472}
473
474// consider a changeset with the following
475//
476// deletions
477// renames from to
478// additions
479//
480// pre-state corresponds to deletions and the "from" side of renames
481// post-state corresponds to the "to" side of renames and additions
482// node-state corresponds to the state of the node with the given name
483//
484// pre/post state are related to the path rearrangement in _MTN/work
485// node state is related to the details of the resulting path
486
487struct inventory_item
488{
489 // pre/post rearrangement state
490 enum pstate
491 { UNCHANGED_PATH, ADDED_PATH, DROPPED_PATH, RENAMED_PATH }
492 pre_state, post_state;
493
494 enum nstate
495 { UNCHANGED_NODE, PATCHED_NODE, MISSING_NODE,
496 UNKNOWN_NODE, IGNORED_NODE }
497 node_state;
498
499 size_t pre_id, post_id;
500
501 inventory_item():
502 pre_state(UNCHANGED_PATH), post_state(UNCHANGED_PATH),
503 node_state(UNCHANGED_NODE),
504 pre_id(0), post_id(0) {}
505};
506
507typedef map<split_path, inventory_item> inventory_map;
508typedef map<split_path, split_path> rename_map; // this might be good in cset.hh
509typedef map<split_path, file_id> addition_map; // ditto
510
511static void
512inventory_pre_state(inventory_map & inventory,
513 path_set const & paths,
514 inventory_item::pstate pre_state,
515 size_t rename_id)
516{
517 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
518 {
519 L(FL("%d %d %s") % inventory[*i].pre_state % pre_state % file_path(*i));
520 I(inventory[*i].pre_state == inventory_item::UNCHANGED_PATH);
521 inventory[*i].pre_state = pre_state;
522 if (rename_id != 0)
523 {
524 I(inventory[*i].pre_id == 0);
525 inventory[*i].pre_id = rename_id;
526 }
527 }
528}
529
530static void
531inventory_post_state(inventory_map & inventory,
532 path_set const & paths,
533 inventory_item::pstate post_state,
534 size_t rename_id)
535{
536 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
537 {
538 L(FL("%d %d %s") % inventory[*i].post_state
539 % post_state % file_path(*i));
540 I(inventory[*i].post_state == inventory_item::UNCHANGED_PATH);
541 inventory[*i].post_state = post_state;
542 if (rename_id != 0)
543 {
544 I(inventory[*i].post_id == 0);
545 inventory[*i].post_id = rename_id;
546 }
547 }
548}
549
550static void
551inventory_node_state(inventory_map & inventory,
552 path_set const & paths,
553 inventory_item::nstate node_state)
554{
555 for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
556 {
557 L(FL("%d %d %s") % inventory[*i].node_state
558 % node_state % file_path(*i));
559 I(inventory[*i].node_state == inventory_item::UNCHANGED_NODE);
560 inventory[*i].node_state = node_state;
561 }
562}
563
564static void
565inventory_renames(inventory_map & inventory,
566 rename_map const & renames)
567{
568 path_set old_name;
569 path_set new_name;
570
571 static size_t rename_id = 1;
572
573 for (rename_map::const_iterator i = renames.begin();
574 i != renames.end(); i++)
575 {
576 old_name.clear();
577 new_name.clear();
578
579 old_name.insert(i->first);
580 new_name.insert(i->second);
581
582 inventory_pre_state(inventory, old_name,
583 inventory_item::RENAMED_PATH, rename_id);
584 inventory_post_state(inventory, new_name,
585 inventory_item::RENAMED_PATH, rename_id);
586
587 rename_id++;
588 }
589}
590
591static void
592extract_added_file_paths(addition_map const & additions, path_set & paths)
593{
594 for (addition_map::const_iterator i = additions.begin();
595 i != additions.end(); ++i)
596 {
597 paths.insert(i->first);
598 }
599}
600
601
602// Name: inventory
603// Arguments: none
604// Added in: 1.0
605
606// Purpose: Prints a summary of every file found in the workspace or its
607// associated base manifest. Each unique path is listed on a line
608// prefixed by three status characters and two numeric values used
609// for identifying renames. The three status characters are as
610// follows.
611//
612// column 1 pre-state
613// ' ' the path was unchanged in the pre-state
614// 'D' the path was deleted from the pre-state
615// 'R' the path was renamed from the pre-state name
616// column 2 post-state
617// ' ' the path was unchanged in the post-state
618// 'R' the path was renamed to the post-state name
619// 'A' the path was added to the post-state
620// column 3 node-state
621// ' ' the node is unchanged from the current roster
622// 'P' the node is patched to a new version
623// 'U' the node is unknown and not included in the roster
624// 'I' the node is ignored and not included in the roster
625// 'M' the node is missing but is included in the roster
626//
627// Output format: Each path is printed on its own line, prefixed by three
628// status characters as described above. The status is followed by a
629// single space and two numbers, each separated by a single space,
630// used for identifying renames. The numbers are followed by a
631// single space and then the pathname, which includes the rest of
632// the line. Directory paths are identified as ending with the "/"
633// character, file paths do not end in this character.
634//
635// Error conditions: If no workspace book keeping _MTN directory is found,
636// prints an error message to stderr, and exits with status 1.
637
638AUTOMATE(inventory, N_(""))
639{
640 if (args.size() != 0)
641 throw usage(help_name);
642
643 app.require_workspace();
644
645 temp_node_id_source nis;
646 roster_t base, curr;
647 inventory_map inventory;
648 cset cs; MM(cs);
649 path_set unchanged, changed, missing, known, unknown, ignored;
650
651 get_base_and_current_roster_shape(base, curr, nis, app);
652 make_cset(base, curr, cs);
653
654 // The current roster (curr) has the complete set of registered nodes
655 // conveniently with unchanged sha1 hash values.
656
657 // The cset (cs) has the list of drops/renames/adds that have
658 // occurred between the two rosters along with an empty list of
659 // deltas. this list is empty only because the current roster used
660 // to generate the cset does not have current hash values as
661 // recorded on the filesystem (because get_..._shape was used to
662 // build it).
663
664 path_set nodes_added(cs.dirs_added);
665 extract_added_file_paths(cs.files_added, nodes_added);
666
667 inventory_pre_state(inventory, cs.nodes_deleted,
668 inventory_item::DROPPED_PATH, 0);
669 inventory_renames(inventory, cs.nodes_renamed);
670 inventory_post_state(inventory, nodes_added,
671 inventory_item::ADDED_PATH, 0);
672
673 classify_roster_paths(curr, unchanged, changed, missing, app);
674 curr.extract_path_set(known);
675
676 path_restriction mask(app);
677 file_itemizer u(app, known, unknown, ignored, mask);
678 walk_tree(file_path(), u);
679
680 inventory_node_state(inventory, unchanged,
681 inventory_item::UNCHANGED_NODE);
682
683 inventory_node_state(inventory, changed,
684 inventory_item::PATCHED_NODE);
685
686 inventory_node_state(inventory, missing,
687 inventory_item::MISSING_NODE);
688
689 inventory_node_state(inventory, unknown,
690 inventory_item::UNKNOWN_NODE);
691
692 inventory_node_state(inventory, ignored,
693 inventory_item::IGNORED_NODE);
694
695 // FIXME: do we want to report on attribute changes here?!?
696
697 for (inventory_map::const_iterator i = inventory.begin();
698 i != inventory.end(); ++i)
699 {
700
701 string path_suffix;
702
703 if (curr.has_node(i->first))
704 {
705 // Explicitly skip the root dir for now. The trailing / dir
706 // format isn't going to work here.
707 node_t n = curr.get_node(i->first);
708 if (is_root_dir_t(n)) continue;
709 if (is_dir_t(n)) path_suffix = "/";
710 }
711 else if (directory_exists(file_path(i->first)))
712 {
713 path_suffix = "/";
714 }
715
716 switch (i->second.pre_state)
717 {
718 case inventory_item::UNCHANGED_PATH: output << " "; break;
719 case inventory_item::DROPPED_PATH: output << "D"; break;
720 case inventory_item::RENAMED_PATH: output << "R"; break;
721 default: I(false); // invalid pre_state
722 }
723
724 switch (i->second.post_state)
725 {
726 case inventory_item::UNCHANGED_PATH: output << " "; break;
727 case inventory_item::RENAMED_PATH: output << "R"; break;
728 case inventory_item::ADDED_PATH: output << "A"; break;
729 default: I(false); // invalid post_state
730 }
731
732 switch (i->second.node_state)
733 {
734 case inventory_item::UNCHANGED_NODE: output << " "; break;
735 case inventory_item::PATCHED_NODE: output << "P"; break;
736 case inventory_item::UNKNOWN_NODE: output << "U"; break;
737 case inventory_item::IGNORED_NODE: output << "I"; break;
738 case inventory_item::MISSING_NODE: output << "M"; break;
739 default: I(false); // invalid node_state
740 }
741
742 output << " " << i->second.pre_id
743 << " " << i->second.post_id
744 << " " << i->first;
745
746 // FIXME: it's possible that a directory was deleted and a file
747 // was added in it's place (or vice-versa) so we need something
748 // like pre/post node type indicators rather than a simple path
749 // suffix! ugh.
750
751 output << path_suffix;
752
753 output << endl;
754 }
755}
756
757// Name: get_revision
758// Arguments:
759// 1: a revision id (optional, determined from the workspace if
760// non-existant)
761// Added in: 1.0
762
763// Purpose: Prints change information for the specified revision id.
764// There are several changes that are described; each of these is
765// described by a different basic_io stanza. The first string pair
766// of each stanza indicates the type of change represented.
767//
768// All stanzas are formatted by basic_io. Stanzas are separated
769// by a blank line. Values will be escaped, '\' to '\\' and
770// '"' to '\"'.
771//
772// Possible values of this first value are along with an ordered list of
773// basic_io formatted stanzas that will be provided are:
774//
775// 'format_version'
776// used in case this format ever needs to change.
777// format: ('format_version', the string "1")
778// occurs: exactly once
779// 'new_manifest'
780// represents the new manifest associated with the revision.
781// format: ('new_manifest', manifest id)
782// occurs: exactly one
783// 'old_revision'
784// represents a parent revision.
785// format: ('old_revision', revision id)
786// occurs: either one or two times
787// 'delete
788// represents a file or directory that was deleted.
789// format: ('delete', path)
790// occurs: zero or more times
791// 'rename'
792// represents a file or directory that was renamed.
793// format: ('rename, old filename), ('to', new filename)
794// occurs: zero or more times
795// 'add_dir'
796// represents a directory that was added.
797// format: ('add_dir, path)
798// occurs: zero or more times
799// 'add_file'
800// represents a file that was added.
801// format: ('add_file', path), ('content', file id)
802// occurs: zero or more times
803// 'patch'
804// represents a file that was modified.
805// format: ('patch', filename), ('from', file id), ('to', file id)
806// occurs: zero or more times
807// 'clear'
808// represents an attr that was removed.
809// format: ('clear', filename), ('attr', attr name)
810// occurs: zero or more times
811// 'set'
812// represents an attr whose value was changed.
813// format: ('set', filename), ('attr', attr name), ('value', attr value)
814// occurs: zero or more times
815//
816// These stanzas will always occur in the order listed here; stanzas of
817// the same type will be sorted by the filename they refer to.
818// Error conditions: If the revision specified is unknown or invalid
819// prints an error message to stderr and exits with status 1.
820AUTOMATE(get_revision, N_("[REVID]"))
821{
822 if (args.size() > 1)
823 throw usage(help_name);
824
825 temp_node_id_source nis;
826 revision_data dat;
827 revision_id ident;
828
829 if (args.size() == 0)
830 {
831 roster_t old_roster, new_roster;
832 revision_id old_revision_id;
833 revision_t rev;
834
835 app.require_workspace();
836 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
837 update_current_roster_from_filesystem(new_roster, app);
838
839 get_revision_id(old_revision_id);
840 make_revision(old_revision_id, old_roster, new_roster, rev);
841
842 calculate_ident(rev, ident);
843 write_revision(rev, dat);
844 }
845 else
846 {
847 ident = revision_id(idx(args, 0)());
848 N(app.db.revision_exists(ident),
849 F("no revision %s found in database") % ident);
850 app.db.get_revision(ident, dat);
851 }
852
853 L(FL("dumping revision %s") % ident);
854 output.write(dat.inner()().data(), dat.inner()().size());
855}
856
857// Name: get_base_revision_id
858// Arguments: none
859// Added in: 2.0
860// Purpose: Prints the revision id the current workspace is based
861// on. This is the value stored in _MTN/revision
862// Error conditions: If no workspace book keeping _MTN directory is found,
863// prints an error message to stderr, and exits with status 1.
864AUTOMATE(get_base_revision_id, N_(""))
865{
866 if (args.size() > 0)
867 throw usage(help_name);
868
869 app.require_workspace();
870
871 revision_id rid;
872 get_revision_id(rid);
873 output << rid << endl;
874}
875
876// Name: get_current_revision_id
877// Arguments: none
878// Added in: 2.0
879// Purpose: Prints the revision id of the current workspace. This is the
880// id of the revision that would be committed by an unrestricted
881// commit calculated from _MTN/revision, _MTN/work and any edits to
882// files in the workspace.
883// Error conditions: If no workspace book keeping _MTN directory is found,
884// prints an error message to stderr, and exits with status 1.
885AUTOMATE(get_current_revision_id, N_(""))
886{
887 if (args.size() > 0)
888 throw usage(help_name);
889
890 app.require_workspace();
891
892 roster_t old_roster, new_roster;
893 revision_id old_revision_id, new_revision_id;
894 revision_t rev;
895 temp_node_id_source nis;
896
897 app.require_workspace();
898 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
899 update_current_roster_from_filesystem(new_roster, app);
900
901 get_revision_id(old_revision_id);
902 make_revision(old_revision_id, old_roster, new_roster, rev);
903
904 calculate_ident(rev, new_revision_id);
905
906 output << new_revision_id << endl;
907}
908
909// Name: get_manifest_of
910// Arguments:
911// 1: a revision id (optional, determined from the workspace if not given)
912// Added in: 2.0
913// Purpose: Prints the contents of the manifest associated with the
914// given revision ID.
915//
916// Output format:
917// There is one basic_io stanza for each file or directory in the
918// manifest.
919//
920// All stanzas are formatted by basic_io. Stanzas are separated
921// by a blank line. Values will be escaped, '\' to '\\' and
922// '"' to '\"'.
923//
924// Possible values of this first value are along with an ordered list of
925// basic_io formatted stanzas that will be provided are:
926//
927// 'format_version'
928// used in case this format ever needs to change.
929// format: ('format_version', the string "1")
930// occurs: exactly once
931// 'dir':
932// represents a directory. The path "" (the empty string) is used
933// to represent the root of the tree.
934// format: ('dir', pathname)
935// occurs: one or more times
936// 'file':
937// represents a file.
938// format: ('file', pathname), ('content', file id)
939// occurs: zero or more times
940//
941// In addition, 'dir' and 'file' stanzas may have attr information
942// included. These are appended to the stanza below the basic
943// dir/file information, with one line describing each attr. These
944// lines take the form ('attr', attr name, attr value).
945//
946// Stanzas are sorted by the path string.
947//
948// Error conditions: If the revision ID specified is unknown or
949// invalid prints an error message to stderr and exits with status 1.
950AUTOMATE(get_manifest_of, N_("[REVID]"))
951{
952 if (args.size() > 1)
953 throw usage(help_name);
954
955 roster_data dat;
956 manifest_id mid;
957 roster_t old_roster, new_roster;
958 temp_node_id_source nis;
959
960 if (args.size() == 0)
961 {
962 revision_id old_revision_id;
963
964 app.require_workspace();
965 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
966 update_current_roster_from_filesystem(new_roster, app);
967 }
968 else
969 {
970 revision_id rid = revision_id(idx(args, 0)());
971 N(app.db.revision_exists(rid),
972 F("no revision %s found in database") % rid);
973 app.db.get_roster(rid, new_roster);
974 }
975
976 calculate_ident(new_roster, mid);
977 write_manifest_of_roster(new_roster, dat);
978 L(FL("dumping manifest %s") % mid);
979 output.write(dat.inner()().data(), dat.inner()().size());
980}
981
982
983// Name: get_file
984// Arguments:
985// 1: a file id
986// Added in: 1.0
987// Purpose: Prints the contents of the specified file.
988//
989// Output format: The file contents are output without modification.
990//
991// Error conditions: If the file id specified is unknown or invalid prints
992// an error message to stderr and exits with status 1.
993AUTOMATE(get_file, N_("FILEID"))
994{
995 if (args.size() != 1)
996 throw usage(help_name);
997
998 file_id ident(idx(args, 0)());
999 N(app.db.file_version_exists(ident),
1000 F("no file version %s found in database") % ident);
1001
1002 file_data dat;
1003 L(FL("dumping file %s") % ident);
1004 app.db.get_file_version(ident, dat);
1005 output.write(dat.inner()().data(), dat.inner()().size());
1006}
1007
1008// Name: packet_for_rdata
1009// Arguments:
1010// 1: a revision id
1011// Added in: 2.0
1012// Purpose: Prints the revision data in packet format
1013//
1014// Output format: revision data in "monotone read" compatible packet
1015// format
1016//
1017// Error conditions: If the revision id specified is unknown or
1018// invalid prints an error message to stderr and exits with status 1.
1019AUTOMATE(packet_for_rdata, N_("REVID"))
1020{
1021 if (args.size() != 1)
1022 throw usage(help_name);
1023
1024 packet_writer pw(output);
1025
1026 revision_id r_id(idx(args, 0)());
1027 revision_data r_data;
1028
1029 N(app.db.revision_exists(r_id),
1030 F("no such revision '%s'") % r_id);
1031 app.db.get_revision(r_id, r_data);
1032 pw.consume_revision_data(r_id,r_data);
1033}
1034
1035// Name: packets_for_certs
1036// Arguments:
1037// 1: a revision id
1038// Added in: 2.0
1039// Purpose: Prints the certs associated with a revision in packet format
1040//
1041// Output format: certs in "monotone read" compatible packet format
1042//
1043// Error conditions: If the revision id specified is unknown or
1044// invalid prints an error message to stderr and exits with status 1.
1045AUTOMATE(packets_for_certs, N_("REVID"))
1046{
1047 if (args.size() != 1)
1048 throw usage(help_name);
1049
1050 packet_writer pw(output);
1051
1052 revision_id r_id(idx(args, 0)());
1053 vector< revision<cert> > certs;
1054
1055 N(app.db.revision_exists(r_id),
1056 F("no such revision '%s'") % r_id);
1057 app.db.get_revision_certs(r_id, certs);
1058 for (size_t i = 0; i < certs.size(); ++i)
1059 pw.consume_revision_cert(idx(certs,i));
1060}
1061
1062// Name: packet_for_fdata
1063// Arguments:
1064// 1: a file id
1065// Added in: 2.0
1066// Purpose: Prints the file data in packet format
1067//
1068// Output format: file data in "monotone read" compatible packet format
1069//
1070// Error conditions: If the file id specified is unknown or invalid
1071// prints an error message to stderr and exits with status 1.
1072AUTOMATE(packet_for_fdata, N_("FILEID"))
1073{
1074 if (args.size() != 1)
1075 throw usage(help_name);
1076
1077 packet_writer pw(output);
1078
1079 file_id f_id(idx(args, 0)());
1080 file_data f_data;
1081
1082 N(app.db.file_version_exists(f_id),
1083 F("no such file '%s'") % f_id);
1084 app.db.get_file_version(f_id, f_data);
1085 pw.consume_file_data(f_id,f_data);
1086}
1087
1088// Name: packet_for_fdelta
1089// Arguments:
1090// 1: a file id
1091// 2: a file id
1092// Added in: 2.0
1093// Purpose: Prints the file delta in packet format
1094//
1095// Output format: file delta in "monotone read" compatible packet format
1096//
1097// Error conditions: If any of the file ids specified are unknown or
1098// invalid prints an error message to stderr and exits with status 1.
1099AUTOMATE(packet_for_fdelta, N_("OLD_FILE NEW_FILE"))
1100{
1101 if (args.size() != 2)
1102 throw usage(help_name);
1103
1104 packet_writer pw(output);
1105
1106 file_id f_old_id(idx(args, 0)());
1107 file_id f_new_id(idx(args, 1)());
1108 file_data f_old_data, f_new_data;
1109
1110 N(app.db.file_version_exists(f_old_id),
1111 F("no such revision '%s'") % f_old_id);
1112 N(app.db.file_version_exists(f_new_id),
1113 F("no such revision '%s'") % f_new_id);
1114 app.db.get_file_version(f_old_id, f_old_data);
1115 app.db.get_file_version(f_new_id, f_new_data);
1116 delta del;
1117 diff(f_old_data.inner(), f_new_data.inner(), del);
1118 pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
1119}
1120
1121// Name: common_ancestors
1122// Arguments:
1123// 1 or more revision ids
1124// Added in: 2.1
1125// Purpose: Prints all revisions which are ancestors of all of the
1126// revisions given as arguments.
1127// Output format: A list of revision ids, in hexadecimal, each
1128// followed by a newline. Revisions are printed in alphabetically
1129// sorted order.
1130// Error conditions: If any of the revisions do not exist, prints
1131// nothing to stdout, prints an error message to stderr, and exits
1132// with status 1.
1133AUTOMATE(common_ancestors, N_("REV1 [REV2 [REV3 [...]]]"))
1134{
1135 if (args.size() == 0)
1136 throw usage(help_name);
1137
1138 set<revision_id> ancestors, common_ancestors;
1139 vector<revision_id> frontier;
1140 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
1141 {
1142 revision_id rid((*i)());
1143 N(app.db.revision_exists(rid), F("No such revision %s") % rid);
1144 ancestors.clear();
1145 ancestors.insert(rid);
1146 frontier.push_back(rid);
1147 while (!frontier.empty())
1148 {
1149 revision_id rid = frontier.back();
1150 frontier.pop_back();
1151 if(!null_id(rid))
1152 {
1153 set<revision_id> parents;
1154 app.db.get_revision_parents(rid, parents);
1155 for (set<revision_id>::const_iterator i = parents.begin();
1156 i != parents.end(); ++i)
1157 {
1158 if (ancestors.find(*i) == ancestors.end())
1159 {
1160 frontier.push_back(*i);
1161 ancestors.insert(*i);
1162 }
1163 }
1164 }
1165 }
1166 if (common_ancestors.empty())
1167 common_ancestors = ancestors;
1168 else
1169 {
1170 set<revision_id> common;
1171 set_intersection(ancestors.begin(), ancestors.end(),
1172 common_ancestors.begin(), common_ancestors.end(),
1173 inserter(common, common.begin()));
1174 common_ancestors = common;
1175 }
1176 }
1177
1178 for (set<revision_id>::const_iterator i = common_ancestors.begin();
1179 i != common_ancestors.end(); ++i)
1180 if (!null_id(*i))
1181 output << (*i).inner()() << endl;
1182}
1183
1184// Name: branches
1185// Arguments:
1186// None
1187// Added in: 2.2
1188// Purpose:
1189// Prints all branch certs present in the revision graph, that are not
1190// excluded by the lua hook 'ignore_branch'.
1191// Output format:
1192// Zero or more lines, each the name of a branch. The lines are printed
1193// in alphabetically sorted order.
1194// Error conditions:
1195// None.
1196AUTOMATE(branches, N_(""))
1197{
1198 if (args.size() > 0)
1199 throw usage(help_name);
1200
1201 vector<string> names;
1202
1203 app.db.get_branches(names);
1204 sort(names.begin(), names.end());
1205
1206 for (vector<string>::const_iterator i = names.begin();
1207 i != names.end(); ++i)
1208 if (!app.lua.hook_ignore_branch(*i))
1209 output << (*i) << endl;
1210}
1211
1212// Name: tags
1213// Arguments:
1214// A branch pattern (optional).
1215// Added in: 2.2
1216// Purpose:
1217// If a branch pattern is given, prints all tags that are attached to
1218// revisions on branches matched by the pattern; otherwise prints all tags
1219// of the revision graph.
1220//
1221// If a branch name is ignored by means of the lua hook 'ignore_branch',
1222// it is neither printed, nor can it be matched by a pattern.
1223// Output format:
1224// There is one basic_io stanza for each tag.
1225//
1226// All stanzas are formatted by basic_io. Stanzas are separated
1227// by a blank line. Values will be escaped, '\' to '\\' and
1228// '"' to '\"'.
1229//
1230// Each stanza has exactly the following four entries:
1231//
1232// 'tag'
1233// the value of the tag cert, i.e. the name of the tag
1234// 'revision'
1235// the hexadecimal id of the revision the tag is attached to
1236// 'signer'
1237// the name of the key used to sign the tag cert
1238// 'branches'
1239// a (possibly empty) list of all branches the tagged revision is on
1240//
1241// Stanzas are printed in arbitrary order.
1242// Error conditions:
1243// A run-time exception is thrown for illegal patterns.
1244AUTOMATE(tags, N_("[BRANCH_PATTERN]"))
1245{
1246 utf8 incl("*");
1247 bool filtering(false);
1248
1249 if (args.size() == 1) {
1250 incl = idx(args, 0);
1251 filtering = true;
1252 }
1253 else if (args.size() > 1)
1254 throw usage(name);
1255
1256 globish_matcher match(incl, utf8());
1257 basic_io::printer prt;
1258 basic_io::stanza stz;
1259 stz.push_str_pair(symbol("format_version"), "1");
1260 prt.print_stanza(stz);
1261
1262 vector<revision<cert> > tag_certs;
1263 app.db.get_revision_certs(tag_cert_name, tag_certs);
1264
1265 for (vector<revision<cert> >::const_iterator i = tag_certs.begin();
1266 i != tag_certs.end(); ++i) {
1267
1268 cert tagcert(i->inner());
1269 vector<revision<cert> > branch_certs;
1270 app.db.get_revision_certs(tagcert.ident, branch_cert_name, branch_certs);
1271
1272 bool show(!filtering);
1273 vector<string> branch_names;
1274
1275 for (vector<revision<cert> >::const_iterator j = branch_certs.begin();
1276 j != branch_certs.end(); ++j) {
1277
1278 cert branchcert(j->inner());
1279 cert_value branch;
1280 decode_base64(branchcert.value, branch);
1281 string branch_name(branch());
1282
1283 if (app.lua.hook_ignore_branch(branch_name))
1284 continue;
1285
1286 if (!show && match(branch_name))
1287 show = true;
1288 branch_names.push_back(branch_name);
1289 }
1290
1291 if (show) {
1292 basic_io::stanza stz;
1293 cert_value tag;
1294 decode_base64(tagcert.value, tag);
1295 stz.push_str_pair(symbol("tag"), tag());
1296 stz.push_hex_pair(symbol("revision"), tagcert.ident);
1297 stz.push_str_pair(symbol("signer"), tagcert.key());
1298 stz.push_str_multi(symbol("branches"), branch_names);
1299 prt.print_stanza(stz);
1300 }
1301 }
1302 output.write(prt.buf.data(), prt.buf.size());
1303}
1304
1305// Local Variables:
1306// mode: C++
1307// fill-column: 76
1308// c-file-style: "gnu"
1309// indent-tabs-mode: nil
1310// End:
1311// 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