/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/annotate.py

  • Committer: Robert Collins
  • Date: 2007-10-05 02:41:37 UTC
  • mto: (2592.3.166 repository)
  • mto: This revision was merged to the branch mainline in revision 2896.
  • Revision ID: robertc@robertcollins.net-20071005024137-kn7brcu07nu8cwl1
* The class ``bzrlib.repofmt.knitrepo.KnitRepository3`` has been folded into
  ``KnitRepository`` by parameters to the constructor. (Robert Collins)
* ``bzrlib.xml_serializer.Serializer`` is now responsible for checking that
  mandatory attributes are present on serialisation and deserialisation.
  This fixes some holes in API usage and allows better separation between
  physical storage and object serialisation. (Robert Collins)

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
    osutils,
 
34
    patiencediff,
 
35
    tsort,
 
36
    )
 
37
from bzrlib.config import extract_email_address
 
38
 
 
39
 
 
40
def annotate_file(branch, rev_id, file_id, verbose=False, full=False,
 
41
                  to_file=None, show_ids=False):
 
42
    if to_file is None:
 
43
        to_file = sys.stdout
 
44
 
 
45
    # Handle the show_ids case
 
46
    last_rev_id = None
 
47
    if show_ids:
 
48
        annotations = _annotations(branch.repository, file_id, rev_id)
 
49
        max_origin_len = max(len(origin) for origin, text in annotations)
 
50
        for origin, text in annotations:
 
51
            if full or last_rev_id != origin:
 
52
                this = origin
 
53
            else:
 
54
                this = ''
 
55
            to_file.write('%*s | %s' % (max_origin_len, this, text))
 
56
            last_rev_id = origin
 
57
        return
 
58
 
 
59
    # Calculate the lengths of the various columns
 
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
    if not verbose:
 
68
        max_revno_len = min(max_revno_len, 12)
 
69
    max_revno_len = max(max_revno_len, 3)
 
70
 
 
71
    # Output the annotations
 
72
    prevanno = ''
 
73
    encoding = getattr(to_file, 'encoding', None) or \
 
74
            osutils.get_terminal_encoding()
 
75
    for (revno_str, author, date_str, line_rev_id, text) in annotation:
 
76
        if verbose:
 
77
            anno = '%-*s %-*s %8s ' % (max_revno_len, revno_str,
 
78
                                       max_origin_len, author, date_str)
 
79
        else:
 
80
            if len(revno_str) > max_revno_len:
 
81
                revno_str = revno_str[:max_revno_len-1] + '>'
 
82
            anno = "%-*s %-7s " % (max_revno_len, revno_str, author[:7])
 
83
        if anno.lstrip() == "" and full:
 
84
            anno = prevanno
 
85
        try:
 
86
            to_file.write(anno)
 
87
        except UnicodeEncodeError:
 
88
            # cmd_annotate should be passing in an 'exact' object, which means
 
89
            # we have a direct handle to sys.stdout or equivalent. It may not
 
90
            # be able to handle the exact Unicode characters, but 'annotate' is
 
91
            # a user function (non-scripting), so shouldn't die because of
 
92
            # unrepresentable annotation characters. So encode using 'replace',
 
93
            # and write them again.
 
94
            to_file.write(anno.encode(encoding, 'replace'))
 
95
        print >>to_file, '| %s' % (text,)
 
96
        prevanno = anno
 
97
 
 
98
 
 
99
def _annotations(repo, file_id, rev_id):
 
100
    """Return the list of (origin,text) for a revision of a file in a repository."""
 
101
    w = repo.weave_store.get_weave(file_id, repo.get_transaction())
 
102
    return list(w.annotate_iter(rev_id))
 
103
 
 
104
 
 
105
def _annotate_file(branch, rev_id, file_id):
 
106
    """Yield the origins for each line of a file.
 
107
 
 
108
    This includes detailed information, such as the author name, and
 
109
    date string for the commit, rather than just the revision id.
 
110
    """
 
