~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Aaron Bentley
  • Date: 2007-09-17 12:46:56 UTC
  • mfrom: (2825 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2826.
  • Revision ID: abentley@panoramicfeedback.com-20070917124656-j3hhxhx9igy11mfc
merge bzr.dev

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>[^"]+)"'
120
390
            "Mismatch between inventory revision" \
121
391
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
122
392
        assert inv.root is not None
123
 
        inv_text = self.serialise_inventory(inv)
124
 
        inv_sha1 = osutils.sha_string(inv_text)
125
 
        inv_vf = self.control_weaves.get_weave('inventory',
126
 
                                               self.get_transaction())
127
 
        self._inventory_add_lines(inv_vf, revision_id, parents,
128
 
            osutils.split_lines(inv_text), check_content=False)
129
 
        return inv_sha1
 
393
        inv_lines = self._serialise_inventory_to_lines(inv)
 
394
        inv_vf = self.get_inventory_weave()
 
395
        return self._inventory_add_lines(inv_vf, revision_id, parents,
 
396
            inv_lines, check_content=False)
130
397
 
131
398
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
132
399
        check_content=True):
 
400
        """Store lines in inv_vf and return the sha1 of the inventory."""
133
401
        final_parents = []
134
402
        for parent in parents:
135
403
            if parent in inv_vf:
136
404
                final_parents.append(parent)
137
 
        inv_vf.add_lines(revision_id, final_parents, lines,
138
 
            check_content=check_content)
 
405
        return inv_vf.add_lines(revision_id, final_parents, lines,
 
406
            check_content=check_content)[0]
139
407
 
140
408
    @needs_write_lock
141
409
    def add_revision(self, revision_id, rev, inv=None, config=None):
463
731
        :param revision_id: Optional revision id.
464
732
        """
465
733
        revision_id = osutils.safe_revision_id(revision_id)
466
 
        result = CommitBuilder(self, parents, config, timestamp, timezone,
467
 
                              committer, revprops, revision_id)
 
734
        result = self._commit_builder_class(self, parents, config,
 
735
            timestamp, timezone, committer, revprops, revision_id)
468
736
        self.start_write_group()
469
737
        return result
470
738
 
842
1110
    def serialise_inventory(self, inv):
843
1111
        return self._serializer.write_inventory_to_string(inv)
844
1112
 
 
1113
    def _serialise_inventory_to_lines(self, inv):
 
1114
        return self._serializer.write_inventory_to_lines(inv)
 
1115
 
845
1116
    def get_serializer_format(self):
846
1117
        return self._serializer.format_num
847
1118
 
2072
2343
        self.pb.update(message, self.count, self.total)
2073
2344
 
2074
2345
 
2075
 
class CommitBuilder(object):
2076
 
    """Provides an interface to build up a commit.
2077
 
 
2078
 
    This allows describing a tree to be committed without needing to 
2079
 
    know the internals of the format of the repository.
2080
 
    """
2081
 
    
2082
 
    # all clients should supply tree roots.
2083
 
    record_root_entry = True
2084
 
 
2085
 
    def __init__(self, repository, parents, config, timestamp=None, 
2086
 
                 timezone=None, committer=None, revprops=None, 
2087
 
                 revision_id=None):
2088
 
        """Initiate a CommitBuilder.
2089
 
 
2090
 
        :param repository: Repository to commit to.
2091
 
        :param parents: Revision ids of the parents of the new revision.
2092
 
        :param config: Configuration to use.
2093
 
        :param timestamp: Optional timestamp recorded for commit.
2094
 
        :param timezone: Optional timezone for timestamp.
2095
 
        :param committer: Optional committer to set for commit.
2096
 
        :param revprops: Optional dictionary of revision properties.
2097
 
        :param revision_id: Optional revision id.
2098
 
        """
2099
 
        self._config = config
2100
 
 
2101
 
        if committer is None:
2102
 
            self._committer = self._config.username()
2103
 
        else:
2104
 
            assert isinstance(committer, basestring), type(committer)
2105
 
            self._committer = committer
2106
 
 
2107
 
        self.new_inventory = Inventory(None)
2108
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
2109
 
        self.parents = parents
2110
 
        self.repository = repository
2111
 
 
2112
 
        self._revprops = {}
2113
 
        if revprops is not None:
2114
 
            self._revprops.update(revprops)
2115
 
 
2116
 
        if timestamp is None:
2117
 
            timestamp = time.time()
2118
 
        # Restrict resolution to 1ms
2119
 
        self._timestamp = round(timestamp, 3)
2120
 
 
2121
 
        if timezone is None:
2122
 
            self._timezone = osutils.local_time_offset()
2123
 
        else:
2124
 
            self._timezone = int(timezone)
2125
 
 
2126
 
        self._generate_revision_if_needed()
2127
 
 
2128
 
    def commit(self, message):
2129
 
        """Make the actual commit.
2130
 
 
2131
 
        :return: The revision id of the recorded revision.
2132
 
        """
2133
 
        rev = _mod_revision.Revision(
2134
 
                       timestamp=self._timestamp,
2135
 
                       timezone=self._timezone,
2136
 
                       committer=self._committer,
2137
 
                       message=message,
2138
 
                       inventory_sha1=self.inv_sha1,
2139
 
                       revision_id=self._new_revision_id,
2140
 
                       properties=self._revprops)
2141
 
        rev.parent_ids = self.parents
2142
 
        self.repository.add_revision(self._new_revision_id, rev,
2143
 
            self.new_inventory, self._config)
2144
 
        self.repository.commit_write_group()
2145
 
        return self._new_revision_id
2146
 
 
2147
 
    def abort(self):
2148
 
        """Abort the commit that is being built.
2149
 
        """
2150
 
        self.repository.abort_write_group()
2151
 
 
2152
 
    def revision_tree(self):
2153
 
        """Return the tree that was just committed.
2154
 
 
2155
 
        After calling commit() this can be called to get a RevisionTree
2156
 
        representing the newly committed tree. This is preferred to
2157
 
        calling Repository.revision_tree() because that may require
2158
 
        deserializing the inventory, while we already have a copy in
2159
 
        memory.
2160
 
        """
2161
 
        return RevisionTree(self.repository, self.new_inventory,
2162
 
                            self._new_revision_id)
2163
 
 
2164
 
    def finish_inventory(self):
2165
 
        """Tell the builder that the inventory is finished."""
2166
 
        if self.new_inventory.root is None:
2167
 
            symbol_versioning.warn('Root entry should be supplied to'
2168
 
                ' record_entry_contents, as of bzr 0.10.',
2169
 
                 DeprecationWarning, stacklevel=2)
2170
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2171
 
        self.new_inventory.revision_id = self._new_revision_id
2172
 
        self.inv_sha1 = self.repository.add_inventory(
2173
 
            self._new_revision_id,
2174
 
            self.new_inventory,
2175
 
            self.parents
2176
 
            )
2177
 
 
2178
 
    def _gen_revision_id(self):
2179
 
        """Return new revision-id."""
2180
 
        return generate_ids.gen_revision_id(self._config.username(),
2181
 
                                            self._timestamp)
2182
 
 
2183
 
    def _generate_revision_if_needed(self):
2184
 
        """Create a revision id if None was supplied.
2185
 
        
2186
 
        If the repository can not support user-specified revision ids
2187
 
        they should override this function and raise CannotSetRevisionId
2188
 
        if _new_revision_id is not None.
2189
 
 
2190
 
        :raises: CannotSetRevisionId
2191
 
        """
2192
 
        if self._new_revision_id is None:
2193
 
            self._new_revision_id = self._gen_revision_id()
2194
 
            self.random_revid = True
2195
 
        else:
2196
 
            self.random_revid = False
2197
 
 
2198
 
    def _check_root(self, ie, parent_invs, tree):
2199
 
        """Helper for record_entry_contents.
2200
 
 
2201
 
        :param ie: An entry being added.
2202
 
        :param parent_invs: The inventories of the parent revisions of the
2203
 
            commit.
2204
 
        :param tree: The tree that is being committed.
2205
 
        """
2206
 
        if ie.parent_id is not None:
2207
 
            # if ie is not root, add a root automatically.
2208
 
            symbol_versioning.warn('Root entry should be supplied to'
2209
 
                ' record_entry_contents, as of bzr 0.10.',
2210
 
                 DeprecationWarning, stacklevel=2)
2211
 
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2212
 
                                       '', tree)
2213
 
        else:
2214
 
            # In this revision format, root entries have no knit or weave When
2215
 
            # serializing out to disk and back in root.revision is always
2216
 
            # _new_revision_id
2217
 
            ie.revision = self._new_revision_id
2218
 
 
2219
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2220
 
        """Record the content of ie from tree into the commit if needed.
2221
 
 
2222
 
        Side effect: sets ie.revision when unchanged
2223
 
 
2224
 
        :param ie: An inventory entry present in the commit.
2225
 
        :param parent_invs: The inventories of the parent revisions of the
2226
 
            commit.
2227
 
        :param path: The path the entry is at in the tree.
2228
 
        :param tree: The tree which contains this entry and should be used to 
2229
 
        obtain content.
2230
 
        """
2231
 
        if self.new_inventory.root is None:
2232
 
            self._check_root(ie, parent_invs, tree)
2233
 
        self.new_inventory.add(ie)
2234
 
 
2235
 
        # ie.revision is always None if the InventoryEntry is considered
2236
 
        # for committing. ie.snapshot will record the correct revision 
2237
 
        # which may be the sole parent if it is untouched.
2238
 
        if ie.revision is not None:
2239
 
            return
2240
 
 
2241
 
        parent_candiate_entries = ie.parent_candidates(parent_invs)
2242
 
        heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
2243
 
        # XXX: Note that this is unordered - and this is tolerable because 
2244
 
        # the previous code was also unordered.
2245
 
        previous_entries = dict((head, parent_candiate_entries[head]) for head
2246
 
            in heads)
2247
 
        # we are creating a new revision for ie in the history store and
2248
 
        # inventory.
2249
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2250
 
 
2251
 
    def modified_directory(self, file_id, file_parents):
2252
 
        """Record the presence of a symbolic link.
2253
 
 
2254
 
        :param file_id: The file_id of the link to record.
2255
 
        :param file_parents: The per-file parent revision ids.
2256
 
        """
2257
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2258
 
 
2259
 
    def modified_reference(self, file_id, file_parents):
2260
 
        """Record the modification of a reference.
2261
 
 
2262
 
        :param file_id: The file_id of the link to record.
2263
 
        :param file_parents: The per-file parent revision ids.
2264
 
        """
2265
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2266
 
    
2267
 
    def modified_file_text(self, file_id, file_parents,
2268
 
                           get_content_byte_lines, text_sha1=None,
2269
 
                           text_size=None):
2270
 
        """Record the text of file file_id
2271
 
 
2272
 
        :param file_id: The file_id of the file to record the text of.
2273
 
        :param file_parents: The per-file parent revision ids.
2274
 
        :param get_content_byte_lines: A callable which will return the byte
2275
 
            lines for the file.
2276
 
        :param text_sha1: Optional SHA1 of the file contents.
2277
 
        :param text_size: Optional size of the file contents.
2278
 
        """
2279
 
        # mutter('storing text of file {%s} in revision {%s} into %r',
2280
 
        #        file_id, self._new_revision_id, self.repository.weave_store)
2281
 
        # special case to avoid diffing on renames or 
2282
 
        # reparenting
2283
 
        if (len(file_parents) == 1
2284
 
            and text_sha1 == file_parents.values()[0].text_sha1
2285
 
            and text_size == file_parents.values()[0].text_size):
2286
 
            previous_ie = file_parents.values()[0]
2287
 
            versionedfile = self.repository.weave_store.get_weave(file_id,
2288
 
                self.repository.get_transaction())
2289
 
            versionedfile.clone_text(self._new_revision_id,
2290
 
                previous_ie.revision, file_parents.keys())
2291
 
            return text_sha1, text_size
2292
 
        else:
2293
 
            new_lines = get_content_byte_lines()
2294
 
            return self._add_text_to_weave(file_id, new_lines,
2295
 
                file_parents.keys())
2296
 
 
2297
 
    def modified_link(self, file_id, file_parents, link_target):
2298
 
        """Record the presence of a symbolic link.
2299
 
 
2300
 
        :param file_id: The file_id of the link to record.
2301
 
        :param file_parents: The per-file parent revision ids.
2302
 
        :param link_target: Target location of this link.
2303
 
        """
2304
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2305
 
 
2306
 
    def _add_text_to_weave(self, file_id, new_lines, parents):
2307
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
2308
 
            file_id, self.repository.get_transaction())
2309
 
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
2310
 
        # than add_lines, and allows committing when a parent is ghosted for
2311
 
        # some reason.
2312
 
        # Note: as we read the content directly from the tree, we know its not
2313
 
        # been turned into unicode or badly split - but a broken tree
2314
 
        # implementation could give us bad output from readlines() so this is
2315
 
        # not a guarantee of safety. What would be better is always checking
2316
 
        # the content during test suite execution. RBC 20070912
2317
 
        result = versionedfile.add_lines_with_ghosts(
2318
 
            self._new_revision_id, parents, new_lines,
2319
 
            random_id=self.random_revid, check_content=False)[0:2]
2320
 
        versionedfile.clear_cache()
2321
 
        return result
2322
 
 
2323
 
 
2324
 
class RootCommitBuilder(CommitBuilder):
2325
 
    """This commitbuilder actually records the root id"""
2326
 
    
2327
 
    def _check_root(self, ie, parent_invs, tree):
2328
 
        """Helper for record_entry_contents.
2329
 
 
2330
 
        :param ie: An entry being added.
2331
 
        :param parent_invs: The inventories of the parent revisions of the
2332
 
            commit.
2333
 
        :param tree: The tree that is being committed.
2334
 
        """
2335
 
        # ie must be root for this builder
2336
 
        assert ie.parent_id is None
2337
 
 
2338
 
 
2339
2346
_unescape_map = {
2340
2347
    'apos':"'",
2341
2348
    'quot':'"',