~bzr-pqm/bzr/bzr.dev

2245.3.2 by John Arbash Meinel
Cleanup according to Wouter's suggestions.
1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
2
#
1385 by Martin Pool
- simple weave-based annotate code (not complete)
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.
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
7
#
1385 by Martin Pool
- simple weave-based annotate code (not complete)
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.
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
12
#
1385 by Martin Pool
- simple weave-based annotate code (not complete)
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
1185.16.8 by Martin Pool
doc
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
1185.16.57 by Martin Pool
[merge] from aaron
25
# TODO: perhaps abbreviate timescales depending on how recent they are
26
# e.g. "3:12 Tue", "13 Oct", "Oct 2005", etc.  
27
1385 by Martin Pool
- simple weave-based annotate code (not complete)
28
import sys
1185.16.1 by Martin Pool
- update annotate for new branch api
29
import time
1385 by Martin Pool
- simple weave-based annotate code (not complete)
30
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
31
from bzrlib import (
32
    errors,
2593.1.3 by Adeodato Simó
Cope with to_file.encoding being None or not present.
33
    osutils,
1551.9.19 by Aaron Bentley
Merge from bzr.dev
34
    patiencediff,
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
35
    tsort,
36
    )
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
37
from bzrlib.config import extract_email_address
38
39
40
def annotate_file(branch, rev_id, file_id, verbose=False, full=False,
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
41
                  to_file=None, show_ids=False):
1385 by Martin Pool
- simple weave-based annotate code (not complete)
42
    if to_file is None:
43
        to_file = sys.stdout
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
44
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
45
    # Handle the show_ids case
2182.3.9 by John Arbash Meinel
Faster annotate --show-ids. No need to pull the history or the revision info
46
    last_rev_id = None
47
    if show_ids:
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
48
        annotations = _annotations(branch.repository, file_id, rev_id)
2182.3.9 by John Arbash Meinel
Faster annotate --show-ids. No need to pull the history or the revision info
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
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
59
    # Calculate the lengths of the various columns
1185.33.39 by Martin Pool
[patch] annotate --long (robey pointer)
60
    annotation = list(_annotate_file(branch, rev_id, file_id))
2027.3.1 by John Arbash Meinel
'bzr annotate' shouldn't fail on an empty file: fix bug #56814
61
    if len(annotation) == 0:
2182.3.4 by John Arbash Meinel
add show-ids and test that nearby areas are collapsed without full
62
        max_origin_len = max_revno_len = max_revid_len = 0
2027.3.1 by John Arbash Meinel
'bzr annotate' shouldn't fail on an empty file: fix bug #56814
63
    else:
2182.3.8 by John Arbash Meinel
Some cleanup to make annotate.py < 79 chars wide.
64
        max_origin_len = max(len(x[1]) for x in annotation)
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
65
        max_revno_len = max(len(x[0]) for x in annotation)
2182.3.4 by John Arbash Meinel
add show-ids and test that nearby areas are collapsed without full
66
        max_revid_len = max(len(x[3]) for x in annotation)
2182.3.2 by John Arbash Meinel
Use shortened revnos unless --long is supplied
67
    if not verbose:
2182.3.7 by John Arbash Meinel
Cleanup and add blackbox tests for annotate.
68
        max_revno_len = min(max_revno_len, 12)
69
    max_revno_len = max(max_revno_len, 3)
2182.3.2 by John Arbash Meinel
Use shortened revnos unless --long is supplied
70
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
71
    # Output the annotations
72
    prevanno = ''
73
    encoding = getattr(to_file, 'encoding', None) or \
74
            osutils.get_terminal_encoding()
2182.3.4 by John Arbash Meinel
add show-ids and test that nearby areas are collapsed without full
75
    for (revno_str, author, date_str, line_rev_id, text) in annotation:
2182.3.9 by John Arbash Meinel
Faster annotate --show-ids. No need to pull the history or the revision info
76
        if verbose:
77
            anno = '%-*s %-*s %8s ' % (max_revno_len, revno_str,
78
                                       max_origin_len, author, date_str)
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
79
        else:
2182.3.9 by John Arbash Meinel
Faster annotate --show-ids. No need to pull the history or the revision info
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])
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
83
        if anno.lstrip() == "" and full:
84
            anno = prevanno
2593.1.1 by Adeodato Simó
Improve annotate to prevent unicode exceptions in certain situations.
85
        try:
86
            to_file.write(anno)
87
        except UnicodeEncodeError:
