~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-09-17 00:50:35 UTC
  • mfrom: (2818.3.3 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070917005035-cshdkpzbj63id1uw
(robertc) Allow declaritive changing of the CommitBuilder class for repositories. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
61
61
_deprecation_warning_done = False
62
62
 
63
63
 
 
64
class CommitBuilder(object):
 
65
    """Provides an interface to build up a commit.
 
66
 
 
67
    This allows describing a tree to be committed without needing to 
 
68
    know the internals of the format of the repository.
 
69
    """
 
70
    
 
71
    # all clients should supply tree roots.
 
72
    record_root_entry = True
 
73
 
 
74
    def __init__(self, repository, parents, config, timestamp=None, 
 
75
                 timezone=None, committer=None, revprops=None, 
 
76
                 revision_id=None):
 
77
        """Initiate a CommitBuilder.
 
78
 
 
79
        :param repository: Repository to commit to.
 
80
        :param parents: Revision ids of the parents of the new revision.
 
81
        :param config: Configuration to use.
 
82
        :param timestamp: Optional timestamp recorded for commit.
 
83
        :param timezone: Optional timezone for timestamp.
 
84
        :param committer: Optional committer to set for commit.
 
85
        :param revprops: Optional dictionary of revision properties.
 
86
        :param revision_id: Optional revision id.
 
87
        """
 
88
        self._config = config
 
89
 
 
90
        if committer is None:
 
91
            self._committer = self._config.username()
 
92
        else:
 
93
            assert isinstance(committer, basestring), type(committer)
 
94
            self._committer = committer
 
95
 
 
96
        self.new_inventory = Inventory(None)
 
97
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
98
        self.parents = parents
 
99
        self.repository = repository
 
100
 
 
101
        self._revprops = {}
 
102
        if revprops is not None:
 
103
            self._revprops.update(revprops)
 
104
 
 
105
        if timestamp is None:
 
106
            timestamp = time.time()
 
107
        # Restrict resolution to 1ms
 
108
        self._timestamp = round(timestamp, 3)
 
109
 
 
110
        if timezone is None:
 
111
            self._timezone = osutils.local_time_offset()
 
112
        else:
 
113
            self._timezone = int(timezone)
 
114
 
 
115
        self._generate_revision_if_needed()
 
116
 
 
117
    def commit(self, message):
 
118
        """Make the actual commit.
 
119
 
 
120
        :return: The revision id of the recorded revision.
 
121
        """
 
122
        rev = _mod_revision.Revision(
 
123
                       timestamp=self._timestamp,
 
124
                       timezone=self._timezone,
 
125
                       committer=self._committer,
 
126
                       message=message,
 
127
                       inventory_sha1=self.inv_sha1,
 
128
                       revision_id=self._new_revision_id,
 
129
                       properties=self._revprops)
 
130
        rev.parent_ids = self.parents
 
131
        self.repository.add_revision(self._new_revision_id, rev,
 
132
            self.new_inventory, self._config)
 
133
        self.repository.commit_write_group()
 
134
        return self._new_revision_id
 
135
 
 
136
    def abort(self):
 
137
        """Abort the commit that is being built.
 
138
        """
 
139
        self.repository.abort_write_group()
 
140
 
 
141
    def revision_tree(self):
 
142
        """Return the tree that was just committed.
 
143
 
 
144
        After calling commit() this can be called to get a RevisionTree
 
145
        representing the newly committed tree. This is preferred to
 
146
        calling Repository.revision_tree() because that may require
 
147
        deserializing the inventory, while we already have a copy in
 
148
        memory.
 
149
        """
 
150
        return RevisionTree(self.repository, self.new_inventory,
 
151
                            self._new_revision_id)
 
152
 
 
153
    def finish_inventory(self):
 
154
        """Tell the builder that the inventory is finished."""
 
155
        if self.new_inventory.root is None:
 
156
            symbol_versioning.warn('Root entry should be supplied to'
 
157
                ' record_entry_contents, as of bzr 0.10.',
 
158
                 DeprecationWarning, stacklevel=2)
 
159
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
160
        self.new_inventory.revision_id = self._new_revision_id
 
161
        self.inv_sha1 = self.repository.add_inventory(
 
162
            self._new_revision_id,
 
163
            self.new_inventory,
 
164
            self.parents
 
165
            )
 
166
 
 
167
    def _gen_revision_id(self):
 
168
        """Return new revision-id."""
 
169
        return generate_ids.gen_revision_id(self._config.username(),
 
170
                                            self._timestamp)
 
171
 
 
172
    def _generate_revision_if_needed(self):
 
173
        """Create a revision id if None was supplied.
 
174
        
 
175
        If the repository can not support user-specified revision ids
 
176
        they should override this function and raise CannotSetRevisionId
 
177
        if _new_revision_id is not None.
 
178
 
 
179
        :raises: CannotSetRevisionId
 
180
        """
 
181
        if self._new_revision_id is None:
 
182
            self._new_revision_id = self._gen_revision_id()
 
183
            self.random_revid = True
 
184
        else:
 
185
            self.random_revid = False
 
186
 
 
187
    def _check_root(self, ie, parent_invs, tree):
 
188
        """Helper for record_entry_contents.
 
189
 
 
190
        :param ie: An entry being added.
 
191
        :param parent_invs: The inventories of the parent revisions of the
 
192
            commit.
 
193
        :param tree: The tree that is being committed.
 
194
        """
 
195
        if ie.parent_id is not None:
 
196
            # if ie is not root, add a root automatically.
 
197
            symbol_versioning.warn('Root entry should be supplied to'
 
198
                ' record_entry_contents, as of bzr 0.10.',
 
199
                 DeprecationWarning, stacklevel=2)
 
200
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
 
201
                                       '', tree)
 
