~abentley/bzrtools/bzrtools.dev

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