monotone

monotone Mtn Source Tree

Root/cmd_diff_log.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include <deque>
12#include <map>
13#include <iostream>
14#include <sstream>
15#include <queue>
16
17#include "asciik.hh"
18#include "charset.hh"
19#include "cmd.hh"
20#include "diff_patch.hh"
21#include "file_io.hh"
22#include "restrictions.hh"
23#include "revision.hh"
24#include "rev_height.hh"
25#include "simplestring_xform.hh"
26#include "transforms.hh"
27#include "app_state.hh"
28
29using std::cout;
30using std::deque;
31using std::make_pair;
32using std::map;
33using std::ostream;
34using std::ostringstream;
35using std::pair;
36using std::set;
37using std::string;
38using std::vector;
39using std::priority_queue;
40
41using boost::lexical_cast;
42
43// The changes_summary structure holds a list all of files and directories
44// affected in a revision, and is useful in the 'log' command to print this
45// information easily. It has to be constructed from all cset objects
46// that belong to a revision.
47
48struct
49changes_summary
50{
51 cset cs;
52 changes_summary(void);
53 void add_change_set(cset const & cs);
54 void print(ostream & os, size_t max_cols) const;
55};
56
57changes_summary::changes_summary(void)
58{
59}
60
61void
62changes_summary::add_change_set(cset const & c)
63{
64 if (c.empty())
65 return;
66
67 // FIXME: not sure whether it matters for an informal summary
68 // object like this, but the pre-state names in deletes and renames
69 // are not really sensible to union; they refer to different trees
70 // so mixing them up in a single set is potentially ambiguous.
71
72 copy(c.nodes_deleted.begin(), c.nodes_deleted.end(),
73 inserter(cs.nodes_deleted, cs.nodes_deleted.begin()));
74
75 copy(c.files_added.begin(), c.files_added.end(),
76 inserter(cs.files_added, cs.files_added.begin()));
77
78 copy(c.dirs_added.begin(), c.dirs_added.end(),
79 inserter(cs.dirs_added, cs.dirs_added.begin()));
80
81 copy(c.nodes_renamed.begin(), c.nodes_renamed.end(),
82 inserter(cs.nodes_renamed, cs.nodes_renamed.begin()));
83
84 copy(c.deltas_applied.begin(), c.deltas_applied.end(),
85 inserter(cs.deltas_applied, cs.deltas_applied.begin()));
86
87 copy(c.attrs_cleared.begin(), c.attrs_cleared.end(),
88 inserter(cs.attrs_cleared, cs.attrs_cleared.begin()));
89
90 copy(c.attrs_set.begin(), c.attrs_set.end(),
91 inserter(cs.attrs_set, cs.attrs_set.begin()));
92}
93
94static void
95print_indented_set(ostream & os,
96 set<file_path> const & s,
97 size_t max_cols)
98{
99 size_t cols = 8;
100 os << " ";
101 for (set<file_path>::const_iterator i = s.begin();
102 i != s.end(); i++)
103 {
104 const string str = lexical_cast<string>(*i);
105 if (cols > 8 && cols + str.size() + 1 >= max_cols)
106 {
107 cols = 8;
108 os << "\n ";
109 }
110 os << ' ' << str;
111 cols += str.size() + 1;
112 }
113 os << '\n';
114}
115
116void
117changes_summary::print(ostream & os, size_t max_cols) const
118{
119
120 if (! cs.nodes_deleted.empty())
121 {
122 os << _("Deleted entries:") << '\n';
123 print_indented_set(os, cs.nodes_deleted, max_cols);
124 }
125
126 if (! cs.nodes_renamed.empty())
127 {
128 os << _("Renamed entries:") << '\n';
129 for (map<file_path, file_path>::const_iterator
130 i = cs.nodes_renamed.begin();
131 i != cs.nodes_renamed.end(); i++)
132 os << " " << i->first
133 << " to " << i->second << '\n';
134 }
135
136 if (! cs.files_added.empty())
137 {
138 set<file_path> tmp;
139 for (map<file_path, file_id>::const_iterator
140 i = cs.files_added.begin();
141 i != cs.files_added.end(); ++i)
142 tmp.insert(i->first);
143 os << _("Added files:") << '\n';
144 print_indented_set(os, tmp, max_cols);
145 }
146
147 if (! cs.dirs_added.empty())
148 {
149 os << _("Added directories:") << '\n';
150 print_indented_set(os, cs.dirs_added, max_cols);
151 }
152
153 if (! cs.deltas_applied.empty())
154 {
155 set<file_path> tmp;
156 for (map<file_path, pair<file_id, file_id> >::const_iterator
157 i = cs.deltas_applied.begin();
158 i != cs.deltas_applied.end(); ++i)
159 tmp.insert(i->first);
160 os << _("Modified files:") << '\n';
161 print_indented_set(os, tmp, max_cols);
162 }
163
164 if (! cs.attrs_set.empty() || ! cs.attrs_cleared.empty())
165 {
166 set<file_path> tmp;
167 for (set<pair<file_path, attr_key> >::const_iterator
168 i = cs.attrs_cleared.begin();
169 i != cs.attrs_cleared.end(); ++i)
170 tmp.insert(i->first);
171
172 for (map<pair<file_path, attr_key>, attr_value>::const_iterator
173 i = cs.attrs_set.begin();
174 i != cs.attrs_set.end(); ++i)
175 tmp.insert(i->first.first);
176
177 os << _("Modified attrs:") << '\n';
178 print_indented_set(os, tmp, max_cols);
179 }
180}
181
182static void
183do_external_diff(cset const & cs,
184 app_state & app,
185 bool new_is_archived)
186{
187 for (map<file_path, pair<file_id, file_id> >::const_iterator
188 i = cs.deltas_applied.begin();
189 i != cs.deltas_applied.end(); ++i)
190 {
191 data data_old;
192 data data_new;
193
194 file_data f_old;
195 app.db.get_file_version(delta_entry_src(i), f_old);
196 data_old = f_old.inner();
197
198 if (new_is_archived)
199 {
200 file_data f_new;
201 app.db.get_file_version(delta_entry_dst(i), f_new);
202 data_new = f_new.inner();
203 }
204 else
205 {
206 read_data(delta_entry_path(i), data_new);
207 }
208
209 bool is_binary = false;
210 if (guess_binary(data_old()) ||
211 guess_binary(data_new()))
212 is_binary = true;
213
214 app.lua.hook_external_diff(delta_entry_path(i),
215 data_old,
216 data_new,
217 is_binary,
218 app.opts.external_diff_args_given,
219 app.opts.external_diff_args,
220 delta_entry_src(i).inner()(),
221 delta_entry_dst(i).inner()());
222 }
223}
224
225static void
226dump_diffs(cset const & cs,
227 app_state & app,
228 bool new_is_archived,
229 std::ostream & output,
230 set<file_path> const & paths,
231 bool limit_paths = false)
232{
233 // 60 is somewhat arbitrary, but less than 80
234 string patch_sep = string(60, '=');
235
236 for (map<file_path, file_id>::const_iterator
237 i = cs.files_added.begin();
238 i != cs.files_added.end(); ++i)
239 {
240 if (limit_paths && paths.find(i->first) == paths.end())
241 continue;
242
243 output << patch_sep << '\n';
244 data unpacked;
245 vector<string> lines;
246
247 if (new_is_archived)
248 {
249 file_data dat;
250 app.db.get_file_version(i->second, dat);
251 unpacked = dat.inner();
252 }
253 else
254 {
255 read_data(i->first, unpacked);
256 }
257
258 std::string pattern("");
259 if (!app.opts.no_show_encloser)
260 app.lua.hook_get_encloser_pattern(i->first, pattern);
261
262 make_diff(i->first.as_internal(),
263 i->first.as_internal(),
264 i->second,
265 i->second,
266 data(), unpacked,
267 output, app.opts.diff_format, pattern);
268 }
269
270 map<file_path, file_path> reverse_rename_map;
271
272 for (map<file_path, file_path>::const_iterator
273 i = cs.nodes_renamed.begin();
274 i != cs.nodes_renamed.end(); ++i)
275 {
276 reverse_rename_map.insert(make_pair(i->second, i->first));
277 }
278
279 for (map<file_path, pair<file_id, file_id> >::const_iterator
280 i = cs.deltas_applied.begin();
281 i != cs.deltas_applied.end(); ++i)
282 {
283 if (limit_paths && paths.find(i->first) == paths.end())
284 continue;
285
286 file_data f_old;
287 data data_old, data_new;
288
289 output << patch_sep << '\n';
290
291 app.db.get_file_version(delta_entry_src(i), f_old);
292 data_old = f_old.inner();
293
294 if (new_is_archived)
295 {
296 file_data f_new;
297 app.db.get_file_version(delta_entry_dst(i), f_new);
298 data_new = f_new.inner();
299 }
300 else
301 {
302 read_data(delta_entry_path(i), data_new);
303 }
304
305 file_path dst_path = delta_entry_path(i);
306 file_path src_path = dst_path;
307 map<file_path, file_path>::const_iterator re;
308 re = reverse_rename_map.find(dst_path);
309 if (re != reverse_rename_map.end())
310 src_path = re->second;
311
312 std::string pattern("");
313 if (!app.opts.no_show_encloser)
314 app.lua.hook_get_encloser_pattern(src_path,
315 pattern);
316
317 make_diff(src_path.as_internal(),
318 dst_path.as_internal(),
319 delta_entry_src(i),
320 delta_entry_dst(i),
321 data_old, data_new,
322 output, app.opts.diff_format, pattern);
323 }
324}
325
326static void
327dump_diffs(cset const & cs,
328 app_state & app,
329 bool new_is_archived,
330 std::ostream & output)
331{
332 set<file_path> dummy;
333 dump_diffs(cs, app, new_is_archived, output, dummy);
334}
335
336// common functionality for diff and automate content_diff to determine
337// revisions and rosters which should be diffed
338static void
339prepare_diff(cset & included,
340 app_state & app,
341 args_vector args,
342 bool & new_is_archived,
343 std::string & revheader)
344{
345 temp_node_id_source nis;
346 ostringstream header;
347 cset excluded;
348
349 // initialize before transaction so we have a database to work with.
350
351 if (app.opts.revision_selectors.size() == 0)
352 app.require_workspace();
353 else if (app.opts.revision_selectors.size() == 1)
354 app.require_workspace();
355
356 N(app.opts.revision_selectors.size() <= 2,
357 F("more than two revisions given"));
358
359 if (app.opts.revision_selectors.size() == 0)
360 {
361 roster_t new_roster, old_roster;
362 revision_id old_rid;
363 parent_map parents;
364
365 app.work.get_parent_rosters(parents);
366
367 // With no arguments, which parent should we diff against?
368 N(parents.size() == 1,
369 F("this workspace has more than one parent\n"
370 "(specify a revision to diff against with --revision)"));
371
372 old_rid = parent_id(parents.begin());
373 old_roster = parent_roster(parents.begin());
374 app.work.get_current_roster_shape(new_roster, nis);
375
376 node_restriction mask(args_to_paths(args),
377 args_to_paths(app.opts.exclude_patterns),
378 app.opts.depth,
379 old_roster, new_roster, app);
380
381 app.work.update_current_roster_from_filesystem(new_roster, mask);
382 make_restricted_csets(old_roster, new_roster,
383 included, excluded, mask);
384 check_restricted_cset(old_roster, included);
385
386 new_is_archived = false;
387 header << "# old_revision [" << old_rid << "]\n";
388 }
389 else if (app.opts.revision_selectors.size() == 1)
390 {
391 roster_t new_roster, old_roster;
392 revision_id r_old_id;
393
394 complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id);
395 N(app.db.revision_exists(r_old_id),
396 F("no such revision '%s'") % r_old_id);
397
398 app.db.get_roster(r_old_id, old_roster);
399 app.work.get_current_roster_shape(new_roster, nis);
400
401 node_restriction mask(args_to_paths(args),
402 args_to_paths(app.opts.exclude_patterns),
403 app.opts.depth,
404 old_roster, new_roster, app);
405
406 app.work.update_current_roster_from_filesystem(new_roster, mask);
407 make_restricted_csets(old_roster, new_roster,
408 included, excluded, mask);
409 check_restricted_cset(old_roster, included);
410
411 new_is_archived = false;
412 header << "# old_revision [" << r_old_id << "]\n";
413 }
414 else if (app.opts.revision_selectors.size() == 2)
415 {
416 roster_t new_roster, old_roster;
417 revision_id r_old_id, r_new_id;
418
419 complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id);
420 complete(app, idx(app.opts.revision_selectors, 1)(), r_new_id);
421
422 N(app.db.revision_exists(r_old_id),
423 F("no such revision '%s'") % r_old_id);
424 N(app.db.revision_exists(r_new_id),
425 F("no such revision '%s'") % r_new_id);
426
427 app.db.get_roster(r_old_id, old_roster);
428 app.db.get_roster(r_new_id, new_roster);
429
430 node_restriction mask(args_to_paths(args),
431 args_to_paths(app.opts.exclude_patterns),
432 app.opts.depth,
433 old_roster, new_roster, app);
434
435 // FIXME: this is *possibly* a UI bug, insofar as we
436 // look at the restriction name(s) you provided on the command
437 // line in the context of new and old, *not* the working copy.
438 // One way of "fixing" this is to map the filenames on the command
439 // line to node_ids, and then restrict based on those. This
440 // might be more intuitive; on the other hand it would make it
441 // impossible to restrict to paths which are dead in the working
442 // copy but live between old and new. So ... no rush to "fix" it;
443 // discuss implications first.
444 //
445 // Let the discussion begin...
446 //
447 // - "map filenames on the command line to node_ids" needs to be done
448 // in the context of some roster, possibly the working copy base or
449 // the current working copy (or both)
450 // - diff with two --revision's may be done with no working copy
451 // - some form of "peg" revision syntax for paths that would allow
452 // for each path to specify which revision it is relevant to is
453 // probably the "right" way to go eventually. something like file@rev
454 // (which fails for paths with @'s in them) or possibly //rev/file
455 // since versioned paths are required to be relative.
456
457 make_restricted_csets(old_roster, new_roster,
458 included, excluded, mask);
459 check_restricted_cset(old_roster, included);
460
461 new_is_archived = true;
462 }
463 else
464 {
465 I(false);
466 }
467
468 revheader = header.str();
469}
470
471CMD(diff, "diff", "", CMD_REF(informative), N_("[PATH]..."),
472 N_("Shows current differences"),
473 N_("Compares the current tree with the files in the repository and "
474 "prints the differences on the standard output.\n"
475 "If one revision is given, the diff between the workspace and "
476 "that revision is shown. If two revisions are given, the diff "
477 "between them is given. If no format is specified, unified is "
478 "used by default."),
479 options::opts::revision | options::opts::depth | options::opts::exclude
480 | options::opts::diff_options)
481{
482 if (app.opts.external_diff_args_given)
483 N(app.opts.diff_format == external_diff,
484 F("--diff-args requires --external\n"
485 "try adding --external or removing --diff-args?"));
486
487 cset included;
488 std::string revs;
489 bool new_is_archived;
490
491 prepare_diff(included, app, args, new_is_archived, revs);
492
493 data summary;
494 write_cset(included, summary);
495
496 vector<string> lines;
497 split_into_lines(summary(), lines);
498 cout << "#\n";
499 if (summary().size() > 0)
500 {
501 cout << revs << "#\n";
502 for (vector<string>::iterator i = lines.begin();
503 i != lines.end(); ++i)
504 cout << "# " << *i << '\n';
505 }
506 else
507 {
508 cout << "# " << _("no changes") << '\n';
509 }
510 cout << "#\n";
511
512 if (app.opts.diff_format == external_diff)
513 {
514 do_external_diff(included, app, new_is_archived);
515 }
516 else
517 {
518 dump_diffs(included, app, new_is_archived, cout);
519 }
520}
521
522
523// Name: content_diff
524// Arguments:
525// (optional) one or more files to include
526// Added in: 4.0
527// Purpose: Availability of mtn diff as automate command.
528//
529// Output format: Like mtn diff, but with the header part omitted (as this is
530// doubles the output of automate get_revision). If no content changes happened,
531// the output is empty. All file operations beside mtn add are omitted,
532// as they don't change the content of the file.
533CMD_AUTOMATE(content_diff, N_("[FILE [...]]"),
534 N_("Calculates diffs of files"),
535 "",
536 options::opts::revision | options::opts::depth |
537 options::opts::exclude)
538{
539 cset included;
540 std::string dummy_header;
541 bool new_is_archived;
542
543 prepare_diff(included, app, args, new_is_archived, dummy_header);
544
545 dump_diffs(included, app, new_is_archived, output);
546}
547
548
549static void
550log_certs(ostream & os, app_state & app, revision_id id, cert_name name,
551 string label, string separator, bool multiline, bool newline)
552{
553 vector< revision<cert> > certs;
554 bool first = true;
555
556 if (multiline)
557 newline = true;
558
559 app.get_project().get_revision_certs_by_name(id, name, certs);
560 for (vector< revision<cert> >::const_iterator i = certs.begin();
561 i != certs.end(); ++i)
562 {
563 cert_value tv;
564 decode_base64(i->inner().value, tv);
565
566 if (first)
567 os << label;
568 else
569 os << separator;
570
571 if (multiline)
572 os << "\n\n";
573 os << tv;
574 if (newline)
575 os << '\n';
576
577 first = false;
578 }
579}
580
581static void
582log_certs(ostream & os, app_state & app, revision_id id, cert_name name,
583 string label, bool multiline)
584{
585 log_certs(os, app, id, name, label, label, multiline, true);
586}
587
588static void
589log_certs(ostream & os, app_state & app, revision_id id, cert_name name)
590{
591 log_certs(os, app, id, name, " ", ",", false, false);
592}
593
594
595struct rev_cmp
596{
597 bool dir;
598 rev_cmp(bool _dir) : dir(_dir) {}
599 bool operator() (pair<rev_height, revision_id> const & x,
600 pair<rev_height, revision_id> const & y) const
601 {
602 return dir ? (x.first < y.first) : (x.first > y.first);
603 }
604};
605
606typedef priority_queue<pair<rev_height, revision_id>,
607 vector<pair<rev_height, revision_id> >,
608 rev_cmp> frontier_t;
609
610CMD(log, "log", "", CMD_REF(informative), N_("[FILE] ..."),
611 N_("Prints history in reverse order"),
612 N_("This command prints history in reverse order, filtering it by "
613 "FILE if given. If one or more revisions are given, uses them as "
614 "a starting point."),
615 options::opts::last | options::opts::next
616 | options::opts::from | options::opts::to
617 | options::opts::brief | options::opts::diffs
618 | options::opts::no_merges | options::opts::no_files
619 | options::opts::no_graph)
620{
621 if (app.opts.from.size() == 0)
622 app.require_workspace("try passing a --from revision to start at");
623
624 long last = app.opts.last;
625 long next = app.opts.next;
626
627 N(last == -1 || next == -1,
628 F("only one of --last/--next allowed"));
629
630 frontier_t frontier(rev_cmp(!(next>0)));
631 revision_id first_rid; // for mapping paths to node ids when restricted
632
633 if (app.opts.from.size() == 0)
634 {
635 revision_t rev;
636 app.work.get_work_rev(rev);
637 for (edge_map::const_iterator i = rev.edges.begin();
638 i != rev.edges.end(); i++)
639 {
640 rev_height height;
641 app.db.get_rev_height(edge_old_revision(i), height);
642 frontier.push(make_pair(height, edge_old_revision(i)));
643 }
644 }
645 else
646 {
647 for (args_vector::const_iterator i = app.opts.from.begin();
648 i != app.opts.from.end(); i++)
649 {
650 set<revision_id> rids;
651 complete(app, (*i)(), rids);
652 for (set<revision_id>::const_iterator j = rids.begin();
653 j != rids.end(); ++j)
654 {
655 rev_height height;
656 app.db.get_rev_height(*j, height);
657 frontier.push(make_pair(height, *j));
658 }
659 if (i == app.opts.from.begin())
660 first_rid = *rids.begin();
661 }
662 }
663
664 node_restriction mask;
665
666 if (args.size() > 0)
667 {
668 // User wants to trace only specific files
669 if (app.opts.from.size() == 0)
670 {
671 roster_t new_roster;
672 parent_map parents;
673 temp_node_id_source nis;
674
675 app.work.get_parent_rosters(parents);
676 app.work.get_current_roster_shape(new_roster, nis);
677
678 mask = node_restriction(args_to_paths(args),
679 args_to_paths(app.opts.exclude_patterns),
680 app.opts.depth, parents, new_roster, app);
681 }
682 else
683 {
684 // FIXME_RESTRICTIONS: should this add paths from the rosters of
685 // all selected revs?
686 roster_t roster;
687 app.db.get_roster(first_rid, roster);
688
689 mask = node_restriction(args_to_paths(args),
690 args_to_paths(app.opts.exclude_patterns),
691 app.opts.depth, roster, app);
692 }
693 }
694
695 // If --to was given, don't log past those revisions.
696 set<revision_id> disallowed;
697 bool use_disallowed(!app.opts.to.empty());
698 if (use_disallowed)
699 {
700 std::deque<revision_id> to;
701 for (args_vector::const_iterator i = app.opts.to.begin();
702 i != app.opts.to.end(); i++)
703 {
704 MM(*i);
705 set<revision_id> rids;
706 complete(app, (*i)(), rids);
707 for (set<revision_id>::const_iterator j = rids.begin();
708 j != rids.end(); ++j)
709 {
710 I(!null_id(*j));
711 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*j);
712 if (res.second)
713 {
714 to.push_back(*j);
715 }
716 }
717 }
718
719 while (!to.empty())
720 {
721 revision_id const & rid(to.front());
722 MM(rid);
723
724 set<revision_id> relatives;
725 MM(relatives);
726 if (next > 0)
727 {
728 app.db.get_revision_children(rid, relatives);
729 }
730 else
731 {
732 app.db.get_revision_parents(rid, relatives);
733 }
734
735 for (set<revision_id>::const_iterator i = relatives.begin();
736 i != relatives.end(); ++i)
737 {
738 if (null_id(*i))
739 continue;
740 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*i);
741 if (res.second)
742 {
743 to.push_back(*i);
744 }
745 }
746
747 to.pop_front();
748 }
749 }
750
751 cert_name author_name(author_cert_name);
752 cert_name date_name(date_cert_name);
753 cert_name branch_name(branch_cert_name);
754 cert_name tag_name(tag_cert_name);
755 cert_name changelog_name(changelog_cert_name);
756 cert_name comment_name(comment_cert_name);
757
758 // we can use the markings if we walk backwards for a restricted log
759 bool use_markings(!(next>0) && !mask.empty());
760
761 set<revision_id> seen;
762 revision_t rev;
763 // this is instantiated even when not used, but it's lightweight
764 asciik graph(cout);
765 while(! frontier.empty() && (last == -1 || last > 0)
766 && (next == -1 || next > 0))
767 {
768 revision_id const & rid = frontier.top().second;
769
770 bool print_this = mask.empty();
771 set<file_path> diff_paths;
772
773 if (null_id(rid) || seen.find(rid) != seen.end())
774 {
775 frontier.pop();
776 continue;
777 }
778
779 seen.insert(rid);
780 app.db.get_revision(rid, rev);
781
782 set<revision_id> marked_revs;
783
784 if (!mask.empty())
785 {
786 roster_t roster;
787 marking_map markings;
788 app.db.get_roster(rid, roster, markings);
789
790 // get all revision ids mentioned in one of the markings
791 for (marking_map::const_iterator m = markings.begin();
792 m != markings.end(); ++m)
793 {
794 node_id node = m->first;
795 marking_t marking = m->second;
796
797 if (mask.includes(roster, node))
798 {
799 marked_revs.insert(marking.file_content.begin(), marking.file_content.end());
800 marked_revs.insert(marking.parent_name.begin(), marking.parent_name.end());
801 for (map<attr_key, set<revision_id> >::const_iterator a = marking.attrs.begin();
802 a != marking.attrs.end(); ++a)
803 marked_revs.insert(a->second.begin(), a->second.end());
804 }
805 }
806
807 // find out whether the current rev is to be printed
808 // we don't care about changed paths if it is not marked
809 if (!use_markings || marked_revs.find(rid) != marked_revs.end())
810 {
811 set<node_id> nodes_modified;
812 select_nodes_modified_by_rev(rev, roster,
813 nodes_modified,
814 app);
815
816 for (set<node_id>::const_iterator n = nodes_modified.begin();
817 n != nodes_modified.end(); ++n)
818 {
819 // a deleted node will be "modified" but won't
820 // exist in the result.
821 // we don't want to print them.
822 if (roster.has_node(*n) && mask.includes(roster, *n))
823 {
824 print_this = true;
825 if (app.opts.diffs)
826 {
827 file_path fp;
828 roster.get_name(*n, fp);
829 diff_paths.insert(fp);
830 }
831 }
832 }
833 }
834 }
835
836 if (app.opts.no_merges && rev.is_merge_node())
837 print_this = false;
838
839 set<revision_id> interesting;
840 // if rid is not marked we can jump directly to the marked ancestors,
841 // otherwise we need to visit the parents
842 if (use_markings && marked_revs.find(rid) == marked_revs.end())
843 {
844 interesting.insert(marked_revs.begin(), marked_revs.end());
845 }
846 else
847 {
848 if (next > 0)
849 app.db.get_revision_children(rid, interesting);
850 else // walk backwards by default
851 app.db.get_revision_parents(rid, interesting);
852 }
853
854 if (print_this)
855 {
856 ostringstream out;
857 if (app.opts.brief)
858 {
859 out << rid;
860 log_certs(out, app, rid, author_name);
861 if (app.opts.no_graph)
862 log_certs(out, app, rid, date_name);
863 else
864 {
865 out << '\n';
866 log_certs(out, app, rid, date_name, string(), string(), false, false);
867 }
868 log_certs(out, app, rid, branch_name);
869 out << '\n';
870 }
871 else
872 {
873 out << string(65, '-') << '\n';
874 out << "Revision: " << rid << '\n';
875
876 changes_summary csum;
877
878 set<revision_id> ancestors;
879
880 for (edge_map::const_iterator e = rev.edges.begin();
881 e != rev.edges.end(); ++e)
882 {
883 ancestors.insert(edge_old_revision(e));
884 csum.add_change_set(edge_changes(e));
885 }
886
887 for (set<revision_id>::const_iterator anc = ancestors.begin();
888 anc != ancestors.end(); ++anc)
889 out << "Ancestor: " << *anc << '\n';
890
891 log_certs(out, app, rid, author_name, "Author: ", false);
892 log_certs(out, app, rid, date_name, "Date: ", false);
893 log_certs(out, app, rid, branch_name, "Branch: ", false);
894 log_certs(out, app, rid, tag_name, "Tag: ", false);
895
896 if (!app.opts.no_files && !csum.cs.empty())
897 {
898 out << '\n';
899 csum.print(out, 70);
900 out << '\n';
901 }
902
903 log_certs(out, app, rid, changelog_name, "ChangeLog: ", true);
904 log_certs(out, app, rid, comment_name, "Comments: ", true);
905 }
906
907 if (app.opts.diffs)
908 {
909 for (edge_map::const_iterator e = rev.edges.begin();
910 e != rev.edges.end(); ++e)
911 dump_diffs(edge_changes(e), app, true, out,
912 diff_paths, !mask.empty());
913 }
914
915 if (next > 0)
916 next--;
917 else if (last > 0)
918 last--;
919
920 string out_system;
921 utf8_to_system_best_effort(utf8(out.str()), out_system);
922 if (app.opts.no_graph)
923 cout << out_system;
924 else
925 graph.print(rid, interesting, out_system);
926 }
927 else if (use_markings && !app.opts.no_graph)
928 graph.print(rid, interesting, (F("(Revision: %s)") % rid).str());
929
930 frontier.pop(); // beware: rid is invalid from now on
931
932 for (set<revision_id>::const_iterator i = interesting.begin();
933 i != interesting.end(); ++i)
934 {
935 if (use_disallowed && (disallowed.find(*i) != disallowed.end()))
936 {
937 continue;
938 }
939 rev_height height;
940 app.db.get_rev_height(*i, height);
941 frontier.push(make_pair(height, *i));
942 }
943 }
944}
945
946// Local Variables:
947// mode: C++
948// fill-column: 76
949// c-file-style: "gnu"
950// indent-tabs-mode: nil
951// End:
952// 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