202
        else:
 
203
            # In this revision format, root entries have no knit or weave When
 
204
            # serializing out to disk and back in root.revision is always
 
205
            # _new_revision_id
 
206
            ie.revision = self._new_revision_id
 
207
 
 
208
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
209
        """Record the content of ie from tree into the commit if needed.
 
210
 
 
211
        Side effect: sets ie.revision when unchanged
 
212
 
 
213
        :param ie: An inventory entry present in the commit.
 
214
        :param parent_invs: The inventories of the parent revisions of the
 
215
            commit.
 
216
        :param path: The path the entry is at in the tree.
 
217
        :param tree: The tree which contains this entry and should be used to 
 
218
        obtain content.
 
219
        """
 
220
        if self.new_inventory.root is None:
 
221
            self._check_root(ie, parent_invs, tree)
 
222
        self.new_inventory.add(ie)
 
223
 
 
224
        # ie.revision is always None if the InventoryEntry is considered
 
225
        # for committing. ie.snapshot will record the correct revision 
 
226
        # which may be the sole parent if it is untouched.
 
227
        if ie.revision is not None:
 
228
            return
 
229
 
 
230
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
231
        heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
 
232
        # XXX: Note that this is unordered - and this is tolerable because 
 
233
        # the previous code was also unordered.
 
234
        previous_entries = dict((head, parent_candiate_entries[head]) for head
 
235
            in heads)
 
236
        # we are creating a new revision for ie in the history store and
 
237
        # inventory.
 
238
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
 
239
 
 
240
    def modified_directory(self, file_id, file_parents):
 
241
        """Record the presence of a symbolic link.
 
242
 
 
243
        :param file_id: The file_id of the link to record.
 
244
        :param file_parents: The per-file parent revision ids.
 
245
        """
 
246
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
247
 
 
248
    def modified_reference(self, file_id, file_parents):
 
249
        """Record the modification of a reference.
 
250
 
 
251
        :param file_id: The file_id of the link to record.
 
252
        :param file_parents: The per-file parent revision ids.
 
253
        """
 
254
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
255
    
 
256
    def modified_file_text(self, file_id, file_parents,
 
257
                           get_content_byte_lines, text_sha1=None,
 
258
                           text_size=None):
 
