~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to read_changeset.py

  • Committer: John Arbash Meinel
  • Date: 2005-06-29 06:59:45 UTC
  • mto: (0.5.85) (1185.82.1 bzr-w-changeset)
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: john@arbash-meinel.com-20050629065945-14a14a6514d5fa46
Updated so that read_changeset is able to parse the output

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
"""
5
5
 
6
6
import bzrlib, bzrlib.changeset
 
7
import pprint
7
8
import common
8
9
 
9
10
class BadChangeset(Exception): pass
29
30
    # We need to handle escaped hexadecimals too.
30
31
    return name[1:-1].replace('\"', '"').replace("\'", "'")
31
32
 
 
33
class RevisionInfo(object):
 
34
    """Gets filled out for each revision object that is read.
 
35
    """
 
36
    def __init__(self, rev_id):
 
37
        self.rev_id = rev_id
 
38
        self.sha1 = None
 
39
        self.committer = None
 
40
        self.timestamp = None
 
41
        self.timezone = None
 
42
        self.inventory_id = None
 
43
        self.inventory_sha1 = None
 
44
 
 
45
        self.parents = None
 
46
        self.message = None
 
47
 
 
48
    def __str__(self):
 
49
        return pprint.pformat(self.__dict__)
 
50
 
 
51
 
32
52
class ChangesetInfo(object):
33
53
    """This is the intermediate class that gets filled out as
34
54
    the file is read.
37
57
        self.committer = None
38
58
        self.date = None
39
59
        self.message = None
40
 
        self.revno = None
41
 
        self.revision = None
42
 
        self.revision_sha1 = None
43
 
        self.precursor = None
44
 
        self.precursor_sha1 = None
45
 
        self.precursor_revno = None
 
60
        self.base = None
 
61
        self.base_sha1 = None
 
62
 
 
63
        self.revisions = []
46
64
 
47
65
        self.timestamp = None
48
66
        self.timezone = None
61
79
        self.old_id2parent = {}
62
80
 
63
81
    def __str__(self):
64
 
        import pprint
65
82
        return pprint.pformat(self.__dict__)
66
83
 
67
84
    def create_maps(self):
176
193
        """
177
194
        object.__init__(self)
178
195
        self.from_file = from_file
 
196
        self._next_line = None
179
197
        
180
198
        self.info = ChangesetInfo()
181
199
        # We put the actual inventory ids in the footer, so that the patch
183
201
        # Unfortunately, that means we need to read everything before we
184
202
        # can create a proper changeset.
185
203
        self._read_header()
186
 
        next_line = self._read_patches()
187
 
        if next_line is not None:
188
 
            self._read_footer(next_line)
 
204
        self._read_patches()
 
205
        self._read_footer()
 
206
 
 
207
    def _next(self):
 
208
        """yield the next line, but secretly
 
209
        keep 1 extra line for peeking.
 
210
        """
 
211
        for line in self.from_file:
 
212
            last = self._next_line
 
213
            self._next_line = line
 
214
            if last is not None:
 
215
                yield last
189
216
 
190
217
    def get_info(self):
191
218
        """Create the actual changeset object.
196
223
    def _read_header(self):
197
224
        """Read the bzr header"""
198
225
        header = common.get_header()
199
 
        for head_line, line in zip(header, self.from_file):
200
 
            if (line[:2] != '# '
201
 
                    or line[-1] != '\n'
202
 
                    or line[2:-1] != head_line):
203
 
                raise MalformedHeader('Did not read the opening'
204
 
                    ' header information.')
 
226
        found = False
 
227
        for line in self._next():
 
228
            if found:
 
229
                if (line[:2] != '# ' or line[-1:] != '\n'
 
230
                        or line[2:-1] != header[0]):
 
231
                    raise MalformedHeader('Found a header, but it'
 
232
                        ' was improperly formatted')
 
233
                header.pop(0) # We read this line.
 
234
                if not header:
 
235
                    break # We found everything.
 
236
            elif (line[:1] == '#' and line[-1:] == '\n'):
 
237
                line = line[1:-1].strip()
 
238
                if line[:len(common.header_str)] == common.header_str:
 
239
                    if line == header[0]:
 
240
                        found = True
 
241
                    else:
 
242
                        raise MalformedHeader('Found what looks like'
 
243
                                ' a header, but did not match')
 
244
                    header.pop(0)
 
245
        else:
 
