1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# Copyright (C) 2004, 2005 Aaron Bentley
# <aaron.bentley@utoronto.ca>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from bzrlib.branch import Branch
from bzrlib.commands import Command
from bzrlib.errors import BzrCommandError
import os
import progress
from progress import show_progress
import patches
import difflib
import sys
def iter_anno_data(branch, file_id):
max_revno = branch.revno()
later_revision = max_revno
q = range(max_revno)
q.append(max_revno)
q.reverse()
next_tree = branch.working_tree()
later_text_sha1 = next_tree.get_file_sha1(file_id)
i = 0
for revno in q:
i += 1
cur_tree = branch.revision_tree(branch.get_rev_id(revno))
if file_id not in cur_tree.inventory:
text_sha1 = None
else:
text_sha1 = cur_tree.get_file_sha1(file_id)
if text_sha1 != later_text_sha1:
patch = get_patch(branch, cur_tree, next_tree, file_id)
next_tree = cur_tree
if revno == max_revno:
display_id = "Tree"
else:
display_id = str(revno+1)
yield display_id, patch.iter_inserted(), patch
later_revision = revno
later_text_sha1 = text_sha1
yield progress.Progress("revisions", i)
def get_patch(branch, old_tree, new_tree, file_id):
if file_id in old_tree.inventory:
old_file = old_tree.get_file(file_id).readlines()
else:
old_file = []
ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
return patches.parse_patch(ud)
class cmd_annotate(Command):
"""Show which revision added each line in a file"""
takes_args = ['filename']
def run(self, filename):
if not os.path.exists(filename):
raise BzrCommandError("The file %s does not exist." % filename)
branch = (Branch.open_containing(filename))
file_id = branch.working_tree().path2id(branch.relpath(filename))
if file_id is None:
raise BzrCommandError("The file %s is not versioned." % filename)
lines = branch.basis_tree().get_file(file_id)
total = branch.revno()
anno_d_iter = iter_anno_data(branch, file_id)
progress_bar = progress.ProgressBar()
try:
for result in iter_annotate_file(lines, anno_d_iter):
if isinstance(result, progress.Progress):
result.total = total
show_progress(progress_bar, result)
else:
anno_lines = result
finally:
progress_bar.clear()
for line in anno_lines:
sys.stdout.write("%4s:%s" % (str(line.log), line.text))
class AnnotateLine:
"""A line associated with the log that produced it"""
def __init__(self, text, log=None):
self.text = text
self.log = log
class CantGetRevisionData(Exception):
def __init__(self, revision):
Exception.__init__(self, "Can't get data for revision %s" % revision)
def annotate_file2(file_lines, anno_iter):
for result in iter_annotate_file(file_lines, anno_iter):
pass
return result
def iter_annotate_file(file_lines, anno_iter):
lines = [AnnotateLine(f) for f in file_lines]
patches = []
try:
for result in anno_iter:
if isinstance(result, progress.Progress):
yield result
continue
log, iter_inserted, patch = result
for (num, line) in iter_inserted:
old_num = num
for cur_patch in patches:
num = cur_patch.pos_in_mod(num)
if num == None:
break
if num >= len(lines):
continue
if num is not None and lines[num].log is None:
lines[num].log = log
patches=[patch]+patches
except CantGetRevisionData:
pass
yield lines
|