3
Read in a changeset output, and process it into a Changeset object.
6
import bzrlib, bzrlib.changeset
9
class BadChangeset(Exception):
11
class MalformedHeader(BadChangeset):
13
class MalformedFooter(BadChangeset):
17
class ChangesetInfo(object):
18
"""This is the intermediate class that gets filled out as the file is read.
25
self.revision_sha1 = None
27
self.precursor_sha1 = None
28
self.precursor_revno = None
30
self.tree_root_id = None
32
self.directory_ids = None
33
self.parent_ids = None
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
42
return pprint.pformat(self.__dict__)
44
def create_maps(self):
45
"""Go through the individual id sections, and generate the id2path and path2id maps.
47
self.id2path[self.tree_root_id] = ''
48
self.path2id[''] = self.tree_root_id
49
self.id2parent[self.tree_root_id] = None # There is no parent for the tree_root_id
50
for var in (self.file_ids, self.directory_ids, self.parent_ids):
53
path, f_id, parent_id = info.split('\t')
54
self.id2path[f_id] = path
55
self.path2id[path] = f_id
56
self.id2parent[f_id] = parent_id
59
class ChangesetReader(object):
60
"""This class reads in a changeset from a file, and returns a Changeset object,
61
which can then be applied against a tree.
63
def __init__(self, from_file):
64
"""Read in the changeset from the file.
66
:param from_file: A file-like object (must have iterator support).
69
self.from_file = from_file
71
self.info = ChangesetInfo()
72
# We put the actual inventory ids in the footer, so that the patch
73
# is easier to read for humans.
74
# Unfortunately, that means we need to read everything before we
75
# can create a proper changeset.
77
next_line = self._read_patches()
78
self._read_footer(next_line)
80
def get_changeset(self):
81
"""Create the actual changeset object.
83
self.info.create_maps()
86
def _read_header(self):
87
"""Read the bzr header"""
88
header = common.get_header()
89
for head_line, line in zip(header, self.from_file):
90
if line[:2] != '# ' or line[-1] != '\n' or line[2:-1] != head_line:
91
raise MalformedHeader('Did not read the opening header information.')
93
for line in self.from_file:
94
if self._handle_info_line(line) is not None:
97
def _handle_info_line(self, line, in_footer=False):
98
"""Handle reading a single line.
100
This may call itself, in the case that we read_multi, and then had a dangling
103
# The bzr header is terminated with a blank line which does not start with #
108
raise MalformedHeader('Opening bzr header did not start with #')
110
line = line[2:-1] # Remove the '# '
112
return # Ignore blank lines
114
if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
117
loc = line.find(': ')
124
value, next_line = self._read_many()
126
raise MalformedHeader('While looking for key: value pairs, did not find the : %r' % (line))
128
key = key.replace(' ', '_')
129
if hasattr(self.info, key):
130
if getattr(self.info, key) is None:
131
setattr(self.info, key, value)
133
raise MalformedHeader('Duplicated Key: %s' % key)
135
# What do we do with a key we don't recognize
136
raise MalformedHeader('Unknown Key: %s' % key)
139
self._handle_info_line(next_line, in_footer=in_footer)
141
def _read_many(self):
142
"""If a line ends with no entry, that means that it should be followed with
143
multiple lines of values.
145
This detects the end of the list, because it will be a line that does not
146
start with '# '. Because it has to read that extra line, it returns the
147
tuple: (values, next_line)
150
for line in self.from_file:
153
values.append(line[5:-1])
156
def _read_patches(self):
157
for line in self.from_file:
161
def _read_footer(self, first_line=None):
162
"""Read the rest of the meta information.
164
:param first_line: The previous step may iterate passed what it can handle.
165
That extra line can be passed here.
167
if first_line is not None:
168
self._handle_info_line(first_line, in_footer=True)
169
for line in self.from_file:
170
if self._handle_info_line(line, in_footer=True) is not None:
174
def read_changeset(from_file):
175
"""Read in a changeset from a filelike object (must have "readline" support), and
176
parse it into a Changeset object.
178
cr = ChangesetReader(from_file)
179
print cr.get_changeset()