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
######################################################################
2072
2343
self.pb.update(message, self.count, self.total)
2075
class CommitBuilder(object):
2076
"""Provides an interface to build up a commit.
2078
This allows describing a tree to be committed without needing to
2079
know the internals of the format of the repository.
2082
# all clients should supply tree roots.
2083
record_root_entry = True
2085
def __init__(self, repository, parents, config, timestamp=None,
2086
timezone=None, committer=None, revprops=None,
2088
"""Initiate a CommitBuilder.
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.
2099
self._config = config
2101
if committer is None:
2102
self._committer = self._config.username()
2104
assert isinstance(committer, basestring), type(committer)
2105
self._committer = committer
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
2113
if revprops is not None:
2114
self._revprops.update(revprops)
2116
if timestamp is None:
2117
timestamp = time.time()
2118
# Restrict resolution to 1ms
2119
self._timestamp = round(timestamp, 3)
2121
if timezone is None:
2122
self._timezone = osutils.local_time_offset()
2124
self._timezone = int(timezone)
2126
self._generate_revision_if_needed()
2128
def commit(self, message):
2129
"""Make the actual commit.
2131
:return: The revision id of the recorded revision.
2133
rev = _mod_revision.Revision(
2134
timestamp=self._timestamp,
2135
timezone=self._timezone,
2136
committer=self._committer,
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
2148
"""Abort the commit that is being built.
2150
self.repository.abort_write_group()
2152
def revision_tree(self):
2153
"""Return the tree that was just committed.
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
2161
return RevisionTree(self.repository, self.new_inventory,
2162
self._new_revision_id)
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,
2178
def _gen_revision_id(self):
2179
"""Return new revision-id."""
2180
return generate_ids.gen_revision_id(self._config.username(),
2183
def _generate_revision_if_needed(self):
2184
"""Create a revision id if None was supplied.
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.
2190
:raises: CannotSetRevisionId
2192
if self._new_revision_id is None:
2193
self._new_revision_id = self._gen_revision_id()
2194
self.random_revid = True
2196
self.random_revid = False
2198
def _check_root(self, ie, parent_invs, tree):
2199
"""Helper for record_entry_contents.
2201
:param ie: An entry being added.
2202
:param parent_invs: The inventories of the parent revisions of the
2204
:param tree: The tree that is being committed.
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,
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
2217
ie.revision = self._new_revision_id
2219
def record_entry_contents(self, ie, parent_invs, path, tree):
2220
"""Record the content of ie from tree into the commit if needed.
2222
Side effect: sets ie.revision when unchanged
2224
:param ie: An inventory entry present in the commit.
2225
:param parent_invs: The inventories of the parent revisions of the
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
2231
if self.new_inventory.root is None:
2232
self._check_root(ie, parent_invs, tree)
2233
self.new_inventory.add(ie)
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:
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
2247
# we are creating a new revision for ie in the history store and
2249
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2251
def modified_directory(self, file_id, file_parents):
2252
"""Record the presence of a symbolic link.
2254
:param file_id: The file_id of the link to record.
2255
:param file_parents: The per-file parent revision ids.
2257
self._add_text_to_weave(file_id, [], file_parents.keys())
2259
def modified_reference(self, file_id, file_parents):
2260
"""Record the modification of a reference.
2262
:param file_id: The file_id of the link to record.
2263
:param file_parents: The per-file parent revision ids.
2265
self._add_text_to_weave(file_id, [], file_parents.keys())
2267
def modified_file_text(self, file_id, file_parents,
2268
get_content_byte_lines, text_sha1=None,
2270
"""Record the text of file file_id
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
2276
:param text_sha1: Optional SHA1 of the file contents.
2277
:param text_size: Optional size of the file contents.
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
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
2293
new_lines = get_content_byte_lines()
2294
return self._add_text_to_weave(file_id, new_lines,
2295
file_parents.keys())
2297
def modified_link(self, file_id, file_parents, link_target):
2298
"""Record the presence of a symbolic link.
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.
2304
self._add_text_to_weave(file_id, [], file_parents.keys())
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
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()
2324
class RootCommitBuilder(CommitBuilder):
2325
"""This commitbuilder actually records the root id"""
2327
def _check_root(self, ie, parent_invs, tree):
2328
"""Helper for record_entry_contents.
2330
:param ie: An entry being added.
2331
:param parent_invs: The inventories of the parent revisions of the
2333
:param tree: The tree that is being committed.
2335
# ie must be root for this builder
2336
assert ie.parent_id is None
2339
2346
_unescape_map = {