259
        """Record the text of file file_id
 
260
 
 
261
        :param file_id: The file_id of the file to record the text of.
 
262
        :param file_parents: The per-file parent revision ids.
 
263
        :param get_content_byte_lines: A callable which will return the byte
 
264
            lines for the file.
 
265
        :param text_sha1: Optional SHA1 of the file contents.
 
266
        :param text_size: Optional size of the file contents.
 
267
        """
 
268
        # mutter('storing text of file {%s} in revision {%s} into %r',
 
269
        #        file_id, self._new_revision_id, self.repository.weave_store)
 
270
        # special case to avoid diffing on renames or 
 
271
        # reparenting
 
272
        if (len(file_parents) == 1
 
273
            and text_sha1 == file_parents.values()[0].text_sha1
 
274
            and text_size == file_parents.values()[0].text_size):
 
275
            previous_ie = file_parents.values()[0]
 
276
            versionedfile = self.repository.weave_store.get_weave(file_id,
 
277
                self.repository.get_transaction())
 
278
            versionedfile.clone_text(self._new_revision_id,
 
279
                previous_ie.revision, file_parents.keys())
 
280
            return text_sha1, text_size
 
281
        else:
 
282
            new_lines = get_content_byte_lines()
 
283
            return self._add_text_to_weave(file_id, new_lines,
 
284
                file_parents.keys())
 
285
 
 
286
    def modified_link(self, file_id, file_parents, link_target):
 
287
        """Record the presence of a symbolic link.
 
288
 
 
289
        :param file_id: The file_id of the link to record.
 
290
        :param file_parents: The per-file parent revision ids.
 
291
        :param link_target: Target location of this link.
 
292
        """
 
293
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
294
 
 
295
    def _add_text_to_weave(self, file_id, new_lines, parents):
 
296
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
297
            file_id, self.repository.get_transaction())
 
298
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
299
        # than add_lines, and allows committing when a parent is ghosted for
 
300
        # some reason.
 
301
        # Note: as we read the content directly from the tree, we know its not
 
302
        # been turned into unicode or badly split - but a broken tree
 
303
        # implementation could give us bad output from readlines() so this is
 
304
        # not a guarantee of safety. What would be better is always checking
 
305
        # the content during test suite execution. RBC 20070912
 
306
        result = versionedfile.add_lines_with_ghosts(
 
307
            self._new_revision_id, parents, new_lines,
 
308
            random_id=self.random_revid, check_content=False)[0:2]
 
309
        versionedfile.clear_cache()
 
310
        return result
 
311
 
 
312
 
 
313
class RootCommitBuilder(CommitBuilder):
 
314
    """This commitbuilder actually records the root id"""
 
315
    
 
316
    def _check_root(self, ie, parent_invs, tree):
 
317
        """Helper for record_entry_contents.
 
318
 
 
319
        :param ie: An entry being added.
 
320
        :param parent_invs: The inventories of the parent revisions of the
 
321
            commit.
 
322
        :param tree: The tree that is being committed.
 
323
        """
 
324
        # ie must be root for this builder
 
325
        assert ie.parent_id is None
 
326
 
 
327
 
64
328
######################################################################
65
329
# Repositories
66
330
 
76
340
    remote) disk.
77
341
    """
78
342
 
 
343
    # What class to use for a CommitBuilder. Often its simpler to change this
 
344
    # in a Repository class subclass rather than to override
 
345
    # get_commit_builder.
 
346
    _commit_builder_class = CommitBuilder
 
347
    # The search regex used by xml based repositories to determine what things
 
348
    # where changed in a single commit.
79
349
    _file_ids_altered_regex = lazy_regex.lazy_compile(
80
350
        r'file_id="(?P<file_id>[^"]+)"'
81
351
        r'.* revision="(?P<revision_id>[^"]+)"'
461
731
        :param revision_id: Optional revision id.
462
732
        """
463
733
        revision_id = osutils.safe_revision_id(revision_id)
464
 
        result = CommitBuilder(self, parents, config, timestamp, timezone,
465
 
                              committer, revprops, revision_id)
 
