~abentley/bzrtools/bzrtools.dev

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