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.empty())
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
483void dump_header(std::string const & revs,
484 cset const & changes,
485 std::ostream & out,
486 bool show_if_empty)
487{
488 data summary;
489 write_cset(changes, summary);
490 if (summary().empty() && !show_if_empty)
491 return;
492
493 vector<string> lines;
494 split_into_lines(summary(), lines);
495 out << "#\n";
496 if (!summary().empty())
497 {
498 out << revs << "#\n";
499 for (vector<string>::iterator i = lines.begin();
500 i != lines.end(); ++i)
501 out << "# " << *i << '\n';
502 }
503 else
504 {
505 out << "# " << _("no changes") << '\n';
506 }
507 out << "#\n";
508}
509
510CMD(diff, "diff", "di", CMD_REF(informative), N_("[PATH]..."),
511 N_("Shows current differences"),
512 N_("Compares the current tree with the files in the repository and "
513 "prints the differences on the standard output.\n"
514 "If one revision is given, the diff between the workspace and "
515 "that revision is shown. If two revisions are given, the diff "
516 "between them is given. If no format is specified, unified is "
517 "used by default."),
518 options::opts::revision | options::opts::depth | options::opts::exclude
519 | options::opts::diff_options)
520{
521 if (app.opts.external_diff_args_given)
522 N(app.opts.diff_format == external_diff,
523 F("--diff-args requires --external\n"
524 "try adding --external or removing --diff-args?"));
525
526 cset included;
527 std::string revs;
528 bool new_is_archived;
529 database db(app);
530
531 prepare_diff(app, db, included, args, new_is_archived, revs);
532
533 if (!app.opts.without_header)
534 {
535 dump_header(revs, included, cout, true);
536 }
537
538 if (app.opts.diff_format == external_diff)
539 {
540 do_external_diff(app.opts, app.lua, db, included, new_is_archived);
541 }
542 else
543 {
544 dump_diffs(app.lua, db, included, cout,
545 app.opts.diff_format, new_is_archived,
546 !app.opts.no_show_encloser);
547 }
548}
549
550
551// Name: content_diff
552// Arguments:
553// (optional) one or more files to include
554// Added in: 4.0
555// Purpose: Availability of mtn diff as automate command.
556//
557// Output format: Like mtn diff, but with the header part omitted by default.
558// If no content changes happened, the output is empty. All file operations
559// beside mtn add are omitted, as they don't change the content of the file.
560CMD_AUTOMATE(content_diff, N_("[FILE [...]]"),
561 N_("Calculates diffs of files"),
562 "",
563 options::opts::with_header | options::opts::without_header |
564 options::opts::revision | options::opts::depth |
565 options::opts::exclude)
566{
567 cset included;
568 std::string dummy_header;
569 bool new_is_archived;
570 database db(app);
571
572 prepare_diff(app, db, included, args, new_is_archived, dummy_header);
573
574
575 if (app.opts.with_header)
576 {
577 dump_header(dummy_header, included, output, false);
578 }
579
580 dump_diffs(app.lua, db, included, output,
581 app.opts.diff_format, new_is_archived, !app.opts.no_show_encloser);
582}
583
584
585static void
586log_certs(project_t & project, ostream & os, revision_id id, cert_name name,
587 string label, string separator, bool multiline, bool newline)
588{
589 vector< revision<cert> > certs;
590 bool first = true;
591
592 if (multiline)
593 newline = true;
594
595 project.get_revision_certs_by_name(id, name, certs);
596 for (vector< revision<cert> >::const_iterator i = certs.begin();
597 i != certs.end(); ++i)
598 {
599 if (first)
600 os << label;
601 else
602 os << separator;
603
604 if (multiline)
605 os << "\n\n";
606 os << i->inner().value;
607 if (newline)
608 os << '\n';
609
610 first = false;
611 }
612}
613
614static void
615log_certs(project_t & project, ostream & os, revision_id id, cert_name name,
616 string label, bool multiline)
617{
618 log_certs(project, os, id, name, label, label, multiline, true);
619}
620
621static void
622log_certs(project_t & project, ostream & os, revision_id id, cert_name name)
623{
624 log_certs(project, os, id, name, " ", ",", false, false);
625}
626
627
628struct rev_cmp
629{
630 bool dir;
631 rev_cmp(bool _dir) : dir(_dir) {}
632 bool operator() (pair<rev_height, revision_id> const & x,
633 pair<rev_height, revision_id> const & y) const
634 {
635 return dir ? (x.first < y.first) : (x.first > y.first);
636 }
637};
638
639typedef priority_queue<pair<rev_height, revision_id>,
640 vector<pair<rev_height, revision_id> >,
641 rev_cmp> frontier_t;
642
643CMD(log, "log", "", CMD_REF(informative), N_("[FILE] ..."),
644 N_("Prints history in reverse order"),
645 N_("This command prints history in reverse order, filtering it by "
646 "FILE if given. If one or more revisions are given, uses them as "
647 "a starting point."),
648 options::opts::last | options::opts::next
649 | options::opts::from | options::opts::to
650 | options::opts::brief | options::opts::diffs
651 | options::opts::no_merges | options::opts::no_files
652 | options::opts::no_graph)
653{
654 database db(app);
655 project_t project(db);
656
657 long last = app.opts.last;
658 long next = app.opts.next;
659
660 N(last == -1 || next == -1,
661 F("only one of --last/--next allowed"));
662
663 frontier_t frontier(rev_cmp(!(next>0)));
664 revision_id first_rid; // for mapping paths to node ids when restricted
665
666 if (app.opts.from.empty())
667 {
668 workspace work(app,
669 F("try passing a --from revision to start at"));
670
671 revision_t rev;
672 work.get_work_rev(rev);
673 for (edge_map::const_iterator i = rev.edges.begin();
674 i != rev.edges.end(); i++)
675 {
676 rev_height height;
677 db.get_rev_height(edge_old_revision(i), height);
678 frontier.push(make_pair(height, edge_old_revision(i)));
679 }
680 }
681 else
682 {
683 for (args_vector::const_iterator i = app.opts.from.begin();
684 i != app.opts.from.end(); i++)
685 {
686 set<revision_id> rids;
687 complete(app.opts, app.lua, project, (*i)(), rids);
688 for (set<revision_id>::const_iterator j = rids.begin();
689 j != rids.end(); ++j)
690 {
691 rev_height height;
692 db.get_rev_height(*j, height);
693 frontier.push(make_pair(height, *j));
694 }
695 if (i == app.opts.from.begin())
696 first_rid = *rids.begin();
697 }
698 }
699
700 node_restriction mask;
701
702 if (!args.empty())
703 {
704 // User wants to trace only specific files
705 if (app.opts.from.empty())
706 {
707 workspace work(app);
708 roster_t new_roster;
709 parent_map parents;
710 temp_node_id_source nis;
711
712 work.get_parent_rosters(db, parents);
713 work.get_current_roster_shape(db, nis, new_roster);
714
715 mask = node_restriction(work, args_to_paths(args),
716 args_to_paths(app.opts.exclude_patterns),
717 app.opts.depth, parents, new_roster);
718 }
719 else
720 {
721 // FIXME_RESTRICTIONS: should this add paths from the rosters of
722 // all selected revs?
723 roster_t roster;
724 db.get_roster(first_rid, roster);
725
726 mask = node_restriction(args_to_paths(args),
727 args_to_paths(app.opts.exclude_patterns),
728 app.opts.depth, roster);
729 }
730 }
731
732 // If --to was given, don't log past those revisions.
733 set<revision_id> disallowed;
734 bool use_disallowed(!app.opts.to.empty());
735 if (use_disallowed)
736 {
737 std::deque<revision_id> to;
738 for (args_vector::const_iterator i = app.opts.to.begin();
739 i != app.opts.to.end(); i++)
740 {
741 MM(*i);
742 set<revision_id> rids;
743 complete(app.opts, app.lua, project, (*i)(), rids);
744 for (set<revision_id>::const_iterator j = rids.begin();
745 j != rids.end(); ++j)
746 {
747 I(!null_id(*j));
748 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*j);
749 if (res.second)
750 {
751 to.push_back(*j);
752 }
753 }
754 }
755
756 while (!to.empty())
757 {
758 revision_id const & rid(to.front());
759 MM(rid);
760
761 set<revision_id> relatives;
762 MM(relatives);
763 if (next > 0)
764 {
765 db.get_revision_children(rid, relatives);
766 }
767 else
768 {
769 db.get_revision_parents(rid, relatives);
770 }
771
772 for (set<revision_id>::const_iterator i = relatives.begin();
773 i != relatives.end(); ++i)
774 {
775 if (null_id(*i))
776 continue;
777 pair<set<revision_id>::iterator, bool> res = disallowed.insert(*i);
778 if (res.second)
779 {
780 to.push_back(*i);
781 }
782 }
783
784 to.pop_front();
785 }
786 }
787
788 cert_name author_name(author_cert_name);
789 cert_name date_name(date_cert_name);
790 cert_name branch_name(branch_cert_name);
791 cert_name tag_name(tag_cert_name);
792 cert_name changelog_name(changelog_cert_name);
793 cert_name comment_name(comment_cert_name);
794
795 // we can use the markings if we walk backwards for a restricted log
796 bool use_markings(!(next>0) && !mask.empty());
797
798 set<revision_id> seen;
799 revision_t rev;
800 // this is instantiated even when not used, but it's lightweight
801 asciik graph(cout);
802 while(! frontier.empty() && (last == -1 || last > 0)
803 && (next == -1 || next > 0))
804 {
805 revision_id const & rid = frontier.top().second;
806
807 bool print_this = mask.empty();
808 set<file_path> diff_paths;
809
810 if (null_id(rid) || seen.find(rid) != seen.end())
811 {
812 frontier.pop();
813 continue;
814 }
815
816 seen.insert(rid);
817 db.get_revision(rid, rev);
818
819 set<revision_id> marked_revs;
820
821 if (!mask.empty())
822 {
823 roster_t roster;
824 marking_map markings;
825 db.get_roster(rid, roster, markings);
826
827 // get all revision ids mentioned in one of the markings
828 for (marking_map::const_iterator m = markings.begin();
829 m != markings.end(); ++m)
830 {
831 node_id node = m->first;
832 marking_t marking = m->second;
833
834 if (mask.includes(roster, node))
835 {
836 marked_revs.insert(marking.file_content.begin(), marking.file_content.end());
837 marked_revs.insert(marking.parent_name.begin(), marking.parent_name.end());
838 for (map<attr_key, set<revision_id> >::const_iterator a = marking.attrs.begin();
839 a != marking.attrs.end(); ++a)
840 marked_revs.insert(a->second.begin(), a->second.end());
841 }
842 }
843
844 // find out whether the current rev is to be printed
845 // we don't care about changed paths if it is not marked
846 if (!use_markings || marked_revs.find(rid) != marked_revs.end())
847 {
848 set<node_id> nodes_modified;
849 select_nodes_modified_by_rev(db, rev, roster,
850 nodes_modified);
851
852 for (set<node_id>::const_iterator n = nodes_modified.begin();
853 n != nodes_modified.end(); ++n)
854 {
855 // a deleted node will be "modified" but won't
856 // exist in the result.
857 // we don't want to print them.
858 if (roster.has_node(*n) && mask.includes(roster, *n))
859 {
860 print_this = true;
861 if (app.opts.diffs)
862 {
863 file_path fp;
864 roster.get_name(*n, fp);
865 diff_paths.insert(fp);
866 }
867 }
868 }
869 }
870 }
871
872 if (app.opts.no_merges && rev.is_merge_node())
873 print_this = false;
874
875 set<revision_id> interesting;
876 // if rid is not marked we can jump directly to the marked ancestors,
877 // otherwise we need to visit the parents
878 if (use_markings && marked_revs.find(rid) == marked_revs.end())
879 {
880 interesting.insert(marked_revs.begin(), marked_revs.end());
881 }
882 else
883 {
884 if (next > 0)
885 db.get_revision_children(rid, interesting);
886 else // walk backwards by default
887 db.get_revision_parents(rid, interesting);
888 }
889
890 if (print_this)
891 {
892 ostringstream out;
893 if (app.opts.brief)
894 {
895 out << rid;
896 log_certs(project, out, rid, author_name);
897 if (app.opts.no_graph)
898 log_certs(project, out, rid, date_name);
899 else
900 {
901 out << '\n';
902 log_certs(project, out, rid, date_name,
903 string(), string(), false, false);
904 }
905 log_certs(project, out, rid, branch_name);
906 out << '\n';
907 }
908 else
909 {
910 out << string(65, '-') << '\n';
911 out << "Revision: " << rid << '\n';
912
913 changes_summary csum;
914
915 set<revision_id> ancestors;
916
917 for (edge_map::const_iterator e = rev.edges.begin();
918 e != rev.edges.end(); ++e)
919 {
920 ancestors.insert(edge_old_revision(e));
921 csum.add_change_set(edge_changes(e));
922 }
923
924 for (set<revision_id>::const_iterator anc = ancestors.begin();
925 anc != ancestors.end(); ++anc)
926 out << "Ancestor: " << *anc << '\n';
927
928 log_certs(project, out, rid, author_name, "Author: ", false);
929 log_certs(project, out, rid, date_name, "Date: ", false);
930 log_certs(project, out, rid, branch_name, "Branch: ", false);
931 log_certs(project, out, rid, tag_name, "Tag: ", false);
932
933 if (!app.opts.no_files && !csum.cs.empty())
934 {
935 out << '\n';
936 csum.print(out, 70);
937 out << '\n';
938 }
939
940 log_certs(project, out, rid, changelog_name, "ChangeLog: ", true);
941 log_certs(project, out, rid, comment_name, "Comments: ", true);
942 }
943
944 if (app.opts.diffs)
945 {
946 for (edge_map::const_iterator e = rev.edges.begin();
947 e != rev.edges.end(); ++e)
948 dump_diffs(app.lua, db, edge_changes(e), diff_paths, out,
949 app.opts.diff_format, true,
950 !app.opts.no_show_encloser, !mask.empty());
951 }
952
953 if (next > 0)
954 next--;
955 else if (last > 0)
956 last--;
957
958 string out_system;
959 utf8_to_system_best_effort(utf8(out.str()), out_system);
960 if (app.opts.no_graph)
961 cout << out_system;
962 else
963 graph.print(rid, interesting, out_system);
964 }
965 else if (use_markings && !app.opts.no_graph)
966 graph.print(rid, interesting,
967 (F("(Revision: %s)") % rid).str());
968
969 frontier.pop(); // beware: rid is invalid from now on
970
971 for (set<revision_id>::const_iterator i = interesting.begin();
972 i != interesting.end(); ++i)
973 {
974 if (use_disallowed && (disallowed.find(*i) != disallowed.end()))
975 {
976 continue;
977 }
978 rev_height height;
979 db.get_rev_height(*i, height);
980 frontier.push(make_pair(height, *i));
981 }
982 }
983}
984
985// Local Variables:
986// mode: C++
987// fill-column: 76
988// c-file-style: "gnu"
989// indent-tabs-mode: nil
990// End:
991// 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