734
        result = self._commit_builder_class(self, parents, config,
 
735
            timestamp, timezone, committer, revprops, revision_id)
466
736
        self.start_write_group()
467
737
        return result
468
738
 
2073
2343
        self.pb.update(message, self.count, self.total)
2074
2344
 
2075
2345
 
2076
 
class CommitBuilder(object):
2077
 
    """Provides an interface to build up a commit.
2078
 
 
2079
 
    This allows describing a tree to be committed without needing to 
2080
 
    know the internals of the format of the repository.
2081
 
    """
2082
 
    
2083
 
    # all clients should supply tree roots.
2084
 
    record_root_entry = True
2085
 
 
2086
 
    def __init__(self, repository, parents, config, timestamp=None, 
2087
 
                 timezone=None, committer=None, revprops=None, 
2088
 
                 revision_id=None):
2089
 
        """Initiate a CommitBuilder.
2090
 
 
2091
 
        :param repository: Repository to commit to.
2092
 
        :param parents: Revision ids of the parents of the new revision.
2093
 
        :param config: Configuration to use.
2094
 
        :param timestamp: Optional timestamp recorded for commit.
2095
 
        :param timezone: Optional timezone for timestamp.
2096
 
        :param committer: Optional committer to set for commit.
2097
 
        :param revprops: Optional dictionary of revision properties.
2098
 
        :param revision_id: Optional revision id.
2099
 
        """
2100
 
        self._config = config
2101
 
 
2102
 
        if committer is None:
2103
 
            self._committer = self._config.username()
2104
 
        else:
2105
 
            assert isinstance(committer, basestring), type(committer)
2106
 
            self._committer = committer
2107
 
 
2108
 
        self.new_inventory = Inventory(None)
2109
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
2110
 
        self.parents = parents
2111
 
        self.repository = repository
2112
 
 
2113
 
        self._revprops = {}
2114
 
        if revprops is not None:
2115
 
            self._revprops.update(revprops)
2116
 
 
2117
 
        if timestamp is None:
2118
 
            timestamp = time.time()
2119
 
        # Restrict resolution to 1ms
2120
 
        self._timestamp = round(timestamp, 3)
2121
 
 
2122
 
        if timezone is None:
2123
 
            self._timezone = osutils.local_time_offset()
2124
 
        else:
2125
 
            self._timezone = int(timezone)
2126
 
 
2127
 
        self._generate_revision_if_needed()
2128
 
 
2129
 
    def commit(self, message):
2130
 
        """Make the actual commit.
2131
 
 
2132
 
        :return: The revision id of the recorded revision.
2133
 
        """
2134
 
        rev = _mod_revision.Revision(
2135
 
                       timestamp=self._timestamp,
2136
 
                       timezone=self._timezone,
2137
 
                       committer=self._committer,
2138
 
                       message=message,
2139
 
                       inventory_sha1=self.inv_sha1,
2140
 
                       revision_id=self._new_revision_id,
2141
 
                       properties=self._revprops)
2142
 
        rev.parent_ids = self.parents
2143
 
        self.repository.add_revision(self._new_revision_id, rev,
2144
 
            self.new_inventory, self._config)
2145
 
        self.repository.commit_write_group()
2146
 
        return self._new_revision_id
2147
 
 
2148
 
    def abort(self):
2149
 
        """Abort the commit that is being built.
2150
 
        """
2151
 
        self.repository.abort_write_group()
2152
 
 
2153
 
    def revision_tree(self):
2154
 
        """Return the tree that was just committed.
2155
 
 
2156
 
        After calling commit() this can be called to get a RevisionTree
2157
 
        representing the newly committed tree. This is preferred to
2158
 
        calling Repository.revision_tree() because that may require
2159
 
        deserializing the inventory, while we already have a copy in
2160
 
        memory.
2161
 
        """
2162
 
        return RevisionTree(self.repository, self.new_inventory,
2163
 
                            self._new_revision_id)
2164
 
 
2165
 
    def finish_inventory(self):