246
            raise MalformedHeader('Did not find an opening header')
205
247
 
206
 
        for line in self.from_file:
207
 
            if self._handle_info_line(line) is not None:
 
248
        for line in self._next():
 
249
            # The bzr header is terminated with a blank line
 
250
            # which does not start with '#'
 
251
            if line == '\n':
208
252
                break
209
 
 
210
 
    def _handle_info_line(self, line, in_footer=False):
211
 
        """Handle reading a single line.
212
 
 
213
 
        This may call itself, in the case that we read_multi,
214
 
        and then had a dangling line on the end.
 
253
            self._handle_next(line)
 
254
 
 
255
    def _read_next_entry(self, line, indent=1):
 
256
        """Read in a key-value pair
215
257
        """
216
 
        # The bzr header is terminated with a blank line
217
 
        # which does not start with #
218
 
        next_line = None
219
 
        if line[:1] == '\n':
220
 
            return 'break'
221
 
        if line[:2] != '# ':
222
 
            raise MalformedHeader('Opening bzr header did not start with #')
223
 
 
224
 
        line = line[2:-1] # Remove the '# '
 
258
        if line[:1] != '#':
 
259
            raise MalformedHeader('Bzr header did not start with #')
 
260
        line = line[1:-1] # Remove the '#' and '\n'
 
261
        if line[:indent] == ' '*indent:
 
262
            line = line[indent:]
225
263
        if not line:
226
 
            return # Ignore blank lines
227
 
 
228
 
        if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
229
 
            return
 
264
            return None, None# Ignore blank lines
230
265
 
231
266
        loc = line.find(': ')
232
267
        if loc != -1:
233
268
            key = line[:loc]
234
269
            value = line[loc+2:]
235
270
            if not value:
236
 
                value, next_line = self._read_many()
 
271
                value = self._read_many(indent=indent+3)
 
272
        elif line[-1:] == ':':
 
273
            key = line[:-1]
 
274
            value = self._read_many(indent=indent+3)
237
275
        else:
238
 
            if line[-1:] == ':':
239
 
                key = line[:-1]
240
 
                value, next_line = self._read_many()
241
 
            else:
242
 
                raise MalformedHeader('While looking for key: value pairs,'
243
 
                        ' did not find the colon %r' % (line))
 
276
            raise MalformedHeader('While looking for key: value pairs,'
 
277
                    ' did not find the colon %r' % (line))
244
278
 
245
279
        key = key.replace(' ', '_')
246
 
        if hasattr(self.info, key):
 
280
        return key, value
 
281
 
 
282
    def _handle_next(self, line):
 
283
        key, value = self._read_next_entry(line, indent=1)
 
284
        if key is None:
 
285
            return
 
286
 
 
287
        if key == 'revision':
 
288
            self._read_revision(value)
 
289
        elif hasattr(self.info, key):
247
290
            if getattr(self.info, key) is None:
248
291
                setattr(self.info, key, value)
249
292
            else:
252
295
            # What do we do with a key we don't recognize
253
296
            raise MalformedHeader('Unknown Key: %s' % key)
254
297
        
255
 
        if next_line:
256
 
            self._handle_info_line(next_line, in_footer=in_footer)
257
 
 
258
 
    def _read_many(self):
 
298
    def _read_many(self, indent):
259
299
        """If a line ends with no entry, that means that it should be
260
300
        followed with multiple lines of values.
261
301
 
262
302
        This detects the end of the list, because it will be a line that
263
 
        does not start with '#    '. Because it has to read that extra
264
 
        line, it returns the tuple: (values, next_line)
 
303
        does not start properly indented.
265
304
        """
266
305
        values = []
267
 
        for line in self.from_file:
268
 
            if line[:5] != '#    ':
269
 
                return values, line
270
 
            values.append(line[5:-1])
271
 
        return values, None
272
 
 
273
 
    def _read_one_patch(self, first_line=None):
 
306
        start = '#' + (' '*indent)
 
307
 
 
308
        if self._next_line[:len(start)] != start:
 
309
            return values
 
310
 
 
311
        for line in self._next():
 
312
            values.append(line[len(start):-1])
 
313
            if self._next_line[:len(start)] != start:
 
314
                break
 
315
        return values
 
316
 
 
317
    def _read_one_patch(self):
274
318
        """Read in one patch, return the complete patch, along with
275
319
        the next line.
276
320
 
