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