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