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 MM(included);
385 MM(excluded);
386 check_restricted_cset(old_roster, included);
387
388 new_is_archived = false;
389 header << "# old_revision [" << old_rid << "]\n";
390 }
391 else if (app.opts.revision_selectors.size() == 1)
392 {
393 roster_t new_roster, old_roster;
394 revision_id r_old_id;
395
396 complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id);
397 N(app.db.revision_exists(r_old_id),
398 F("no such revision '%s'") % r_old_id);
399
400 app.db.get_roster(r_old_id, old_roster);
401 app.work.get_current_roster_shape(new_roster, nis);
402
403 node_restriction mask(args_to_paths(args),
404 args_to_paths(app.opts.exclude_patterns),
405 app.opts.depth,
406 old_roster, new_roster, app);
407
408 app.work.update_current_roster_from_filesystem(new_roster, mask);
409 make_restricted_csets(old_roster, new_roster,
410 included, excluded, mask);
411 MM(included);
412 MM(excluded);
413 check_restricted_cset(old_roster, included);
414
415 new_is_archived = false;
416 header << "# old_revision [" << r_old_id << "]\n";
417 }
418 else if (app.opts.revision_selectors.size() == 2)
419 {
420 roster_t new_roster, old_roster;
421 revision_id r_old_id, r_new_id;
422
423 complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id);
424 complete(app, idx(app.opts.revision_selectors, 1)(), r_new_id);
425
426 N(app.db.revision_exists(r_old_id),
427 F("no such revision '%s'") % r_old_id);
428 N(app.db.revision_exists(r_new_id),
429 F("no such revision '%s'") % r_new_id);
430
431 app.db.get_roster(r_old_id, old_roster);
432 app.db.get_roster(r_new_id, new_roster);
433
434 node_restriction mask(args_to_paths(args),
435 args_to_paths(app.opts.exclude_patterns),
436 app.opts.depth,
437 old_roster, new_roster, app);
438
439 // FIXME: this is *possibly* a UI bug, insofar as we
440 // look at the restriction name(s) you provided on the command
441 // line in the context of new and old, *not* the working copy.
442 // One way of "fixing" this is to map the filenames on the command
443 // line to node_ids, and then restrict based on those. This
444 // might be more intuitive; on the other hand it would make it
445 // impossible to restrict to paths which are dead in the working
446 // copy but live between old and new. So ... no rush to "fix" it;
447 // discuss implications first.
448 //
449 // Let the discussion begin...
450 //
451 // - "map filenames on the command line to node_ids" needs to be done
452 // in the context of some roster, possibly the working copy base or
453 // the current working copy (or both)
454 // - diff with two --revision's may be done with no working copy
455 // - some form of "peg" revision syntax for paths that would allow
456 // for each path to specify which revision it is relevant to is
457 // probably the "right" way to go eventually. something like file@rev
458 // (which fails for paths with @'s in them) or possibly //rev/file
459 // since versioned paths are required to be relative.
460
461 make_restricted_csets(old_roster, new_roster,
462 included, excluded, mask);
463 MM(included);
464 MM(excluded);
465 check_restricted_cset(old_roster, included);
466
467 new_is_archived = true;
468 }
469 else
470 {
471 I(false);
472 }
473
474 revheader = header.str();
475}
476
477CMD(diff, "diff", "", CMD_REF(informative), N_("[PATH]..."),
478 N_("Shows current differences"),
479 N_("Compares the current tree with the files in the repository and "
480 "prints the differences on the standard output.\n"
481 "If one revision is given, the diff between the workspace and "
482 "that revision is shown. If two revisions are given, the diff "
483 "between them is given. If no format is specified, unified is "
484 "used by default."),
485 options::opts::revision | options::opts::depth | options::opts::exclude
486 | options::opts::diff_options)
487{
488 if (app.opts.external_diff_args_given)
489 N(app.opts.diff_format == external_diff,
490 F("--diff-args requires --external\n"
491 "try adding --external or removing --diff-args?"));
492
493 cset included;
494 std::string revs;
495 bool new_is_archived;
496
497 prepare_diff(included, app, args, new_is_archived, revs);
498
499 data summary;
500 write_cset(included, summary);
501
502 vector<string> lines;
503 split_into_lines(summary(), lines);
504 cout << "#\n";
505 if (summary().size() > 0)
506 {
507 cout << revs << "#\n";
508 for (vector<string>::iterator i = lines.begin();
509 i != lines.end(); ++i)
510 cout << "# " << *i << '\n';
511 }
512 else
513 {
514 cout << "# " << _("no changes") << '\n';
515 }
516 cout << "#\n";
517
518 if (app.opts.diff_format == external_diff)
519 {
520 do_external_diff(included, app, new_is_archived);
521 }
522 else
523 {
524 dump_diffs(included, app, new_is_archived, cout);
525 }
526}
527
528
529// Name: content_diff
530// Arguments:
531// (optional) one or more files to include
532// Added in: 4.0
533// Purpose: Availability of mtn diff as automate command.
534//
535// Output format: Like mtn diff, but with the header part omitted (as this is
536// doubles the output of automate get_revision). If no content changes happened,
537// the output is empty. All file operations beside mtn add are omitted,
538// as they don't change the content of the file.
539CMD_AUTOMATE(content_diff, N_("[FILE [...]]"),
540 N_("Calculates diffs of files"),
541 "",
542 options::opts::revision | options::opts::depth |
543 options::opts::exclude)
544{
545 cset included;
546 std::string dummy_header;
547 bool new_is_archived;
548
549 prepare_diff(included, app, args, new_is_archived, dummy_header);
550
551 dump_diffs(included, app, new_is_archived, output);
552}
553
554
555static void
556log_certs(ostream & os, app_state & app, revision_id id, cert_name name,
557 string label, string separator, bool multiline, bool newline)
558{
559 vector< revision<cert> > certs;
560 bool first = true;
561
562 if (multiline)
563 newline = true;
564
565 app.get_project().get_revision_certs_by_name(id, name, certs);
566 for (vector< revision<cert> >::const_iterator i = certs.begin();
567 i != certs.end(); ++i)
568 {
569 cert_value tv;
570 decode_base64(i->inner().value, tv);
571
572 if (first)
573 os << label;
574 else
575 os << separator;
576
577 if (multiline)
578 os << "\n\n";
579 os << tv;
580 if (newline)
581 os << '\n';
582
583 first = false;
584 }
585}
586
587static void
588log_certs(ostream & os, app_state & app, revision_id id, cert_name name,
589 string label, bool multiline)
590{
591 log_certs(os, app, id, name, label, label, multiline, true);
592}
593
594static void
595log_certs(ostream & os, app_state & app, revision_id id, cert_name name)
596{
597 log_certs(os, app, id, name, " ", ",", false, false);
598}
599
600
601struct rev_cmp
602{
603 bool dir;
604 rev_cmp(bool _dir) : dir(_dir) {}
605 bool operator() (pair<rev_height, revision_id> const & x,
606 pair<rev_height, revision_id> const & y) const
607 {
608 return dir ? (x.first < y.first) : (x.first > y.first);
609 }
610};
611
612typedef priority_queue<pair<rev_height, revision_id>,
613 vector<pair<rev_height, revision_id> >,
614 rev_cmp> frontier_t;
615
616CMD(log, "log", "", CMD_REF(informative), N_("[FILE] ..."),
617 N_("Prints history in reverse order"),
618 N_("This command prints history in reverse order, filtering it by "
619 "FILE if given. If one or more revisions are given, uses them as "
620 "a starting point."),
621 options::opts::last | options::opts::next
622 | options::opts::from | options::opts::to
623 | options::opts::brief | options::opts::diffs
624 | options::opts::no_merges | options::opts::no_files
625 | options::opts::no_graph)
626{
627 if (app.opts.from.size() == 0)
628 app.require_workspace("try passing a --from revision to start at");
629
630 long last = app.opts.last;
631 long next = app.opts.next;
632
633 N(last == -1 || next == -1,
634 F("only one of --last/--next allowed"));
635
636 frontier_t frontier(rev_cmp(!(next>0)));
637 revision_id first_rid; // for mapping paths to node ids when restricted
638
639 if (app.opts.from.size() == 0)
640 {
641 revision_t rev;
642 app.work.get_work_rev(rev);
643 for (edge_map::const_iterator i = rev.edges.begin();
644 i != rev.edges.end(); i++)
645 {
646 rev_height height;
647 app.db.get_rev_height(edge_old_revision(i), height);
648 frontier.push(make_pair(height, edge_old_revision(i)));
649 }
650 }
651 else
652 {
653 for (args_vector::const_iterator i = app.opts.from.begin();
654 i != app.opts.from.end(); i++)
655 {
656 set<revision_id> rids;
657 complete(app, (*i)(), rids);
658 for (set<revision_id>::const_iterator j = rids.begin();
659 j != rids.end(); ++j)
660 {
661 rev_height height;
662 app.db.get_rev_height(*j, height);
663 frontier.push(make_pair(height, *j));
664 }
665 if (i == app.opts.from.begin())
666 first_rid = *rids.begin();
667 }
668 }
669
670 node_restriction mask;
671
672 if (args.size() > 0)
673 {
674 // User wants to trace only specific files
675 if (app.opts.from.size() == 0)
676 {
677 roster_t new_roster;
678 parent_map parents;
679 temp_node_id_source nis;
680
681 app.work.get_parent_rosters(parents);
682 app.work.get_current_roster_shape(new_roster, nis);
683
684 mask = node_restriction(args_to_paths(args),
685 args_to_paths(app.opts.exclude_patterns),
686 app.opts.depth, parents, new_roster, app);
687 }
688 else
689 {
690 // FIXME_RESTRICTIONS: should this add paths from the rosters of
691 // all selected revs?
692 roster_t roster;
693 app.db.get_roster(first_rid, roster);
694
695 mask = node_restriction(args_to_paths(args),
696 args_to_paths(app.opts.exclude_patterns),
697 app.opts.depth, roster, app);
698 }
699 }
700
701 // If --to was given, don't log past those revisions.
702 set<revision_id> disallowed;
703 bool use_disallowed(!app.opts.to.empty());
704 if (use_disallowed)
705 {
706 std::deque<revision_id> to;
707 for (args_vector::const_iterator i = app.opts.to.begin();
708 i != app.opts.to.end(); i++)
709 {
710 MM(*i);
711 set<revision_id> rids;
712 complete(app, (*i)(), rids);
713 for (set<revision_id>::const_iterator j = rids.begin();
714 j != rids.end(); ++j)
715 {
716 I(!null_id(*j));
717 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*j);
718 if (res.second)
719 {
720 to.push_back(*j);
721 }
722 }
723 }
724
725 while (!to.empty())
726 {
727 revision_id const & rid(to.front());
728 MM(rid);
729
730 set<revision_id> relatives;
731 MM(relatives);
732 if (next > 0)
733 {
734 app.db.get_revision_children(rid, relatives);
735 }
736 else
737 {
738 app.db.get_revision_parents(rid, relatives);
739 }
740
741 for (set<revision_id>::const_iterator i = relatives.begin();
742 i != relatives.end(); ++i)
743 {
744 if (null_id(*i))
745 continue;
746 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*i);
747 if (res.second)
748 {
749 to.push_back(*i);
750 }
751 }
752
753 to.pop_front();
754 }
755 }
756
757 cert_name author_name(author_cert_name);
758 cert_name date_name(date_cert_name);
759 cert_name branch_name(branch_cert_name);
760 cert_name tag_name(tag_cert_name);
761 cert_name changelog_name(changelog_cert_name);
762 cert_name comment_name(comment_cert_name);
763
764 // we can use the markings if we walk backwards for a restricted log
765 bool use_markings(!(next>0) && !mask.empty());
766
767 set<revision_id> seen;
768 revision_t rev;
769 // this is instantiated even when not used, but it's lightweight
770 asciik graph(cout);
771 while(! frontier.empty() && (last == -1 || last > 0)
772 && (next == -1 || next > 0))
773 {
774 revision_id const & rid = frontier.top().second;
775
776 bool print_this = mask.empty();
777 set<file_path> diff_paths;
778
779 if (null_id(rid) || seen.find(rid) != seen.end())
780 {
781 frontier.pop();
782 continue;
783 }
784
785 seen.insert(rid);
786 app.db.get_revision(rid, rev);
787
788 set<revision_id> marked_revs;
789
790 if (!mask.empty())
791 {
792 roster_t roster;
793 marking_map markings;
794 app.db.get_roster(rid, roster, markings);
795
796 // get all revision ids mentioned in one of the markings
797 for (marking_map::const_iterator m = markings.begin();
798 m != markings.end(); ++m)
799 {
800 node_id node = m->first;
801 marking_t marking = m->second;
802
803 if (mask.includes(roster, node))
804 {
805 marked_revs.insert(marking.file_content.begin(), marking.file_content.end());
806 marked_revs.insert(marking.parent_name.begin(), marking.parent_name.end());
807 for (map<attr_key, set<revision_id> >::const_iterator a = marking.attrs.begin();
808 a != marking.attrs.end(); ++a)
809 marked_revs.insert(a->second.begin(), a->second.end());
810 }
811 }
812
813 // find out whether the current rev is to be printed
814 // we don't care about changed paths if it is not marked
815 if (!use_markings || marked_revs.find(rid) != marked_revs.end())
816 {
817 set<node_id> nodes_modified;
818 select_nodes_modified_by_rev(rev, roster,
819 nodes_modified,
820 app);
821
822 for (set<node_id>::const_iterator n = nodes_modified.begin();
823 n != nodes_modified.end(); ++n)
824 {
825 // a deleted node will be "modified" but won't
826 // exist in the result.
827 // we don't want to print them.
828 if (roster.has_node(*n) && mask.includes(roster, *n))
829 {
830 print_this = true;
831 if (app.opts.diffs)
832 {
833 file_path fp;
834 roster.get_name(*n, fp);
835 diff_paths.insert(fp);
836 }
837 }
838 }
839 }
840 }
841
842 if (app.opts.no_merges && rev.is_merge_node())
843 print_this = false;
844
845 set<revision_id> interesting;
846 // if rid is not marked we can jump directly to the marked ancestors,
847 // otherwise we need to visit the parents
848 if (use_markings && marked_revs.find(rid) == marked_revs.end())
849 {
850 interesting.insert(marked_revs.begin(), marked_revs.end());
851 }
852 else
853 {
854 if (next > 0)
855 app.db.get_revision_children(rid, interesting);
856 else // walk backwards by default
857 app.db.get_revision_parents(rid, interesting);
858 }
859
860 if (print_this)
861 {
862 ostringstream out;
863 if (app.opts.brief)
864 {
865 out << rid;
866 log_certs(out, app, rid, author_name);
867 if (app.opts.no_graph)
868 log_certs(out, app, rid, date_name);
869 else
870 {
871 out << '\n';
872 log_certs(out, app, rid, date_name, string(), string(), false, false);
873 }
874 log_certs(out, app, rid, branch_name);
875 out << '\n';
876 }
877 else
878 {
879 out << string(65, '-') << '\n';
880 out << "Revision: " << rid << '\n';
881
882 changes_summary csum;
883
884 set<revision_id> ancestors;
885
886 for (edge_map::const_iterator e = rev.edges.begin();
887 e != rev.edges.end(); ++e)
888 {
889 ancestors.insert(edge_old_revision(e));
890 csum.add_change_set(edge_changes(e));
891 }
892
893 for (set<revision_id>::const_iterator anc = ancestors.begin();
894 anc != ancestors.end(); ++anc)
895 out << "Ancestor: " << *anc << '\n';
896
897 log_certs(out, app, rid, author_name, "Author: ", false);
898 log_certs(out, app, rid, date_name, "Date: ", false);
899 log_certs(out, app, rid, branch_name, "Branch: ", false);
900 log_certs(out, app, rid, tag_name, "Tag: ", false);
901
902 if (!app.opts.no_files && !csum.cs.empty())
903 {
904 out << '\n';
905 csum.print(out, 70);
906 out << '\n';
907 }
908
909 log_certs(out, app, rid, changelog_name, "ChangeLog: ", true);
910 log_certs(out, app, rid, comment_name, "Comments: ", true);
911 }
912
913 if (app.opts.diffs)
914 {
915 for (edge_map::const_iterator e = rev.edges.begin();
916 e != rev.edges.end(); ++e)
917 dump_diffs(edge_changes(e), app, true, out,
918 diff_paths, !mask.empty());
919 }
920
921 if (next > 0)
922 next--;
923 else if (last > 0)
924 last--;
925
926 string out_system;
927 utf8_to_system_best_effort(utf8(out.str()), out_system);
928 if (app.opts.no_graph)
929 cout << out_system;
930 else
931 graph.print(rid, interesting, out_system);
932 }
933 else if (use_markings && !app.opts.no_graph)
934 graph.print(rid, interesting, (F("(Revision: %s)") % rid).str());
935
936 frontier.pop(); // beware: rid is invalid from now on
937
938 for (set<revision_id>::const_iterator i = interesting.begin();
939 i != interesting.end(); ++i)
940 {
941 if (use_disallowed && (disallowed.find(*i) != disallowed.end()))
942 {
943 continue;
944 }
945 rev_height height;
946 app.db.get_rev_height(*i, height);
947 frontier.push(make_pair(height, *i));
948 }
949 }
950}
951
952// Local Variables:
953// mode: C++
954// fill-column: 76
955// c-file-style: "gnu"
956// indent-tabs-mode: nil
957// End:
958// 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