277
 
        :return: action, lines, next_line, do_continue
 
321
        :return: action, lines, do_continue
278
322
        """
279
 
        first = True
280
 
        action = None
281
 
 
282
 
        def parse_firstline(line):
283
 
            if line[:1] == '#':
284
 
                return None
285
 
            if line[:3] != '***':
286
 
                raise MalformedPatches('The first line of all patches'
287
 
                    ' should be a bzr meta line "***"')
288
 
            return line[4:-1]
289
 
 
290
 
        if first_line is not None:
291
 
            action = parse_firstline(first_line)
292
 
            first = False
293
 
            if action is None:
294
 
                return None, [], first_line, False
 
323
        # Peek and see if there are no patches
 
324
        if self._next_line[:1] == '#':
 
325
            return None, [], False
 
326
 
 
327
        line = self._next().next()
 
328
        if line[:3] != '***':
 
329
            raise MalformedPatches('The first line of all patches'
 
330
                ' should be a bzr meta line "***"')
 
331
        action = line[4:-1]
295
332
 
296
333
        lines = []
297
 
        for line in self.from_file:
298
 
            if first:
299
 
                action = parse_firstline(line)
300
 
                first = False
301
 
                if action is None:
302
 
                    return None, [], line, False
303
 
            else:
304
 
                if line[:3] == '***':
305
 
                    return action, lines, line, True
306
 
                elif line[:1] == '#':
307
 
                    return action, lines, line, False
308
 
                lines.append(line)
309
 
        return action, lines, None, False
 
334
        for line in self._next():
 
335
            lines.append(line)
 
336
 
 
337
            if self._next_line[:3] == '***':
 
338
                return action, lines, True
 
339
            elif self._next_line[:1] == '#':
 
340
                return action, lines, False
 
341
        return action, lines, False
310
342
            
311
343
    def _read_patches(self):
312
 
        next_line = None
313
344
        do_continue = True
314
345
        while do_continue:
315
 
            action, lines, next_line, do_continue = \
316
 
                    self._read_one_patch(next_line)
 
346
            action, lines, do_continue = self._read_one_patch()
317
347
            if action is not None:
318
348
                self.info.actions.append((action, lines))
319
 
        return next_line
320
 
 
321
 
    def _read_footer(self, first_line=None):
 
349
 
 
350
    def _read_revision(self, rev_id):
 
351
        """Revision entries have extra information associated.
 
352
        """
 
353
        rev_info = RevisionInfo(rev_id)
 
354
        start = '#    '
 
355
        for line in self._next():
 
356
            key,value = self._read_next_entry(line, indent=4)
 
357
            #if key is None:
 
358
            #    continue
 
359
            if hasattr(rev_info, key):
 
360
                if getattr(rev_info, key) is None:
 
361
                    setattr(rev_info, key, value)
 
362
                else:
 
363
                    raise MalformedHeader('Duplicated Key: %s' % key)
 
364
            else:
 
365
                # What do we do with a key we don't recognize
 
366
                raise MalformedHeader('Unknown Key: %s' % key)
 
367
 
 
368
            if self._next_line[:len(start)] != start:
 
369
                break
 
370
 
 
371
        self.info.revisions.append(rev_info)
 
372
 
 
373
    def _read_footer(self):
322
374
        """Read the rest of the meta information.
323
375
 
324
376
        :param first_line:  The previous step iterates past what it
325
377
                            can handle. That extra line is given here.
326
378
        """
327
 
        if first_line is not None:
328
 
            if self._handle_info_line(first_line, in_footer=True) is not None:
 
379
        line = self._next().next()
 
380
        if line != '# BEGIN BZR FOOTER\n':
 
381
            raise MalformedFooter('Footer did not begin with BEGIN BZR FOOTER')
 
382
 
 
383
        for line in self._next():
 
384
            if line == '# END BZR FOOTER\n':
329
385
                return
330
 
        for line in self.from_file:
331
 
            if self._handle_info_line(line, in_footer=True) is not None:
332
 
                break
333
 
 
 
386
            self._handle_next(line)
334
387
 
335
388
def read_changeset(from_file):
336
389
    """Read in a changeset from a filelike object (must have "readline" support), and
340
393
    info = cr.get_info()
341
394
    return info
342
395
 
 
396
if __name__ == '__main__':
 
397
    import sys
 
398
    print read_changeset(sys.stdin)