2593.1.4 by Adeodato Simó
Add comment from John to the try/except block.
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.
2593.1.3 by Adeodato Simó
Cope with to_file.encoding being None or not present.
94
            to_file.write(anno.encode(encoding, 'replace'))
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
95
        to_file.write('| %s\n' % (text,))
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
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))
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
103
2182.3.10 by John Arbash Meinel
minor cleanup.
104
2245.3.1 by John Arbash Meinel
bzr annotate should use Branch's dotted revnos.
105
def _annotate_file(branch, rev_id, file_id):
2182.3.10 by John Arbash Meinel
minor cleanup.
106
    """Yield the origins for each line of a file.
107
2671.5.3 by Lukáš Lalinsky
Use the author name in annotate.
108
    This includes detailed information, such as the author name, and
2182.3.10 by John Arbash Meinel
minor cleanup.
109
    date string for the commit, rather than just the revision id.
110
    """
2418.5.8 by John Arbash Meinel
Update annotate.py to use the new helper function.
111
    revision_id_to_revno = branch.get_revision_id_to_revno_map()
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
112
    annotations = _annotations(branch.repository, file_id, rev_id)
1385 by Martin Pool
- simple weave-based annotate code (not complete)
113
    last_origin = None
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
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:
1385 by Martin Pool
- simple weave-based annotate code (not complete)
120
        text = text.rstrip('\r\n')
121
        if origin == last_origin:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
122
            (revno_str, author, date_str) = ('','','')
1385 by Martin Pool
- simple weave-based annotate code (not complete)
123
        else:
124
            last_origin = origin
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
125
            if origin not in revisions:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
126
                (revno_str, author, date_str) = ('?','?','?')
1185.16.1 by Martin Pool
- update annotate for new branch api
127
            else:
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
128
                revno_str = '.'.join(str(i) for i in
129
                                            revision_id_to_revno[origin])
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
130
            rev = revisions[origin]
1185.16.32 by Martin Pool
- add a basic annotate built-in command
131
            tz = rev.timezone or 0
2182.3.8 by John Arbash Meinel
Some cleanup to make annotate.py < 79 chars wide.
132
            date_str = time.strftime('%Y%m%d',
1185.16.32 by Martin Pool
- add a basic annotate built-in command
133
                                     time.gmtime(rev.timestamp + tz))
134
            # a lazy way to get something like the email address
135
            # TODO: Get real email address
2671.5.7 by Lukáš Lalinsky
Rename get_author to get_apparent_author, revert the long log back to displaying the committer.
136
            author = rev.get_apparent_author()
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
137
            try:
138
                author = extract_email_address(author)
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
139
            except errors.NoEmailInUsername:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
140
                pass        # use the whole name
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
141
        yield (revno_str, author, date_str, origin, text)
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
142
143
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
144
def reannotate(parents_lines, new_lines, new_revision_id,
145
               _left_matching_blocks=None):
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
146
    """Create a new annotated version from new lines and parent annotations.
147
    
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
148
    :param parents_lines: List of annotated lines for all parents
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
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)
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
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.
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
155
    """
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
156
    if len(parents_lines) == 0:
157
        for line in new_lines:
158
            yield new_revision_id, line
159
    elif len(parents_lines) == 1:
2770.1.4 by Aaron Bentley
Further optimize annotation, using existing matching blocks
160
        for data in _reannotate(parents_lines[0], new_lines, new_revision_id,
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
161
                                _left_matching_blocks):
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
162
            yield data
163
    else:
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
164
        block_list = [_left_matching_blocks] + [None] * len(parents_lines)
2770.1.4 by Aaron Bentley
Further optimize annotation, using existing matching blocks
165
        reannotations = [list(_reannotate(p, new_lines, new_revision_id, b))
166
                         for p, b in zip(parents_lines, block_list)]
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
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
1551.9.18 by Aaron Bentley
Updates from review comments
176
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
177
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
178
def _reannotate(parent_lines, new_lines, new_revision_id,
179
                matching_blocks=None):
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
180
    new_cur = 0
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
181
    if matching_blocks is None:
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
182
        plain_parent_lines = [l for r, l in parent_lines]
183
        matcher = patiencediff.PatienceSequenceMatcher(None,
184
            plain_parent_lines, new_lines)
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
185
        matching_blocks = matcher.get_matching_blocks()
186
    for i, j, n in matching_blocks:
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
187
        for line in new_lines[new_cur:j]:
188
            yield new_revision_id, line
189
        for data in parent_lines[i:i+n]:
1551.9.18 by Aaron Bentley
Updates from review comments
190
            yield data
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
191
        new_cur = j + n