~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):
3010.1.1 by Robert Collins
Lock the tree's used to test annotate_file, and add a docstring for annotate_file explaining its needs.
42
    """Annotate file_id at revision rev_id in branch.
43
44
    The branch should already be read_locked() when annotate_file is called.
45
46
    :param branch: The branch to look for revision numbers and history from.
47
    :param rev_id: The revision id to annotate.
48
    :param file_id: The file_id to annotate.
49
    :param verbose: Show all details rather than truncating to ensure
50
        reasonable text width.
51
    :param full: XXXX Not sure what this does.
52
    :param to_file: The file to output the annotation to; if None stdout is
53
        used.
54
    :param show_ids: Show revision ids in the annotation output.
55
    """
1385 by Martin Pool
- simple weave-based annotate code (not complete)
56
    if to_file is None:
57
        to_file = sys.stdout
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
58
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
59
    # 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
60
    last_rev_id = None
61
    if show_ids:
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
62
        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
63
        max_origin_len = max(len(origin) for origin, text in annotations)
64
        for origin, text in annotations:
65
            if full or last_rev_id != origin:
66
                this = origin
67
            else:
68
                this = ''
69
            to_file.write('%*s | %s' % (max_origin_len, this, text))
70
            last_rev_id = origin
71
        return
72
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
73
    # Calculate the lengths of the various columns
1185.33.39 by Martin Pool
[patch] annotate --long (robey pointer)
74
    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
75
    if len(annotation) == 0:
2182.3.4 by John Arbash Meinel
add show-ids and test that nearby areas are collapsed without full
76
        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
77
    else:
2182.3.8 by John Arbash Meinel
Some cleanup to make annotate.py < 79 chars wide.
78
        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.
79
        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
80
        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
81
    if not verbose:
2182.3.7 by John Arbash Meinel
Cleanup and add blackbox tests for annotate.
82
        max_revno_len = min(max_revno_len, 12)
83
    max_revno_len = max(max_revno_len, 3)
2182.3.2 by John Arbash Meinel
Use shortened revnos unless --long is supplied
84
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
85
    # Output the annotations
86
    prevanno = ''
87
    encoding = getattr(to_file, 'encoding', None) or \
88
            osutils.get_terminal_encoding()
2182.3.4 by John Arbash Meinel
add show-ids and test that nearby areas are collapsed without full
89
    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
90
        if verbose:
91
            anno = '%-*s %-*s %8s ' % (max_revno_len, revno_str,
92
                                       max_origin_len, author, date_str)
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
93
        else:
2182.3.9 by John Arbash Meinel
Faster annotate --show-ids. No need to pull the history or the revision info
94
            if len(revno_str) > max_revno_len:
95
                revno_str = revno_str[:max_revno_len-1] + '>'
96
            anno = "%-*s %-7s " % (max_revno_len, revno_str, author[:7])
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
97
        if anno.lstrip() == "" and full:
98
            anno = prevanno
2593.1.1 by Adeodato Simó
Improve annotate to prevent unicode exceptions in certain situations.
99
        try:
100
            to_file.write(anno)
101
        except UnicodeEncodeError:
2593.1.4 by Adeodato Simó
Add comment from John to the try/except block.
102
            # cmd_annotate should be passing in an 'exact' object, which means
103
            # we have a direct handle to sys.stdout or equivalent. It may not
104
            # be able to handle the exact Unicode characters, but 'annotate' is
105
            # a user function (non-scripting), so shouldn't die because of
106
            # unrepresentable annotation characters. So encode using 'replace',
107
            # and write them again.
2593.1.3 by Adeodato Simó
Cope with to_file.encoding being None or not present.
108
            to_file.write(anno.encode(encoding, 'replace'))
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
109
        to_file.write('| %s\n' % (text,))
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
110
        prevanno = anno
111
112
113
def _annotations(repo, file_id, rev_id):
114
    """Return the list of (origin,text) for a revision of a file in a repository."""
115
    w = repo.weave_store.get_weave(file_id, repo.get_transaction())
116
    return list(w.annotate_iter(rev_id))
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
117
2182.3.10 by John Arbash Meinel
minor cleanup.
118
2245.3.1 by John Arbash Meinel
bzr annotate should use Branch's dotted revnos.
119
def _annotate_file(branch, rev_id, file_id):
2182.3.10 by John Arbash Meinel
minor cleanup.
120
    """Yield the origins for each line of a file.
121
2671.5.3 by Lukáš Lalinsky
Use the author name in annotate.
122
    This includes detailed information, such as the author name, and
2182.3.10 by John Arbash Meinel
minor cleanup.
123
    date string for the commit, rather than just the revision id.
124
    """
2418.5.8 by John Arbash Meinel
Update annotate.py to use the new helper function.
125
    revision_id_to_revno = branch.get_revision_id_to_revno_map()
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
126
    annotations = _annotations(branch.repository, file_id, rev_id)
1385 by Martin Pool
- simple weave-based annotate code (not complete)
127
    last_origin = None
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
128
    revision_ids = set(o for o, t in annotations)
129
    revision_ids = [o for o in revision_ids if 
130
                    branch.repository.has_revision(o)]
131
    revisions = dict((r.revision_id, r) for r in 
132
                     branch.repository.get_revisions(revision_ids))
133
    for origin, text in annotations:
1385 by Martin Pool
- simple weave-based annotate code (not complete)
134
        text = text.rstrip('\r\n')