2166
 
        """Tell the builder that the inventory is finished."""
2167
 
        if self.new_inventory.root is None:
2168
 
            symbol_versioning.warn('Root entry should be supplied to'
2169
 
                ' record_entry_contents, as of bzr 0.10.',
2170
 
                 DeprecationWarning, stacklevel=2)
2171
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2172
 
        self.new_inventory.revision_id = self._new_revision_id
2173
 
        self.inv_sha1 = self.repository.add_inventory(
2174
 
            self._new_revision_id,
2175
 
            self.new_inventory,
2176
 
            self.parents
2177
 
            )
2178
 
 
2179
 
    def _gen_revision_id(self):
2180
 
        """Return new revision-id."""
2181
 
        return generate_ids.gen_revision_id(self._config.username(),
2182
 
                                            self._timestamp)
2183
 
 
2184
 
    def _generate_revision_if_needed(self):
2185
 
        """Create a revision id if None was supplied.
2186
 
        
2187
 
        If the repository can not support user-specified revision ids
2188
 
        they should override this function and raise CannotSetRevisionId
2189
 
        if _new_revision_id is not None.
2190
 
 
2191
 
        :raises: CannotSetRevisionId
2192
 
        """
2193
 
        if self._new_revision_id is None:
2194
 
            self._new_revision_id = self._gen_revision_id()
2195
 
            self.random_revid = True
2196
 
        else:
2197
 
            self.random_revid = False
2198
 
 
2199
 
    def _check_root(self, ie, parent_invs, tree):
2200
 
        """Helper for record_entry_contents.
2201
 
 
2202
 
        :param ie: An entry being added.
2203
 
        :param parent_invs: The inventories of the parent revisions of the
2204
 
            commit.
2205
 
        :param tree: The tree that is being committed.
2206
 
        """
2207
 
        if ie.parent_id is not None:
2208
 
            # if ie is not root, add a root automatically.
2209
 
            symbol_versioning.warn('Root entry should be supplied to'
2210
 
                ' record_entry_contents, as of bzr 0.10.',
2211
 
                 DeprecationWarning, stacklevel=2)
2212
 
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2213
 
                                       '', tree)
2214
 
        else:
2215
 
            # In this revision format, root entries have no knit or weave When
2216
 
            # serializing out to disk and back in root.revision is always
2217
 
            # _new_revision_id
2218
 
            ie.revision = self._new_revision_id
2219
 
 
2220
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2221
 
        """Record the content of ie from tree into the commit if needed.
2222
 
 
2223
 
        Side effect: sets ie.revision when unchanged
2224
 
 
2225
 
        :param ie: An inventory entry present in the commit.
2226
 
        :param parent_invs: The inventories of the parent revisions of the
2227
 
            commit.
2228
 
        :param path: The path the entry is at in the tree.
2229
 
        :param tree: The tree which contains this entry and should be used to 
2230
 
        obtain content.
2231
 
        """
2232
 
        if self.new_inventory.root is None:
2233
 
            self._check_root(ie, parent_invs, tree)
2234
 
        self.new_inventory.add(ie)
2235
 
 
2236
 
        # ie.revision is always None if the InventoryEntry is considered
2237
 
        # for committing. ie.snapshot will record the correct revision 
2238
 
        # which may be the sole parent if it is untouched.
2239
 
        if ie.revision is not None:
2240
 
            return
2241
 
 
2242
 
        parent_candiate_entries = ie.parent_candidates(parent_invs)
2243
 
        heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
2244
 
        # XXX: Note that this is unordered - and this is tolerable because 
2245
 
        # the previous code was also unordered.
2246
 
        previous_entries = dict((head, parent_candiate_entries[head]) for head
2247
 
            in heads)
2248
 
        # we are creating a new revision for ie in the history store and
2249
 
        # inventory.
2250
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2251
 
 
2252
 
    def modified_directory(self, file_id, file_parents):
