/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to read_changeset.py

  • Committer: John Arbash Meinel
  • Date: 2005-06-19 22:39:03 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-20050619223903-b85a522a84697931
Added a bunch more information about changesets. Can now read back in all of the meta information.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        """
 
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):
 
51
            if var is not None:
 
52
                for info in var:
 
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
 
57
 
 
58
 
 
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.
 
62
    """
 
63
    def __init__(self, from_file):
 
64
        """Read in the changeset from the file.
 
65
 
 
66
        :param from_file: A file-like object (must have iterator support).
 
67
        """
 
68
        object.__init__(self)
 
69
        self.from_file = from_file
 
70
        
 
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.
 
76
        self._read_header()
 
77
        next_line = self._read_patches()
 
78
        self._read_footer(next_line)
 
79
 
 
80
    def get_changeset(self):
 
81
        """Create the actual changeset object.
 
82
        """
 
83
        self.info.create_maps()
 
84
        return self.info
 
85
 
 
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.')
 
92
 
 
93
        for line in self.from_file:
 
94
            if self._handle_info_line(line) is not None:
 
95
                break
 
96
 
 
97
    def _handle_info_line(self, line, in_footer=False):
 
98
        """Handle reading a single line.
 
99
 
 
100
        This may call itself, in the case that we read_multi, and then had a dangling
 
101
        line on the end.
 
102
        """
 
103
        # The bzr header is terminated with a blank line which does not start with #
 
104
        next_line = None
 
105
        if line[:1] == '\n':
 
106
            return 'break'
 
107
        if line[:2] != '# ':
 
108
            raise MalformedHeader('Opening bzr header did not start with #')
 
109
 
 
110
        line = line[2:-1] # Remove the '# '
 
111
        if not line:
 
112
            return # Ignore blank lines
 
113
 
 
114
        if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
 
115
            return
 
116
 
 
117
        loc = line.find(': ')
 
118
        if loc != -1:
 
119
            key = line[:loc]
 
120
            value = line[loc+2:]
 
121
        else:
 
122
            if line[-1:] == ':':
 
123
                key = line[:-1]
 
124
                value, next_line = self._read_many()
 
125
            else:
 
126
                raise MalformedHeader('While looking for key: value pairs, did not find the : %r' % (line))
 
127
 
 
128
        key = key.replace(' ', '_')
 
129
        if hasattr(self.info, key):
 
130
            if getattr(self.info, key) is None:
 
131
                setattr(self.info, key, value)
 
132
            else:
 
133
                raise MalformedHeader('Duplicated Key: %s' % key)
 
134
        else:
 
135
            # What do we do with a key we don't recognize
 
136
            raise MalformedHeader('Unknown Key: %s' % key)
 
137
        
 
138
        if next_line:
 
139
            self._handle_info_line(next_line, in_footer=in_footer)
 
140
 
 
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.
 
144
 
 
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)
 
148
        """
 
149
        values = []
 
150
        for line in self.from_file:
 
151
            if line[:5] != '#    ':
 
152
                return values, line
 
153
            values.append(line[5:-1])
 
154
        return values, None
 
155
 
 
156
    def _read_patches(self):
 
157
        for line in self.from_file:
 
158
            if line[0] == '#':
 
159
                return line
 
160
 
 
161
    def _read_footer(self, first_line=None):
 
162
        """Read the rest of the meta information.
 
163
 
 
164
        :param first_line:  The previous step may iterate passed what it can handle.
 
165
                            That extra line can be passed here.
 
166
        """
 
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:
 
171
                break
 
172
 
 
173
 
 
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.
 
177
    """
 
178
    cr = ChangesetReader(from_file)
 
179
    print cr.get_changeset()
 
180