monotone

monotone Mtn Source Tree

Root/annotate.cc

1// Copyright (C) 2005 Emile Snyder <emile@alumni.reed.edu>
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 <set>
12#include <iostream>
13
14#include <boost/shared_ptr.hpp>
15#include <boost/multi_index_container.hpp>
16#include <boost/multi_index/ordered_index.hpp>
17#include <boost/multi_index/key_extractors.hpp>
18
19#include "annotate.hh"
20#include "cert.hh"
21#include "constants.hh"
22#include "cset.hh"
23#include "database.hh"
24#include "date_format.hh"
25#include "interner.hh"
26#include "lcs.hh"
27#include "platform.hh"
28#include "project.hh"
29#include "revision.hh"
30#include "sanity.hh"
31#include "simplestring_xform.hh"
32#include "transforms.hh"
33#include "ui.hh"
34#include "vocab.hh"
35#include "rev_height.hh"
36#include "roster.hh"
37
38using std::back_insert_iterator;
39using std::back_inserter;
40using std::cout;
41using std::map;
42using std::min;
43using std::set;
44using std::string;
45using std::vector;
46using std::pair;
47
48using boost::shared_ptr;
49
50using boost::multi_index::multi_index_container;
51using boost::multi_index::indexed_by;
52using boost::multi_index::ordered_unique;
53using boost::multi_index::tag;
54using boost::multi_index::member;
55
56
57class annotate_lineage_mapping;
58
59
60class annotate_context
61{
62public:
63 annotate_context(app_state & app, project_t & project, file_id fid);
64
65 shared_ptr<annotate_lineage_mapping> initial_lineage() const;
66
67 /// credit any uncopied lines (as recorded in copied_lines) to
68 /// rev, and reset copied_lines.
69 void evaluate(revision_id rev);
70
71 void set_copied(int index);
72 void set_touched(int index);
73
74 void set_equivalent(int index, int index2);
75 void annotate_equivalent_lines();
76
77 /// return true if we have no more unassigned lines
78 bool is_complete() const;
79
80 void dump(bool just_revs) const;
81
82 string get_line(int line_index) const
83 {
84 return file_lines[line_index];
85 }
86
87private:
88 void build_revisions_to_annotations(map<revision_id, string> & r2a) const;
89
90 app_state & app;
91 project_t & project;
92
93 /// keep a count so we can tell quickly whether we can terminate
94 size_t annotated_lines_completed;
95
96 vector<string> file_lines;
97 vector<revision_id> annotations;
98
99 /// equivalent_lines[n] = m means that line n should be blamed to the same
100 /// revision as line m
101 map<int, int> equivalent_lines;
102
103 // elements of the set are indexes into the array of lines in the
104 // UDOI lineages add entries here when they notice that they copied
105 // a line from the UDOI
106 set<size_t> copied_lines;
107
108 // similarly, lineages add entries here for all the lines from the
109 // UDOI they know about that they didn't copy
110 set<size_t> touched_lines;
111};
112
113
114/*
115 An annotate_lineage_mapping tells you, for each line of a file,
116 where in the ultimate descendent of interest (UDOI) the line came
117 from (a line not present in the UDOI is represented as -1).
118*/
119class annotate_lineage_mapping
120{
121public:
122 annotate_lineage_mapping(file_data const & data);
123 annotate_lineage_mapping(vector<string> const & lines);
124
125 // debugging
126 //bool equal_interned (const annotate_lineage_mapping &rhs) const;
127
128 /// need a better name. does the work of setting copied bits in the
129 /// context object.
130 shared_ptr<annotate_lineage_mapping>
131 build_parent_lineage(shared_ptr<annotate_context> acp,
132 revision_id parent_rev,
133 file_data const & parent_data) const;
134
135 void merge(annotate_lineage_mapping const & other,
136 shared_ptr<annotate_context> const & acp);
137
138 void credit_mapped_lines(shared_ptr<annotate_context> acp) const;
139 void set_copied_all_mapped(shared_ptr<annotate_context> acp) const;
140
141private:
142 void init_with_lines(vector<string> const & lines);
143
144 static interner<long> in; // FIX, testing hack
145
146 vector<long, QA(long)> file_interned;
147
148 // maps an index into the vector of lines for our current version of
149 // the file into an index into the vector of lines of the UDOI:
150 // eg. if the line file_interned[i] will turn into line 4 in the
151 // UDOI, mapping[i] = 4
152 vector<int> mapping;
153};
154
155interner<long> annotate_lineage_mapping::in;
156
157
158/*
159 annotate_node_work encapsulates the input data needed to process
160 the annotations for a given childrev, considering all the
161 childrev -> parentrevN edges.
162*/
163struct annotate_node_work
164{
165 annotate_node_work(shared_ptr<annotate_context> annotations_,
166 shared_ptr<annotate_lineage_mapping> lineage_,
167 revision_id revision_, node_id fid_,
168 rev_height height_,
169 set<revision_id> interesting_ancestors_,
170 file_id content_,
171 bool marked_)
172 : annotations(annotations_),
173 lineage(lineage_),
174 revision(revision_),
175 fid(fid_),
176 height(height_),
177 interesting_ancestors(interesting_ancestors_),
178 content(content_),
179 marked(marked_)
180 {}
181
182 shared_ptr<annotate_context> annotations;
183 shared_ptr<annotate_lineage_mapping> lineage;
184 revision_id revision;
185 node_id fid;
186 rev_height height;
187 set<revision_id> interesting_ancestors;
188 file_id content;
189 bool marked;
190};
191
192struct by_rev {};
193
194// instead of using a priority queue and a set to keep track of the already
195// seen revisions, we use a multi index container. it stores work units
196// indexed by both, their revision and their revision's height, with the
197// latter being used by default. usage of that data structure frees us from
198// the burden of keeping two data structures in sync.
199typedef multi_index_container<
200 annotate_node_work,
201 indexed_by<
202 ordered_unique<
203 member<annotate_node_work,rev_height,&annotate_node_work::height>,
204 std::greater<rev_height> >,
205 ordered_unique<
206 tag<by_rev>,
207 member<annotate_node_work,revision_id,&annotate_node_work::revision> >
208 >
209 > work_units;
210
211
212annotate_context::annotate_context(app_state & app, project_t & project, file_id fid)
213 : app(app), project(project), annotated_lines_completed(0)
214{
215 // initialize file_lines
216 file_data fpacked;
217 project.db.get_file_version(fid, fpacked);
218 string encoding = constants::default_encoding; // FIXME
219 split_into_lines(fpacked.inner()(), encoding, file_lines);
220 L(FL("annotate_context::annotate_context initialized "
221 "with %d file lines\n") % file_lines.size());
222
223 // initialize annotations
224 revision_id nullid;
225 annotations.clear();
226 annotations.reserve(file_lines.size());
227 annotations.insert(annotations.begin(), file_lines.size(), nullid);
228 L(FL("annotate_context::annotate_context initialized "
229 "with %d entries in annotations\n") % annotations.size());
230
231 // initialize copied_lines and touched_lines
232 copied_lines.clear();
233 touched_lines.clear();
234}
235
236
237shared_ptr<annotate_lineage_mapping>
238annotate_context::initial_lineage() const
239{
240 shared_ptr<annotate_lineage_mapping>
241 res(new annotate_lineage_mapping(file_lines));
242 return res;
243}
244
245
246void
247annotate_context::evaluate(revision_id rev)
248{
249 revision_id nullid;
250 I(copied_lines.size() <= annotations.size());
251 I(touched_lines.size() <= annotations.size());
252
253 // Find the lines that we touched but that no other parent copied.
254 set<size_t> credit_lines;
255 set_difference(touched_lines.begin(), touched_lines.end(),
256 copied_lines.begin(), copied_lines.end(),
257 inserter(credit_lines, credit_lines.begin()));
258
259 set<size_t>::const_iterator i;
260 for (i = credit_lines.begin(); i != credit_lines.end(); i++)
261 {
262 I(*i < annotations.size());
263 if (annotations[*i] == nullid)
264 {
265 // L(FL("evaluate setting annotations[%d] -> %s, since "
266 // "touched_lines contained %d, copied_lines didn't and "
267 // "annotations[%d] was nullid\n") % *i % rev % *i % *i);
268
269 annotations[*i] = rev;
270 annotated_lines_completed++;
271 }
272 else
273 {
274 //L(FL("evaluate LEAVING annotations[%d] -> %s")
275 // % *i % annotations[*i]);
276 }
277 }
278
279 copied_lines.clear();
280 touched_lines.clear();
281}
282
283void
284annotate_context::set_copied(int index)
285{
286 //L(FL("annotate_context::set_copied %d") % index);
287
288 if (index == -1)
289 return;
290
291 I(index >= 0 && index < (int)file_lines.size());
292 copied_lines.insert(index);
293}
294
295void
296annotate_context::set_touched(int index)
297{
298 //L(FL("annotate_context::set_touched %d") % index);
299
300 if (index == -1)
301 return;
302
303 I(index >= 0 && index <= (int)file_lines.size());
304 touched_lines.insert(index);
305}
306
307void
308annotate_context::set_equivalent(int index, int index2)
309{
310 L(FL("annotate_context::set_equivalent "
311 "index %d index2 %d\n") % index % index2);
312 equivalent_lines[index] = index2;
313}
314
315void
316annotate_context::annotate_equivalent_lines()
317{
318 revision_id null_id;
319
320 for (size_t i=0; i<annotations.size(); i++)
321 {
322 if (annotations[i] == null_id)
323 {
324 map<int, int>::const_iterator j = equivalent_lines.find(i);
325 if (j == equivalent_lines.end())
326 {
327 L(FL("annotate_equivalent_lines unable to find "
328 "equivalent for line %d\n") % i);
329 }
330 I(j != equivalent_lines.end());
331 annotations[i] = annotations[j->second];
332 annotated_lines_completed++;
333 }
334 }
335}
336
337bool
338annotate_context::is_complete() const
339{
340 if (annotated_lines_completed == annotations.size())
341 return true;
342
343 I(annotated_lines_completed < annotations.size());
344 return false;
345}
346
347static string
348cert_string_value(vector<cert> const & certs,
349 cert_name const & name,
350 bool from_start, bool from_end,
351 string const & sep)
352{
353 for (vector<cert>::const_iterator i = certs.begin();
354 i != certs.end(); ++i)
355 {
356 if (i->name == name)
357 {
358 cert_value tv(i->value);
359 string::size_type f = 0;
360 string::size_type l = string::npos;
361 if (from_start)
362 l = tv ().find_first_of(sep);
363 if (from_end)
364 {
365 f = tv ().find_last_of(sep);
366 if (f == string::npos)
367 f = 0;
368 }
369 return tv().substr(f, l);
370 }
371 }
372
373 return "";
374}
375
376static string
377cert_date_value(vector<cert> const & certs,
378 cert_name const & name,
379 bool from_start, bool from_end,
380 string const & fmt)
381{
382 string certval = cert_string_value(certs, name, from_start, from_end, "");
383 if (fmt.empty() || certval.empty())
384 return certval;
385 return date_t(certval).as_formatted_localtime(fmt);
386}
387
388void
389annotate_context::build_revisions_to_annotations
390(map<revision_id, string> & revs_to_notations) const
391{
392 I(annotations.size() == file_lines.size());
393
394 // build set of unique revisions present in annotations
395 set<revision_id> seen;
396 for (vector<revision_id>::const_iterator i = annotations.begin();
397 i != annotations.end(); i++)
398 {
399 seen.insert(*i);
400 }
401
402 size_t max_note_length = 0;
403 string date_fmt = get_date_format(app.opts, app.lua, date_short);
404
405 // build revision -> annotation string mapping
406 for (set<revision_id>::const_iterator i = seen.begin();
407 i != seen.end(); i++)
408 {
409 vector<cert> certs;
410 project.get_revision_certs(*i, certs);
411 project.db.erase_bogus_certs(project, certs);
412
413 string author(cert_string_value(certs, author_cert_name,
414 true, false, "@< "));
415
416 string date(cert_date_value(certs, date_cert_name, true, false, date_fmt));
417
418 string hex_rev_str(encode_hexenc(i->inner()(), i->inner().made_from));
419
420 string result;
421 // author and / or date could be empty for two reasons
422 // a) the specific certs are untrusted by the user, and as such
423 // got erased earlier from the set
424 // b) the specific revision does not contain (for whatever reason)
425 // the needed certificate
426 // for both reasons we change the output slightly so that no unwanted
427 // spaces pop up
428 if (!author.empty() && !date.empty())
429 result = (F("%s.. by %s %s: ") % hex_rev_str.substr(0, 8) % author % date).str();
430 else if (!author.empty())
431 result = (F("%s.. by %s: ") % hex_rev_str.substr(0, 8) % author).str();
432 else if (!date.empty())
433 result = (F("%s.. %s: ") % hex_rev_str.substr(0, 8) % date).str();
434 else
435 result = (F("%s..: ") % hex_rev_str.substr(0, 8)).str();
436
437 max_note_length = ((result.size() > max_note_length)
438 ? result.size()
439 : max_note_length);
440 revs_to_notations[*i] = result;
441 }
442
443 // justify annotation strings
444 for (map<revision_id, string>::iterator i = revs_to_notations.begin();
445 i != revs_to_notations.end(); i++)
446 {
447 size_t l = i->second.size();
448 i->second.insert(string::size_type(0), max_note_length - l, ' ');
449 }
450}
451
452void
453annotate_context::dump(bool just_revs) const
454{
455 revision_id nullid;
456 I(annotations.size() == file_lines.size());
457
458 if (file_lines.empty())
459 return;
460
461 map<revision_id, string> revs_to_notations;
462 string empty_note;
463 if (!just_revs)
464 {
465 build_revisions_to_annotations(revs_to_notations);
466 size_t max_note_length = revs_to_notations.begin()->second.size();
467 empty_note.insert(string::size_type(0), max_note_length - 2, ' ');
468 }
469
470 revision_id lastid = nullid;
471 for (size_t i = 0; i < file_lines.size(); i++)
472 {
473 //I(! (annotations[i] == nullid) );
474 if (!just_revs)
475 {
476 if (lastid == annotations[i])
477 cout << empty_note << ": "
478 << file_lines[i] << '\n';
479 else
480 cout << revs_to_notations[annotations[i]]
481 << file_lines[i] << '\n';
482 lastid = annotations[i];
483 }
484 else
485 cout << annotations[i] << ": "
486 << file_lines[i] << '\n';
487 }
488}
489
490annotate_lineage_mapping::annotate_lineage_mapping(file_data const & data)
491{
492 // split into lines
493 vector<string> lines;
494 string encoding = constants::default_encoding; // FIXME
495 split_into_lines (data.inner()().data(), encoding, lines);
496
497 init_with_lines(lines);
498}
499
500annotate_lineage_mapping::annotate_lineage_mapping
501(vector<string> const & lines)
502{
503 init_with_lines(lines);
504}
505
506/*
507bool
508annotate_lineage_mapping::equal_interned
509(annotate_lineage_mapping const & rhs) const
510{
511 bool result = true;
512
513 if (file_interned.size() != rhs.file_interned.size()) {
514 L(FL("annotate_lineage_mapping::equal_interned "
515 "lhs size %d != rhs size %d\n")
516 % file_interned.size() % rhs.file_interned.size());
517 result = false;
518 }
519
520 size_t limit = min(file_interned.size(), rhs.file_interned.size());
521 for (size_t i=0; i<limit; i++)
522 {
523 if (file_interned[i] != rhs.file_interned[i])
524 {
525 L(FL("annotate_lineage_mapping::equal_interned "
526 "lhs[%d]:%ld != rhs[%d]:%ld\n")
527 % i % file_interned[i] % i % rhs.file_interned[i]);
528 result = false;
529 }
530 }
531
532 return result;
533}
534*/
535
536void
537annotate_lineage_mapping::init_with_lines(vector<string> const & lines)
538{
539 file_interned.clear();
540 file_interned.reserve(lines.size());
541 mapping.clear();
542 mapping.reserve(lines.size());
543
544 int count;
545 vector<string>::const_iterator i;
546 for (count=0, i = lines.begin(); i != lines.end(); i++, count++)
547 {
548 file_interned.push_back(in.intern(*i));
549 mapping.push_back(count);
550 }
551 L(FL("annotate_lineage_mapping::init_with_lines "
552 "ending with %d entries in mapping\n") % mapping.size());
553}
554
555shared_ptr<annotate_lineage_mapping>
556annotate_lineage_mapping::build_parent_lineage
557(shared_ptr<annotate_context> acp,
558 revision_id parent_rev,
559 file_data const & parent_data) const
560{
561 bool verbose = false;
562 shared_ptr<annotate_lineage_mapping>
563 parent_lineage(new annotate_lineage_mapping(parent_data));
564
565 vector<long, QA(long)> lcs;
566 back_insert_iterator< vector<long, QA(long)> > bii(lcs);
567 longest_common_subsequence(file_interned.begin(),
568 file_interned.end(),
569 parent_lineage->file_interned.begin(),
570 parent_lineage->file_interned.end(),
571 min(file_interned.size(),
572 parent_lineage->file_interned.size()),
573 back_inserter(lcs));
574
575 if (verbose)
576 L(FL("build_parent_lineage: "
577 "file_lines.size() == %d, "
578 "parent.file_lines.size() == %d, "
579 "lcs.size() == %d\n")
580 % file_interned.size()
581 % parent_lineage->file_interned.size()
582 % lcs.size());
583
584 // do the copied lines thing for our annotate_context
585 vector<long> lcs_src_lines;
586 lcs_src_lines.resize(lcs.size());
587 size_t i, j;
588 i = j = 0;
589 while (i < file_interned.size() && j < lcs.size())
590 {
591 //if (verbose)
592 if (file_interned[i] == 14)
593 L(FL("%s file_interned[%d]: %ld\tlcs[%d]: %ld\tmapping[%d]: %ld")
594 % parent_rev % i % file_interned[i] % j % lcs[j] % i % mapping[i]);
595
596 if (file_interned[i] == lcs[j])
597 {
598 acp->set_copied(mapping[i]);
599 lcs_src_lines[j] = mapping[i];
600 j++;
601 }
602 else
603 {
604 acp->set_touched(mapping[i]);
605 }
606
607 i++;
608 }
609 if (verbose)
610 L(FL("loop ended with i: %d, j: %d, lcs.size(): %d")
611 % i % j % lcs.size());
612 I(j == lcs.size());
613
614 // set touched for the rest of the lines in the file
615 while (i < file_interned.size())
616 {
617 acp->set_touched(mapping[i]);
618 i++;
619 }
620
621 // determine the mapping for parent lineage
622 if (verbose)
623 L(FL("build_parent_lineage: building mapping now "
624 "for parent_rev %s\n") % parent_rev);
625
626 i = j = 0;
627
628 while (i < parent_lineage->file_interned.size() && j < lcs.size())
629 {
630 if (parent_lineage->file_interned[i] == lcs[j])
631 {
632 parent_lineage->mapping[i] = lcs_src_lines[j];
633 j++;
634 }
635 else
636 {
637 parent_lineage->mapping[i] = -1;
638 }
639 if (verbose)
640 L(FL("mapping[%d] -> %d") % i % parent_lineage->mapping[i]);
641
642 i++;
643 }
644 I(j == lcs.size());
645 // set mapping for the rest of the lines in the file
646 while (i < parent_lineage->file_interned.size())
647 {
648 parent_lineage->mapping[i] = -1;
649 if (verbose)
650 L(FL("mapping[%d] -> %d") % i % parent_lineage->mapping[i]);
651 i++;
652 }
653
654 return parent_lineage;
655}
656
657void
658annotate_lineage_mapping::merge(annotate_lineage_mapping const & other,
659 shared_ptr<annotate_context> const & acp)
660{
661 I(file_interned.size() == other.file_interned.size());
662 I(mapping.size() == other.mapping.size());
663 //I(equal_interned(other)); // expensive check
664
665 for (size_t i=0; i<mapping.size(); i++)
666 {
667 if (mapping[i] == -1 && other.mapping[i] >= 0)
668 mapping[i] = other.mapping[i];
669
670 if (mapping[i] >= 0 && other.mapping[i] >= 0)
671 {
672 //I(mapping[i] == other.mapping[i]);
673 if (mapping[i] != other.mapping[i])
674 {
675 // a given line in the current merged mapping will split
676 // and become multiple lines in the UDOI. so we have to
677 // remember that whenever we ultimately assign blame for
678 // mapping[i] we blame the same revision on
679 // other.mapping[i].
680 acp->set_equivalent(other.mapping[i], mapping[i]);
681 }
682 }
683 }
684}
685
686void
687annotate_lineage_mapping::credit_mapped_lines
688(shared_ptr<annotate_context> acp) const
689{
690 vector<int>::const_iterator i;
691 for (i=mapping.begin(); i != mapping.end(); i++)
692 {
693 acp->set_touched(*i);
694 }
695}
696
697void
698annotate_lineage_mapping::set_copied_all_mapped
699(shared_ptr<annotate_context> acp) const
700{
701 vector<int>::const_iterator i;
702 for (i=mapping.begin(); i != mapping.end(); i++)
703 {
704 acp->set_copied(*i);
705 }
706}
707
708// fetches the list of file_content markings for the given revision_id and
709// node_id
710static void get_file_content_marks(database & db,
711 revision_id const & rev,
712 node_id const & fid,
713 set<revision_id> & content_marks)
714{
715 const_marking_t markings;
716 db.get_markings(rev, fid, markings);
717
718 I(!markings->file_content.empty());
719
720 content_marks.clear();
721 content_marks.insert(markings->file_content.begin(),
722 markings->file_content.end());
723}
724
725static void
726do_annotate_node(database & db,
727 annotate_node_work const & work_unit,
728 work_units & work_units)
729{
730 L(FL("do_annotate_node for node %s") % work_unit.revision);
731
732 size_t added_in_parent_count = 0;
733
734 for (set<revision_id>::const_iterator i = work_unit.interesting_ancestors.begin();
735 i != work_unit.interesting_ancestors.end(); i++)
736 {
737 // here, 'parent' means either a real parent or one of the marked
738 // ancestors, depending on whether work_unit.marked is true.
739 revision_id parent_revision = *i;
740
741 L(FL("do_annotate_node processing edge from parent %s to child %s")
742 % parent_revision % work_unit.revision);
743
744 I(!(work_unit.revision == parent_revision));
745
746 file_id file_in_parent;
747
748 work_units::index<by_rev>::type::iterator lmn =
749 work_units.get<by_rev>().find(parent_revision);
750
751 // find out the content hash of the file in parent.
752 if (lmn != work_units.get<by_rev>().end())
753 // we already got the content hash.
754 file_in_parent = lmn->content;
755 else
756 {
757 if (work_unit.marked)
758 db.get_file_content(parent_revision, work_unit.fid, file_in_parent);
759 else
760 // we are not marked, so parent is marked.
761 file_in_parent = work_unit.content;
762 }
763
764 // stop if file is not present in the parent.
765 if (null_id(file_in_parent))
766 {
767 L(FL("file added in %s, continuing") % work_unit.revision);
768 added_in_parent_count++;
769 continue;
770 }
771
772 // the node was live in the parent, so this represents a delta.
773 shared_ptr<annotate_lineage_mapping> parent_lineage;
774
775 if (file_in_parent == work_unit.content)
776 {
777 L(FL("parent file identical, "
778 "set copied all mapped and copy lineage\n"));
779 parent_lineage = work_unit.lineage;
780 parent_lineage->set_copied_all_mapped(work_unit.annotations);
781 }
782 else
783 {
784 file_data data;
785 db.get_file_version(file_in_parent, data);
786 L(FL("building parent lineage for parent file %s")
787 % file_in_parent);
788 parent_lineage
789 = work_unit.lineage->build_parent_lineage(work_unit.annotations,
790 parent_revision,
791 data);
792 }
793
794 // If this parent has not yet been queued for processing, create the
795 // work unit for it.
796 if (lmn == work_units.get<by_rev>().end())
797 {
798 set<revision_id> parents_interesting_ancestors;
799 bool parent_marked;
800
801 if (work_unit.marked)
802 {
803 // we are marked, thus we don't know a priori whether parent
804 // is marked or not.
805 get_file_content_marks(db, parent_revision, work_unit.fid, parents_interesting_ancestors);
806 parent_marked = (parents_interesting_ancestors.size() == 1
807 && *(parents_interesting_ancestors.begin()) == parent_revision);
808 }
809 else
810 parent_marked = true;
811
812 // if it's marked, we need to look at its parents instead.
813 if (parent_marked)
814 db.get_revision_parents(parent_revision, parents_interesting_ancestors);
815
816 rev_height parent_height;
817 db.get_rev_height(parent_revision, parent_height);
818 annotate_node_work newunit(work_unit.annotations,
819 parent_lineage,
820 parent_revision,
821 work_unit.fid,
822 parent_height,
823 parents_interesting_ancestors,
824 file_in_parent,
825 parent_marked);
826 work_units.insert(newunit);
827 }
828 else
829 {
830 // already a pending node, so we just have to merge the lineage.
831 L(FL("merging lineage from node %s to parent %s")
832 % work_unit.revision % parent_revision);
833
834 lmn->lineage->merge(*parent_lineage, work_unit.annotations);
835 }
836 }
837
838 if (added_in_parent_count == work_unit.interesting_ancestors.size())
839 {
840 work_unit.lineage->credit_mapped_lines(work_unit.annotations);
841 }
842
843 work_unit.annotations->evaluate(work_unit.revision);
844}
845
846void
847do_annotate (app_state & app, project_t & project, const_file_t file_node,
848 revision_id rid, bool just_revs)
849{
850 L(FL("annotating file %s with content %s in revision %s")
851 % file_node->self % file_node->content % rid);
852
853 shared_ptr<annotate_context>
854 acp(new annotate_context(app, project, file_node->content));
855
856 shared_ptr<annotate_lineage_mapping> lineage
857 = acp->initial_lineage();
858
859 work_units work_units;
860 {
861 // prepare the first work_unit
862 rev_height height;
863 project.db.get_rev_height(rid, height);
864 set<revision_id> rids_interesting_ancestors;
865 get_file_content_marks(project.db, rid, file_node->self,
866 rids_interesting_ancestors);
867 bool rid_marked = (rids_interesting_ancestors.size() == 1
868 && *(rids_interesting_ancestors.begin()) == rid);
869 if (rid_marked)
870 project.db.get_revision_parents(rid, rids_interesting_ancestors);
871
872 annotate_node_work workunit(acp, lineage, rid, file_node->self, height,
873 rids_interesting_ancestors, file_node->content,
874 rid_marked);
875 work_units.insert(workunit);
876 }
877
878 while (!(work_units.empty() || acp->is_complete()))
879 {
880 // get the work unit for the revision with the greatest height
881 work_units::iterator w = work_units.begin();
882 I(w != work_units.end());
883
884 // do_annotate_node() might insert new work units into work_units, and
885 // thus might invalidate the iterator
886 annotate_node_work work = *w;
887 work_units.erase(w);
888
889 do_annotate_node(project.db, work, work_units);
890 }
891
892 acp->annotate_equivalent_lines();
893 I(acp->is_complete());
894
895 acp->dump(just_revs);
896}
897
898// Local Variables:
899// mode: C++
900// fill-column: 76
901// c-file-style: "gnu"
902// indent-tabs-mode: nil
903// End:
904// 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