/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
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
9
class BadChangeset(Exception): pass
10
class MalformedHeader(BadChangeset): pass
11
class MalformedPatches(BadChangeset): pass
12
class MalformedFooter(BadChangeset): 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.
13
14
15
class ChangesetInfo(object):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
16
    """This is the intermediate class that gets filled out as
17
    the file is read.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
18
    """
19
    def __init__(self):
20
        self.committer = None
21
        self.date = None
22
        self.revno = None
23
        self.revision = None
24
        self.revision_sha1 = None
25
        self.precursor = None
26
        self.precursor_sha1 = None
27
        self.precursor_revno = None
28
29
        self.tree_root_id = None
30
        self.file_ids = None
31
        self.directory_ids = None
32
        self.parent_ids = None
33
34
        self.actions = [] #this is the list of things that happened
35
        self.id2path = {} # A mapping from file id to path name
36
        self.path2id = {} # The reverse mapping
37
        self.id2parent = {} # A mapping from a given id to it's parent id
38
39
    def __str__(self):
40
        import pprint
41
        return pprint.pformat(self.__dict__)
42
43
    def create_maps(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
44
        """Go through the individual id sections, and generate the 
45
        id2path and path2id maps.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
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
class ChangesetReader(object):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
62
    """This class reads in a changeset from a file, and returns
63
    a Changeset object, which can then be applied against a tree.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
64
    """
65
    def __init__(self, from_file):
66
        """Read in the changeset from the file.
67
68
        :param from_file: A file-like object (must have iterator support).
69
        """
70
        object.__init__(self)
71
        self.from_file = from_file
72
        
73
        self.info = ChangesetInfo()
74
        # We put the actual inventory ids in the footer, so that the patch
75
        # is easier to read for humans.
76
        # Unfortunately, that means we need to read everything before we
77
        # can create a proper changeset.
78
        self._read_header()
79
        next_line = self._read_patches()
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
80
        if next_line is not None:
81
            self._read_footer(next_line)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
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):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
93
            if (line[:2] != '# '
94
                    or line[-1] != '\n'
95
                    or line[2:-1] != head_line):
96
                raise MalformedHeader('Did not read the opening'
97
                    ' header information.')
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
98
99
        for line in self.from_file:
100
            if self._handle_info_line(line) is not None:
101
                break
102
103
    def _handle_info_line(self, line, in_footer=False):
104
        """Handle reading a single line.
105
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
106
        This may call itself, in the case that we read_multi,
107
        and then had a dangling line on the end.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
108
        """
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
109
        # The bzr header is terminated with a blank line
110
        # which does not start with #
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
111
        next_line = None
112
        if line[:1] == '\n':
113
            return 'break'
114
        if line[:2] != '# ':
115
            raise MalformedHeader('Opening bzr header did not start with #')
116
117
        line = line[2:-1] # Remove the '# '
118
        if not line:
119
            return # Ignore blank lines
120
121
        if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
122
            return
123
124
        loc = line.find(': ')
125
        if loc != -1:
126
            key = line[:loc]
127
            value = line[loc+2:]
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
128
            if not value:
129
                value, next_line = self._read_many()
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
130
        else:
131
            if line[-1:] == ':':
132
                key = line[:-1]
133
                value, next_line = self._read_many()
134
            else:
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
135
                raise MalformedHeader('While looking for key: value pairs,'
136
                        ' did not find the colon %r' % (line))
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
137
138
        key = key.replace(' ', '_')
139
        if hasattr(self.info, key):
140
            if getattr(self.info, key) is None:
141
                setattr(self.info, key, value)
142
            else:
143
                raise MalformedHeader('Duplicated Key: %s' % key)
144
        else:
145
            # What do we do with a key we don't recognize
146
            raise MalformedHeader('Unknown Key: %s' % key)
147
        
148
        if next_line:
149
            self._handle_info_line(next_line, in_footer=in_footer)
150
151
    def _read_many(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
152
        """If a line ends with no entry, that means that it should be
153
        followed with multiple lines of values.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
154
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
155
        This detects the end of the list, because it will be a line that
156
        does not start with '#    '. Because it has to read that extra
157
        line, it returns the tuple: (values, next_line)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
158
        """
159
        values = []
160
        for line in self.from_file:
161
            if line[:5] != '#    ':
162
                return values, line
163
            values.append(line[5:-1])
164
        return values, None
165
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
166
    def _read_one_patch(self, first_line=None):
167
        """Read in one patch, return the complete patch, along with
168
        the next line.
169
170
        :return: action, lines, next_line, do_continue
171
        """
172
        first = True
173
        action = None
174
175
        def parse_firstline(line):
176
            if line[:1] == '#':
177
                return None
178
            if line[:3] != '***':
179
                raise MalformedPatches('The first line of all patches'
180
                    ' should be a bzr meta line "***"')
181
            return line[4:-1]
182
183
        if first_line is not None:
184
            action = parse_firstline(first_line)
185
            first = False
186
            if action is None:
187
                return None, [], first_line, False
188
189
        lines = []
190
        for line in self.from_file:
191
            if first:
192
                action = parse_firstline(line)
193
                first = False
194
                if action is None:
195
                    return None, [], line, False
196
            else:
197
                if line[:3] == '***':
198
                    return action, lines, line, True
199
                elif line[:1] == '#':
200
                    return action, lines, line, False
201
                lines.append(line)
202
        return action, lines, None, False
203
            
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
204
    def _read_patches(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
205
        next_line = None
206
        do_continue = True
207
        while do_continue:
208
            action, lines, next_line, do_continue = \
209
                    self._read_one_patch(next_line)
210
            if action is not None:
211
                self.info.actions.append((action, lines))
212
        return next_line
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
213
214
    def _read_footer(self, first_line=None):
215
        """Read the rest of the meta information.
216
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
217
        :param first_line:  The previous step iterates past what it
218
                            can handle. That extra line is given here.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
219
        """
220
        if first_line is not None:
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
221
            if self._handle_info_line(first_line, in_footer=True) is not None:
222
                return
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
223
        for line in self.from_file:
224
            if self._handle_info_line(line, in_footer=True) is not None:
225
                break
226
227
228
def read_changeset(from_file):
229
    """Read in a changeset from a filelike object (must have "readline" support), and
230
    parse it into a Changeset object.
231
    """
232
    cr = ChangesetReader(from_file)
233
    print cr.get_changeset()
234