~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/annotate.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-15 22:35:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2363.
  • Revision ID: john@arbash-meinel.com-20070315223535-d3d4964oe1hc8zhg
Add an overzealous test, for Unicode support of _iter_changes.
For both knowns and unknowns.
And include a basic, if suboptimal, fix.
I would rather defer the decoding until we've determined that we are going to return the tuple.
There is still something broken with added files, but I'll get to that.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""File annotate based on weave storage"""
 
18
 
 
19
# TODO: Choice of more or less verbose formats:
 
20
 
21
# interposed: show more details between blocks of modified lines
 
22
 
 
23
# TODO: Show which revision caused a line to merge into the parent
 
24
 
 
25
# TODO: perhaps abbreviate timescales depending on how recent they are
 
26
# e.g. "3:12 Tue", "13 Oct", "Oct 2005", etc.  
 
27
 
 
28
import sys
 
29
import time
 
30
 
 
31
from bzrlib import (
 
32
    errors,
 
33
    patiencediff,
 
34
    tsort,
 
35
    )
 
36
from bzrlib.config import extract_email_address
 
37
 
 
38
 
 
39
def annotate_file(branch, rev_id, file_id, verbose=False, full=False,
 
40
                  to_file=None, show_ids=False):
 
41
    if to_file is None:
 
42
        to_file = sys.stdout
 
43
 
 
44
    prevanno=''
 
45
    last_rev_id = None
 
46
    if show_ids:
 
47
        w = branch.repository.weave_store.get_weave(file_id,
 
48
            branch.repository.get_transaction())
 
49
        annotations = list(w.annotate_iter(rev_id))
 
50
        max_origin_len = max(len(origin) for origin, text in annotations)
 
51
        for origin, text in annotations:
 
52
            if full or last_rev_id != origin:
 
53
                this = origin
 
54
            else:
 
55
                this = ''
 
56
            to_file.write('%*s | %s' % (max_origin_len, this, text))
 
57
            last_rev_id = origin
 
58
        return
 
59
 
 
60
    annotation = list(_annotate_file(branch, rev_id, file_id))
 
61
    if len(annotation) == 0:
 
62
        max_origin_len = max_revno_len = max_revid_len = 0
 
63
    else:
 
64
        max_origin_len = max(len(x[1]) for x in annotation)
 
65
        max_revno_len = max(len(x[0]) for x in annotation)
 
66
        max_revid_len = max(len(x[3]) for x in annotation)
 
67
 
 
68
    if not verbose:
 
69
        max_revno_len = min(max_revno_len, 12)
 
70
    max_revno_len = max(max_revno_len, 3)
 
71
 
 
72
    for (revno_str, author, date_str, line_rev_id, text) in annotation:
 
73
        if verbose:
 
74
            anno = '%-*s %-*s %8s ' % (max_revno_len, revno_str,
 
75
                                       max_origin_len, author, date_str)
 
76
        else:
 
77
            if len(revno_str) > max_revno_len:
 
78
                revno_str = revno_str[:max_revno_len-1] + '>'
 
79
            anno = "%-*s %-7s " % (max_revno_len, revno_str, author[:7])
 
80
 
 
81
        if anno.lstrip() == "" and full: anno = prevanno
 
82
        print >>to_file, '%s| %s' % (anno, text)
 
83
        prevanno=anno
 
84
 
 
85
 
 
86
def _annotate_file(branch, rev_id, file_id):
 
87
    """Yield the origins for each line of a file.
 
88
 
 
89
    This includes detailed information, such as the committer name, and
 
90
    date string for the commit, rather than just the revision id.
 
91
    """
 
92
    branch_last_revision = branch.last_revision()
 
93
    revision_graph = branch.repository.get_revision_graph(branch_last_revision)
 
94
    merge_sorted_revisions = tsort.merge_sort(
 
95
        revision_graph,
 
96
        branch_last_revision,
 
97
        None,
 
98
        generate_revno=True)
 
99
    revision_id_to_revno = dict((rev_id, revno)
 
100
                                for seq_num, rev_id, depth, revno, end_of_merge
 
101
                                 in merge_sorted_revisions)
 
102
    w = branch.repository.weave_store.get_weave(file_id,
 
103
        branch.repository.get_transaction())
 
104
    last_origin = None
 
105
    annotations = list(w.annotate_iter(rev_id))
 
106
    revision_ids = set(o for o, t in annotations)
 
107
    revision_ids = [o for o in revision_ids if 
 
108
                    branch.repository.has_revision(o)]
 
109
    revisions = dict((r.revision_id, r) for r in 
 
110
                     branch.repository.get_revisions(revision_ids))
 
111
    for origin, text in annotations:
 
112
        text = text.rstrip('\r\n')
 
113
        if origin == last_origin:
 
114
            (revno_str, author, date_str) = ('','','')
 
115
        else:
 
116
            last_origin = origin
 
117
            if origin not in revisions:
 
118
                (revno_str, author, date_str) = ('?','?','?')
 
119
            else:
 
120
                revno_str = '.'.join(str(i) for i in
 
121
                                            revision_id_to_revno[origin])
 
122
            rev = revisions[origin]
 
123
            tz = rev.timezone or 0
 
124
            date_str = time.strftime('%Y%m%d',
 
125
                                     time.gmtime(rev.timestamp + tz))
 
126
            # a lazy way to get something like the email address
 
127
            # TODO: Get real email address
 
128
            author = rev.committer
 
129
            try:
 
130
                author = extract_email_address(author)
 
131
            except errors.NoEmailInUsername:
 
132
                pass        # use the whole name
 
133
        yield (revno_str, author, date_str, origin, text)
 
134
 
 
135
 
 
136
def reannotate(parents_lines, new_lines, new_revision_id):
 
137
    """Create a new annotated version from new lines and parent annotations.
 
138
    
 
139
    :param parents_lines: List of annotated lines for all parents
 
140
    :param new_lines: The un-annotated new lines
 
141
    :param new_revision_id: The revision-id to associate with new lines
 
142
        (will often be CURRENT_REVISION)
 
143
    """
 
144
    if len(parents_lines) == 1:
 
145
        for data in _reannotate(parents_lines[0], new_lines, new_revision_id):
 
146
            yield data
 
147
    else:
 
148
        reannotations = [list(_reannotate(p, new_lines, new_revision_id)) for
 
149
                         p in parents_lines]
 
150
        for annos in zip(*reannotations):
 
151
            origins = set(a for a, l in annos)
 
152
            line = annos[0][1]
 
153
            if len(origins) == 1:
 
154
                yield iter(origins).next(), line
 
155
            elif len(origins) == 2 and new_revision_id in origins:
 
156
                yield (x for x in origins if x != new_revision_id).next(), line
 
157
            else:
 
158
                yield new_revision_id, line
 
159
 
 
160
 
 
161
def _reannotate(parent_lines, new_lines, new_revision_id):
 
162
    plain_parent_lines = [l for r, l in parent_lines]
 
163
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_parent_lines,
 
164
                                                   new_lines)
 
165
    new_cur = 0
 
166
    for i, j, n in matcher.get_matching_blocks():
 
167
        for line in new_lines[new_cur:j]:
 
168
            yield new_revision_id, line
 
169
        for data in parent_lines[i:i+n]:
 
170
            yield data
 
171
        new_cur = j + n