2253
 
        """Record the presence of a symbolic link.
2254
 
 
2255
 
        :param file_id: The file_id of the link to record.
2256
 
        :param file_parents: The per-file parent revision ids.
2257
 
        """
2258
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2259
 
 
2260
 
    def modified_reference(self, file_id, file_parents):
2261
 
        """Record the modification of a reference.
2262
 
 
2263
 
        :param file_id: The file_id of the link to record.
2264
 
        :param file_parents: The per-file parent revision ids.
2265
 
        """
2266
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2267
 
    
2268
 
    def modified_file_text(self, file_id, file_parents,
2269
 
                           get_content_byte_lines, text_sha1=None,
2270
 
                           text_size=None):
2271
 
        """Record the text of file file_id
2272
 
 
2273
 
        :param file_id: The file_id of the file to record the text of.
2274
 
        :param file_parents: The per-file parent revision ids.
2275
 
        :param get_content_byte_lines: A callable which will return the byte
2276
 
            lines for the file.
2277
 
        :param text_sha1: Optional SHA1 of the file contents.
2278
 
        :param text_size: Optional size of the file contents.
2279
 
        """
2280
 
        # mutter('storing text of file {%s} in revision {%s} into %r',
2281
 
        #        file_id, self._new_revision_id, self.repository.weave_store)
2282
 
        # special case to avoid diffing on renames or 
2283
 
        # reparenting
2284
 
        if (len(file_parents) == 1
2285
 
            and text_sha1 == file_parents.values()[0].text_sha1
2286
 
            and text_size == file_parents.values()[0].text_size):
2287
 
            previous_ie = file_parents.values()[0]
2288
 
            versionedfile = self.repository.weave_store.get_weave(file_id,
2289
 
                self.repository.get_transaction())
2290
 
            versionedfile.clone_text(self._new_revision_id,
2291
 
                previous_ie.revision, file_parents.keys())
2292
 
            return text_sha1, text_size
2293
 
        else:
2294
 
            new_lines = get_content_byte_lines()
2295
 
            return self._add_text_to_weave(file_id, new_lines,
2296
 
                file_parents.keys())
2297
 
 
2298
 
    def modified_link(self, file_id, file_parents, link_target):
2299
 
        """Record the presence of a symbolic link.
2300
 
 
2301
 
        :param file_id: The file_id of the link to record.
2302
 
        :param file_parents: The per-file parent revision ids.
2303
 
        :param link_target: Target location of this link.
2304
 
        """
2305
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2306
 
 
2307
 
    def _add_text_to_weave(self, file_id, new_lines, parents):
2308
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
2309
 
            file_id, self.repository.get_transaction())
2310
 
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
2311
 
        # than add_lines, and allows committing when a parent is ghosted for
2312
 
        # some reason.
2313
 
        # Note: as we read the content directly from the tree, we know its not
2314
 
        # been turned into unicode or badly split - but a broken tree
2315
 
        # implementation could give us bad output from readlines() so this is
2316
 
        # not a guarantee of safety. What would be better is always checking
2317
 
        # the content during test suite execution. RBC 20070912
2318
 
        result = versionedfile.add_lines_with_ghosts(
2319
 
            self._new_revision_id, parents, new_lines,
2320
 
            random_id=self.random_revid, check_content=False)[0:2]
2321
 
        versionedfile.clear_cache()
2322
 
        return result
2323
 
 
2324
 
 
2325
 
class RootCommitBuilder(CommitBuilder):
2326
 
    """This commitbuilder actually records the root id"""
2327
 
    
2328
 
    def _check_root(self, ie, parent_invs, tree):
2329
 
        """Helper for record_entry_contents.
2330
 
 
2331
 
        :param ie: An entry being added.
2332
 
        :param parent_invs: The inventories of the parent revisions of the
2333
 
            commit.
2334
 
        :param tree: The tree that is being committed.
2335
 
        """
2336
 
        # ie must be root for this builder
2337
 
        assert ie.parent_id is None
2338
 
 
2339
 
 
2340
2346
_unescape_map = {
2341
2347
    'apos':"'",
2342
2348
    'quot':'"',