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