135
        if origin == last_origin:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
136
            (revno_str, author, date_str) = ('','','')
1385 by Martin Pool
- simple weave-based annotate code (not complete)
137
        else:
138
            last_origin = origin
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
139
            if origin not in revisions:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
140
                (revno_str, author, date_str) = ('?','?','?')
1185.16.1 by Martin Pool
- update annotate for new branch api
141
            else:
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
142
                revno_str = '.'.join(str(i) for i in
143
                                            revision_id_to_revno[origin])
1551.9.6 by Aaron Bentley
Optimize annotate by retrieving all revisions at once
144
            rev = revisions[origin]
1185.16.32 by Martin Pool
- add a basic annotate built-in command
145
            tz = rev.timezone or 0
2182.3.8 by John Arbash Meinel
Some cleanup to make annotate.py < 79 chars wide.
146
            date_str = time.strftime('%Y%m%d',
1185.16.32 by Martin Pool
- add a basic annotate built-in command
147
                                     time.gmtime(rev.timestamp + tz))
148
            # a lazy way to get something like the email address
149
            # 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.
150
            author = rev.get_apparent_author()
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
151
            try:
152
                author = extract_email_address(author)
2182.3.1 by John Arbash Meinel
Annotate now shows dotted revnos instead of plain revnos.
153
            except errors.NoEmailInUsername:
1185.16.53 by Martin Pool
- annotate improvements from Goffreddo, with extra bug fixes and tests
154
                pass        # use the whole name
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
155
        yield (revno_str, author, date_str, origin, text)
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
156
157
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
158
def reannotate(parents_lines, new_lines, new_revision_id,
159
               _left_matching_blocks=None):
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
160
    """Create a new annotated version from new lines and parent annotations.
161
    
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
162
    :param parents_lines: List of annotated lines for all parents
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
163
    :param new_lines: The un-annotated new lines
164
    :param new_revision_id: The revision-id to associate with new lines
165
        (will often be CURRENT_REVISION)
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
166
    :param left_matching_blocks: a hint about which areas are common
167
        between the text and its left-hand-parent.  The format is
168
        the SequenceMatcher.get_matching_blocks format.
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
169
    """
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
170
    if len(parents_lines) == 0:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
171
        lines = [(new_revision_id, line) for line in new_lines]
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
172
    elif len(parents_lines) == 1:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
173
        lines = _reannotate(parents_lines[0], new_lines, new_revision_id,
174
                            _left_matching_blocks)
175
    elif len(parents_lines) == 2:
176
        left = _reannotate(parents_lines[0], new_lines, new_revision_id,
177
                           _left_matching_blocks)
178
        right = _reannotate(parents_lines[1], new_lines, new_revision_id)
179
        lines = []
180
        for idx in xrange(len(new_lines)):
181
            if left[idx][0] == right[idx][0]:
182
                # The annotations match, just return the left one
183
                lines.append(left[idx])
184
            elif left[idx][0] == new_revision_id:
185
                # The left parent claims a new value, return the right one
186
                lines.append(right[idx])
187
            elif right[idx][0] == new_revision_id:
188
                # The right parent claims a new value, return the left one
189
                lines.append(left[idx])
190
            else:
191
                # Both claim different origins
192
                lines.append((new_revision_id, left[idx][1]))
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
193
    else:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
194
        reannotations = [_reannotate(parents_lines[0], new_lines,
195
                                     new_revision_id, _left_matching_blocks)]
196
        reannotations.extend(_reannotate(p, new_lines, new_revision_id)
197
                             for p in parents_lines[1:])
198
        lines = []
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
199
        for annos in zip(*reannotations):
200
            origins = set(a for a, l in annos)
201
            if len(origins) == 1:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
202
                # All the parents agree, so just return the first one
203
                lines.append(annos[0])
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
204
            else:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
205
                line = annos[0][1]
206
                if len(origins) == 2 and new_revision_id in origins:
207
                    origins.remove(new_revision_id)
208
                if len(origins) == 1:
3180.2.2 by John Arbash Meinel
Fix typo, (thanks Ian)
209
                    lines.append((origins.pop(), line))
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
210
                else:
211
                    lines.append((new_revision_id, line))
212
    return lines
1551.9.18 by Aaron Bentley
Updates from review comments
213
1551.9.17 by Aaron Bentley
Annotate for working trees across all parents
214
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
215
def _reannotate(parent_lines, new_lines, new_revision_id,
216
                matching_blocks=None):
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
217
    new_cur = 0
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
218
    if matching_blocks is None:
2831.3.1 by Ian Clatworthy
code cleanups for annotate.py
219
        plain_parent_lines = [l for r, l in parent_lines]
220
        matcher = patiencediff.PatienceSequenceMatcher(None,
221
            plain_parent_lines, new_lines)
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
222
        matching_blocks = matcher.get_matching_blocks()
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
223
    lines = []
2770.1.5 by Aaron Bentley
Clean up docs, test matching blocks for reannotate
224
    for i, j, n in matching_blocks:
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
225
        for line in new_lines[new_cur:j]:
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
226
            lines.append((new_revision_id, line))
227
        lines.extend(parent_lines[i:i+n])
1551.9.16 by Aaron Bentley
Implement Tree.annotate_iter for RevisionTree and WorkingTree
228
        new_cur = j + n
3180.2.1 by John Arbash Meinel
Change reannotate to special case the 2-parent case.
229
    return lines