61
61
_deprecation_warning_done = False
64
class CommitBuilder(object):
65
"""Provides an interface to build up a commit.
67
This allows describing a tree to be committed without needing to
68
know the internals of the format of the repository.
71
# all clients should supply tree roots.
72
record_root_entry = True
74
def __init__(self, repository, parents, config, timestamp=None,
75
timezone=None, committer=None, revprops=None,
77
"""Initiate a CommitBuilder.
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.
91
self._committer = self._config.username()
93
assert isinstance(committer, basestring), type(committer)
94
self._committer = committer
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
102
if revprops is not None:
103
self._revprops.update(revprops)
105
if timestamp is None:
106
timestamp = time.time()
107
# Restrict resolution to 1ms
108
self._timestamp = round(timestamp, 3)
111
self._timezone = osutils.local_time_offset()
113
self._timezone = int(timezone)
115
self._generate_revision_if_needed()
117
def commit(self, message):
118
"""Make the actual commit.
120
:return: The revision id of the recorded revision.
122
rev = _mod_revision.Revision(
123
timestamp=self._timestamp,
124
timezone=self._timezone,
125
committer=self._committer,
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
137
"""Abort the commit that is being built.
139
self.repository.abort_write_group()
141
def revision_tree(self):
142
"""Return the tree that was just committed.
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
150
return RevisionTree(self.repository, self.new_inventory,
151
self._new_revision_id)
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,
167
def _gen_revision_id(self):
168
"""Return new revision-id."""
169
return generate_ids.gen_revision_id(self._config.username(),
172
def _generate_revision_if_needed(self):
173
"""Create a revision id if None was supplied.
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.
179
:raises: CannotSetRevisionId
181
if self._new_revision_id is None:
182
self._new_revision_id = self._gen_revision_id()
183
self.random_revid = True
185
self.random_revid = False
187
def _check_root(self, ie, parent_invs, tree):
188
"""Helper for record_entry_contents.
190
:param ie: An entry being added.
191
:param parent_invs: The inventories of the parent revisions of the
193
:param tree: The tree that is being committed.
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,
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
206
ie.revision = self._new_revision_id
208
def record_entry_contents(self, ie, parent_invs, path, tree):
209
"""Record the content of ie from tree into the commit if needed.
211
Side effect: sets ie.revision when unchanged
213
:param ie: An inventory entry present in the commit.
214
:param parent_invs: The inventories of the parent revisions of the
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
220
if self.new_inventory.root is None:
221
self._check_root(ie, parent_invs, tree)
222
self.new_inventory.add(ie)
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:
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
236
# we are creating a new revision for ie in the history store and
238
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
240
def modified_directory(self, file_id, file_parents):
241
"""Record the presence of a symbolic link.
243
:param file_id: The file_id of the link to record.
244
:param file_parents: The per-file parent revision ids.
246
self._add_text_to_weave(file_id, [], file_parents.keys())
248
def modified_reference(self, file_id, file_parents):
249
"""Record the modification of a reference.
251
:param file_id: The file_id of the link to record.
252
:param file_parents: The per-file parent revision ids.
254
self._add_text_to_weave(file_id, [], file_parents.keys())
256
def modified_file_text(self, file_id, file_parents,
257
get_content_byte_lines, text_sha1=None,
259
"""Record the text of file file_id
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
265
:param text_sha1: Optional SHA1 of the file contents.
266
:param text_size: Optional size of the file contents.
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
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
282
new_lines = get_content_byte_lines()
283
return self._add_text_to_weave(file_id, new_lines,
286
def modified_link(self, file_id, file_parents, link_target):
287
"""Record the presence of a symbolic link.
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.
293
self._add_text_to_weave(file_id, [], file_parents.keys())
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
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()
313
class RootCommitBuilder(CommitBuilder):
314
"""This commitbuilder actually records the root id"""
316
def _check_root(self, ie, parent_invs, tree):
317
"""Helper for record_entry_contents.
319
:param ie: An entry being added.
320
:param parent_invs: The inventories of the parent revisions of the
322
:param tree: The tree that is being committed.
324
# ie must be root for this builder
325
assert ie.parent_id is None
64
328
######################################################################
2073
2343
self.pb.update(message, self.count, self.total)
2076
class CommitBuilder(object):
2077
"""Provides an interface to build up a commit.
2079
This allows describing a tree to be committed without needing to
2080
know the internals of the format of the repository.
2083
# all clients should supply tree roots.
2084
record_root_entry = True
2086
def __init__(self, repository, parents, config, timestamp=None,
2087
timezone=None, committer=None, revprops=None,
2089
"""Initiate a CommitBuilder.
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.
2100
self._config = config
2102
if committer is None:
2103
self._committer = self._config.username()
2105
assert isinstance(committer, basestring), type(committer)
2106
self._committer = committer
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
2114
if revprops is not None:
2115
self._revprops.update(revprops)
2117
if timestamp is None:
2118
timestamp = time.time()
2119
# Restrict resolution to 1ms
2120
self._timestamp = round(timestamp, 3)
2122
if timezone is None:
2123
self._timezone = osutils.local_time_offset()
2125
self._timezone = int(timezone)
2127
self._generate_revision_if_needed()
2129
def commit(self, message):
2130
"""Make the actual commit.
2132
:return: The revision id of the recorded revision.
2134
rev = _mod_revision.Revision(
2135
timestamp=self._timestamp,
2136
timezone=self._timezone,
2137
committer=self._committer,
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
2149
"""Abort the commit that is being built.
2151
self.repository.abort_write_group()
2153
def revision_tree(self):
2154
"""Return the tree that was just committed.
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
2162
return RevisionTree(self.repository, self.new_inventory,
2163
self._new_revision_id)
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,
2179
def _gen_revision_id(self):
2180
"""Return new revision-id."""
2181
return generate_ids.gen_revision_id(self._config.username(),
2184
def _generate_revision_if_needed(self):
2185
"""Create a revision id if None was supplied.
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.
2191
:raises: CannotSetRevisionId
2193
if self._new_revision_id is None:
2194
self._new_revision_id = self._gen_revision_id()
2195
self.random_revid = True
2197
self.random_revid = False
2199
def _check_root(self, ie, parent_invs, tree):
2200
"""Helper for record_entry_contents.
2202
:param ie: An entry being added.
2203
:param parent_invs: The inventories of the parent revisions of the
2205
:param tree: The tree that is being committed.
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,
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
2218
ie.revision = self._new_revision_id
2220
def record_entry_contents(self, ie, parent_invs, path, tree):
2221
"""Record the content of ie from tree into the commit if needed.
2223
Side effect: sets ie.revision when unchanged
2225
:param ie: An inventory entry present in the commit.
2226
:param parent_invs: The inventories of the parent revisions of the
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
2232
if self.new_inventory.root is None:
2233
self._check_root(ie, parent_invs, tree)
2234
self.new_inventory.add(ie)
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:
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
2248
# we are creating a new revision for ie in the history store and
2250
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2252
def modified_directory(self, file_id, file_parents):
2253
"""Record the presence of a symbolic link.
2255
:param file_id: The file_id of the link to record.
2256
:param file_parents: The per-file parent revision ids.
2258
self._add_text_to_weave(file_id, [], file_parents.keys())
2260
def modified_reference(self, file_id, file_parents):
2261
"""Record the modification of a reference.
2263
:param file_id: The file_id of the link to record.
2264
:param file_parents: The per-file parent revision ids.
2266
self._add_text_to_weave(file_id, [], file_parents.keys())
2268
def modified_file_text(self, file_id, file_parents,
2269
get_content_byte_lines, text_sha1=None,
2271
"""Record the text of file file_id
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
2277
:param text_sha1: Optional SHA1 of the file contents.
2278
:param text_size: Optional size of the file contents.
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
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
2294
new_lines = get_content_byte_lines()
2295
return self._add_text_to_weave(file_id, new_lines,
2296
file_parents.keys())
2298
def modified_link(self, file_id, file_parents, link_target):
2299
"""Record the presence of a symbolic link.
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.
2305
self._add_text_to_weave(file_id, [], file_parents.keys())
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
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()
2325
class RootCommitBuilder(CommitBuilder):
2326
"""This commitbuilder actually records the root id"""
2328
def _check_root(self, ie, parent_invs, tree):
2329
"""Helper for record_entry_contents.
2331
:param ie: An entry being added.
2332
:param parent_invs: The inventories of the parent revisions of the
2334
:param tree: The tree that is being committed.
2336
# ie must be root for this builder
2337
assert ie.parent_id is None
2340
2346
_unescape_map = {