111
    revision_id_to_revno = branch.get_revision_id_to_revno_map()
 
112
    annotations = _annotations(branch.repository, file_id, rev_id)
 
113
    last_origin = None
 
114
    revision_ids = set(o for o, t in annotations)
 
115
    revision_ids = [o for o in revision_ids if 
 
116
                    branch.repository.has_revision(o)]
 
117
    revisions = dict((r.revision_id, r) for r in 
 
118
                     branch.repository.get_revisions(revision_ids))
 
119
    for origin, text in annotations:
 
120
        text = text.rstrip('\r\n')
 
121
        if origin == last_origin:
 
122
            (revno_str, author, date_str) = ('','','')
 
123
        else:
 
124
            last_origin = origin
 
125
            if origin not in revisions:
 
126
                (revno_str, author, date_str) = ('?','?','?')
 
127
            else:
 
128
                revno_str = '.'.join(str(i) for i in
 
129
                                            revision_id_to_revno[origin])
 
130
            rev = revisions[origin]
 
131
            tz = rev.timezone or 0
 
132
            date_str = time.strftime('%Y%m%d',
 
133
                                     time.gmtime(rev.timestamp + tz))
 
134
            # a lazy way to get something like the email address
 
135
            # TODO: Get real email address
 
136
            author = rev.get_apparent_author()
 
137
            try:
 
138
                author = extract_email_address(author)
 
139
            except errors.NoEmailInUsername:
 
140
                pass        # use the whole name
 
141
        yield (revno_str, author, date_str, origin, text)
 
142
 
 
143
 
 
144
def reannotate(parents_lines, new_lines, new_revision_id,
 
145
               _left_matching_blocks=None):
 
146
    """Create a new annotated version from new lines and parent annotations.
 
147
    
 
148
    :param parents_lines: List of annotated lines for all parents
 
149
    :param new_lines: The un-annotated new lines
 
150
    :param new_revision_id: The revision-id to associate with new lines
 
151
        (will often be CURRENT_REVISION)
 
152
    :param left_matching_blocks: a hint about which areas are common
 
153
        between the text and its left-hand-parent.  The format is
 
154
        the SequenceMatcher.get_matching_blocks format.
 
155
    """
 
156
    if len(parents_lines) == 0:
 
157
        for line in new_lines:
 
158
            yield new_revision_id, line
 
159
    elif len(parents_lines) == 1:
 
160
        for data in _reannotate(parents_lines[0], new_lines, new_revision_id,
 
161
                                _left_matching_blocks):
 
162
            yield data
 
163
    else:
 
164
        block_list = [_left_matching_blocks] + [None] * len(parents_lines)
 
165
        reannotations = [list(_reannotate(p, new_lines, new_revision_id, b))
 
166
                         for p, b in zip(parents_lines, block_list)]
 
167
        for annos in zip(*reannotations):
 
168
            origins = set(a for a, l in annos)
 
169
            line = annos[0][1]
 
170
            if len(origins) == 1:
 
171
                yield iter(origins).next(), line
 
172
            elif len(origins) == 2 and new_revision_id in origins:
 
173
                yield (x for x in origins if x != new_revision_id).next(), line
 
174
            else:
 
175
                yield new_revision_id, line
 
176
 
 
177
 
 
178
def _reannotate(parent_lines, new_lines, new_revision_id,
 
179
                matching_blocks=None):
 
180
    new_cur = 0
 
181
    if matching_blocks is None:
 
182
        plain_parent_lines = [l for r, l in parent_lines]
 
183
        matcher = patiencediff.PatienceSequenceMatcher(None,
 
184
            plain_parent_lines, new_lines)
 
185
        matching_blocks = matcher.get_matching_blocks()
 
186
    for i, j, n in matching_blocks:
 
187
        for line in new_lines[new_cur:j]:
 
188
            yield new_revision_id, line
 
189
        for data in parent_lines[i:i+n]:
 
190
            yield data
 
191
        new_cur = j + n