/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
1
#!/usr/bin/env python
2
"""\
3
Read in a changeset output, and process it into a Changeset object.
4
"""
5
6
import bzrlib, bzrlib.changeset
7
import common
8
9
class BadChangeset(Exception):
10
    pass
11
class MalformedHeader(BadChangeset):
12
    pass
13
class MalformedFooter(BadChangeset):
14
    pass
15
16
17
class ChangesetInfo(object):
18
    """This is the intermediate class that gets filled out as the file is read.
19
    """
20
    def __init__(self):
21
        self.committer = None
22
        self.date = None
23
        self.revno = None
24
        self.revision = None
25
        self.revision_sha1 = None
26
        self.precursor = None
27
        self.precursor_sha1 = None
28
        self.precursor_revno = None
29
30
        self.tree_root_id = None
31
        self.file_ids = None
32
        self.directory_ids = None
33
        self.parent_ids = None
34
35
        self.actions = [] #this is the list of things that happened
36
        self.id2path = {} # A mapping from file id to path name
37
        self.path2id = {} # The reverse mapping
38
        self.id2parent = {} # A mapping from a given id to it's parent id
39
40
    def __str__(self):
41
        import pprint
42
        return pprint.pformat(self.__dict__)
43
44
    def create_maps(self):
45
        """Go through the individual id sections, and generate the id2path and path2id maps.
46
        """
0.5.8 by John Arbash Meinel
Added some extra work into changeset, created some dummy files for testing.
47
        # Rather than use an empty path, the changeset code seems 
48
        # to like to use "./." for the tree root.
49
        self.id2path[self.tree_root_id] = './.'
50
        self.path2id['./.'] = self.tree_root_id
51
        self.id2parent[self.tree_root_id] = bzrlib.changeset.NULL_ID
52
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
53
        for var in (self.file_ids, self.directory_ids, self.parent_ids):
54
            if var is not None:
55
                for info in var:
56
                    path, f_id, parent_id = info.split('\t')
57
                    self.id2path[f_id] = path
58
                    self.path2id[path] = f_id
59
                    self.id2parent[f_id] = parent_id
60
61
62
class ChangesetReader(object):
63
    """This class reads in a changeset from a file, and returns a Changeset object,
64
    which can then be applied against a tree.
65
    """
66
    def __init__(self, from_file):
67
        """Read in the changeset from the file.
68
69
        :param from_file: A file-like object (must have iterator support).
70
        """
71
        object.__init__(self)
72
        self.from_file = from_file
73
        
74
        self.info = ChangesetInfo()
75
        # We put the actual inventory ids in the footer, so that the patch
76
        # is easier to read for humans.
77
        # Unfortunately, that means we need to read everything before we
78
        # can create a proper changeset.
79
        self._read_header()
80
        next_line = self._read_patches()
81
        self._read_footer(next_line)
82
83
    def get_changeset(self):
84
        """Create the actual changeset object.
85
        """
86
        self.info.create_maps()
87
        return self.info
88
89
    def _read_header(self):
90
        """Read the bzr header"""
91
        header = common.get_header()
92
        for head_line, line in zip(header, self.from_file):
93
            if line[:2] != '# ' or line[-1] != '\n' or line[2:-1] != head_line:
94
                raise MalformedHeader('Did not read the opening header information.')
95
96
        for line in self.from_file:
97
            if self._handle_info_line(line) is not None:
98
                break
99
100
    def _handle_info_line(self, line, in_footer=False):
101
        """Handle reading a single line.
102
103
        This may call itself, in the case that we read_multi, and then had a dangling
104
        line on the end.
105
        """
106
        # The bzr header is terminated with a blank line which does not start with #
107
        next_line = None
108
        if line[:1] == '\n':
109
            return 'break'
110
        if line[:2] != '# ':
111
            raise MalformedHeader('Opening bzr header did not start with #')
112
113
        line = line[2:-1] # Remove the '# '
114
        if not line:
115
            return # Ignore blank lines
116
117
        if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
118
            return
119
120
        loc = line.find(': ')
121
        if loc != -1:
122
            key = line[:loc]
123
            value = line[loc+2:]
124
        else:
125
            if line[-1:] == ':':
126
                key = line[:-1]
127
                value, next_line = self._read_many()
128
            else:
129
                raise MalformedHeader('While looking for key: value pairs, did not find the : %r' % (line))
130
131
        key = key.replace(' ', '_')
132
        if hasattr(self.info, key):
133
            if getattr(self.info, key) is None:
134
                setattr(self.info, key, value)
135
            else:
136
                raise MalformedHeader('Duplicated Key: %s' % key)
137
        else:
138
            # What do we do with a key we don't recognize
139
            raise MalformedHeader('Unknown Key: %s' % key)
140
        
141
        if next_line:
142
            self._handle_info_line(next_line, in_footer=in_footer)
143
144
    def _read_many(self):
145
        """If a line ends with no entry, that means that it should be followed with
146
        multiple lines of values.
147
148
        This detects the end of the list, because it will be a line that does not
149
        start with '#    '. Because it has to read that extra line, it returns the
150
        tuple: (values, next_line)
151
        """
152
        values = []
153
        for line in self.from_file:
154
            if line[:5] != '#    ':
155
                return values, line
156
            values.append(line[5:-1])
157
        return values, None
158
159
    def _read_patches(self):
160
        for line in self.from_file:
0.5.8 by John Arbash Meinel
Added some extra work into changeset, created some dummy files for testing.
161
            if line[:3] == '***': # This is a bzr meta field
162
                self._parse_meta(line)
163
            elif line[:3] == '---': # This is the 'pre' line for a pre+post patch
164
                pass
165
            elif line[:3] == '+++': # This is the 'post' line for the pre+post patch
166
                pass
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
167
            if line[0] == '#':
168
                return line
169
170
    def _read_footer(self, first_line=None):
171
        """Read the rest of the meta information.
172
173
        :param first_line:  The previous step may iterate passed what it can handle.
174
                            That extra line can be passed here.
175
        """
176
        if first_line is not None:
177
            self._handle_info_line(first_line, in_footer=True)
178
        for line in self.from_file:
179
            if self._handle_info_line(line, in_footer=True) is not None:
180
                break
181
182
183
def read_changeset(from_file):
184
    """Read in a changeset from a filelike object (must have "readline" support), and
185
    parse it into a Changeset object.
186
    """
187
    cr = ChangesetReader(from_file)
188
    print cr.get_changeset()
189