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,
201
'', tree, tree.path_content_summary(''))
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,
210
"""Record the content of ie from tree into the commit if needed.
212
Side effect: sets ie.revision when unchanged
214
:param ie: An inventory entry present in the commit.
215
:param parent_invs: The inventories of the parent revisions of the
217
:param path: The path the entry is at in the tree.
218
:param tree: The tree which contains this entry and should be used to
220
:param content_summary: Summary data from the tree about the paths
221
content - stat, length, exec, sha/link target. This is only
222
accessed when the entry has a revision of None - that is when it is
223
a candidate to commit.
225
if self.new_inventory.root is None:
226
self._check_root(ie, parent_invs, tree)
227
if ie.revision is None:
228
kind = content_summary[0]
230
# ie is carried over from a prior commit
232
# XXX: repository specific check for nested tree support goes here - if
233
# the repo doesn't want nested trees we skip it ?
234
if (kind == 'tree-reference' and
235
not self.repository._format.supports_tree_reference):
236
# mismatch between commit builder logic and repository:
237
# this needs the entry creation pushed down into the builder.
238
raise NotImplementedError
239
# transitional assert only, will remove before release.
240
assert ie.kind == kind
241
self.new_inventory.add(ie)
243
# ie.revision is always None if the InventoryEntry is considered
244
# for committing. ie.snapshot will record the correct revision
245
# which may be the sole parent if it is untouched.
246
if ie.revision is not None:
249
# XXX: Friction: parent_candidates should return a list not a dict
250
# so that we don't have to walk the inventories again.
251
parent_candiate_entries = ie.parent_candidates(parent_invs)
252
head_set = self.repository.get_graph().heads(parent_candiate_entries.keys())
254
for inv in parent_invs:
255
if ie.file_id in inv:
256
old_rev = inv[ie.file_id].revision
257
if old_rev in head_set:
258
heads.append(inv[ie.file_id].revision)
259
head_set.remove(inv[ie.file_id].revision)
262
# now we check to see if we need to write a new record to the
264
# We write a new entry unless there is one head to the ancestors, and
265
# the kind-derived content is unchanged.
267
# Cheapest check first: no ancestors, or more the one head in the
268
# ancestors, we write a new node.
272
# There is a single head, look it up for comparison
273
parent_entry = parent_candiate_entries[heads[0]]
274
# if the non-content specific data has changed, we'll be writing a
276
if (parent_entry.parent_id != ie.parent_id or
277
parent_entry.name != ie.name):
279
# now we need to do content specific checks:
281
# if the kind changed the content obviously has
282
if kind != parent_entry.kind:
286
if (# if the file length changed we have to store:
287
parent_entry.text_size != content_summary[1] or
288
# if the exec bit has changed we have to store:
289
parent_entry.executable != content_summary[2]):
291
elif parent_entry.text_sha1 == content_summary[3]:
292
# all meta and content is unchanged (using a hash cache
293
# hit to check the sha)
294
ie.revision = parent_entry.revision
295
ie.text_size = parent_entry.text_size
296
ie.text_sha1 = parent_entry.text_sha1
297
ie.executable = parent_entry.executable
300
# Either there is only a hash change(no hash cache entry,
301
# or same size content change), or there is no change on
303
# There is a race condition when inserting content into the
304
# knit though that can result in different content being
305
# inserted so even though we may have had a hash cache hit
306
# here we still tell the store the hash we would *not*
307
# store a new text on, which means that it can avoid for us
308
# without a race condition and without double-shaing the
310
nostore_sha = parent_entry.text_sha1
314
ie.executable = content_summary[2]
315
lines = tree.get_file(ie.file_id, path).readlines()
316
ie.text_sha1, ie.text_size = self._add_text_to_weave(
317
ie.file_id, lines, heads, nostore_sha)
318
except errors.ExistingContent:
319
# we are not going to store a new file graph node as it turns
320
# out to be unchanged.
321
ie.revision = parent_entry.revision
322
ie.text_size = parent_entry.text_size
323
ie.text_sha1 = parent_entry.text_sha1
324
ie.executable = parent_entry.executable
326
elif kind == 'directory':
328
# all data is meta here, nothing specific to directory, so
330
ie.revision = parent_entry.revision
333
self._add_text_to_weave(ie.file_id, lines, heads, None)
334
elif kind == 'symlink':
335
current_link_target = content_summary[3]
337
# symmlink target is not generic metadata, check if it has
339
if current_link_target != parent_entry.symlink_target:
342
# unchanged, carry over.
343
ie.revision = parent_entry.revision
344
ie.symlink_target = parent_entry.symlink_target
346
ie.symlink_target = current_link_target
348
self._add_text_to_weave(ie.file_id, lines, heads, None)
349
elif kind == 'tree-reference':
351
if content_summary[3] != parent_entry.reference_revision:
354
# unchanged, carry over.
355
ie.reference_revision = parent_entry.reference_revision
356
ie.revision = parent_entry.revision
358
ie.reference_revision = content_summary[3]
360
self._add_text_to_weave(ie.file_id, lines, heads, None)
362
raise NotImplementedError('unknown kind')
363
ie.revision = self._new_revision_id
365
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
366
versionedfile = self.repository.weave_store.get_weave_or_empty(
367
file_id, self.repository.get_transaction())
368
# Don't change this to add_lines - add_lines_with_ghosts is cheaper
369
# than add_lines, and allows committing when a parent is ghosted for
372
return versionedfile.add_lines_with_ghosts(
373
self._new_revision_id, parents, new_lines,
374
nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
376
versionedfile.clear_cache()
379
class RootCommitBuilder(CommitBuilder):
380
"""This commitbuilder actually records the root id"""
382
def _check_root(self, ie, parent_invs, tree):
383
"""Helper for record_entry_contents.
385
:param ie: An entry being added.
386
:param parent_invs: The inventories of the parent revisions of the
388
:param tree: The tree that is being committed.
390
# ie must be root for this builder
391
assert ie.parent_id is None
64
394
######################################################################
2254
2590
self.pb.update(message, self.count, self.total)
2257
class CommitBuilder(object):
2258
"""Provides an interface to build up a commit.
2260
This allows describing a tree to be committed without needing to
2261
know the internals of the format of the repository.
2264
# all clients should supply tree roots.
2265
record_root_entry = True
2267
def __init__(self, repository, parents, config, timestamp=None,
2268
timezone=None, committer=None, revprops=None,
2270
"""Initiate a CommitBuilder.
2272
:param repository: Repository to commit to.
2273
:param parents: Revision ids of the parents of the new revision.
2274
:param config: Configuration to use.
2275
:param timestamp: Optional timestamp recorded for commit.
2276
:param timezone: Optional timezone for timestamp.
2277
:param committer: Optional committer to set for commit.
2278
:param revprops: Optional dictionary of revision properties.
2279
:param revision_id: Optional revision id.
2281
self._config = config
2283
if committer is None:
2284
self._committer = self._config.username()
2286
assert isinstance(committer, basestring), type(committer)
2287
self._committer = committer
2289
self.new_inventory = Inventory(None)
2290
self._new_revision_id = osutils.safe_revision_id(revision_id)
2291
self.parents = parents
2292
self.repository = repository
2295
if revprops is not None:
2296
self._revprops.update(revprops)
2298
if timestamp is None:
2299
timestamp = time.time()
2300
# Restrict resolution to 1ms
2301
self._timestamp = round(timestamp, 3)
2303
if timezone is None:
2304
self._timezone = osutils.local_time_offset()
2306
self._timezone = int(timezone)
2308
self._generate_revision_if_needed()
2310
def commit(self, message):
2311
"""Make the actual commit.
2313
:return: The revision id of the recorded revision.
2315
rev = _mod_revision.Revision(
2316
timestamp=self._timestamp,
2317
timezone=self._timezone,
2318
committer=self._committer,
2320
inventory_sha1=self.inv_sha1,
2321
revision_id=self._new_revision_id,
2322
properties=self._revprops)
2323
rev.parent_ids = self.parents
2324
self.repository.add_revision(self._new_revision_id, rev,
2325
self.new_inventory, self._config)
2326
self.repository.commit_write_group()
2327
return self._new_revision_id
2330
"""Abort the commit that is being built.
2332
self.repository.abort_write_group()
2334
def revision_tree(self):
2335
"""Return the tree that was just committed.
2337
After calling commit() this can be called to get a RevisionTree
2338
representing the newly committed tree. This is preferred to
2339
calling Repository.revision_tree() because that may require
2340
deserializing the inventory, while we already have a copy in
2343
return RevisionTree(self.repository, self.new_inventory,
2344
self._new_revision_id)
2346
def finish_inventory(self):
2347
"""Tell the builder that the inventory is finished."""
2348
if self.new_inventory.root is None:
2349
symbol_versioning.warn('Root entry should be supplied to'
2350
' record_entry_contents, as of bzr 0.10.',
2351
DeprecationWarning, stacklevel=2)
2352
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2353
self.new_inventory.revision_id = self._new_revision_id
2354
self.inv_sha1 = self.repository.add_inventory(
2355
self._new_revision_id,
2360
def _gen_revision_id(self):
2361
"""Return new revision-id."""
2362
return generate_ids.gen_revision_id(self._config.username(),
2365
def _generate_revision_if_needed(self):
2366
"""Create a revision id if None was supplied.
2368
If the repository can not support user-specified revision ids
2369
they should override this function and raise CannotSetRevisionId
2370
if _new_revision_id is not None.
2372
:raises: CannotSetRevisionId
2374
if self._new_revision_id is None:
2375
self._new_revision_id = self._gen_revision_id()
2376
self.random_revid = True
2378
self.random_revid = False
2380
def _check_root(self, ie, parent_invs, tree):
2381
"""Helper for record_entry_contents.
2383
:param ie: An entry being added.
2384
:param parent_invs: The inventories of the parent revisions of the
2386
:param tree: The tree that is being committed.
2388
if ie.parent_id is not None:
2389
# if ie is not root, add a root automatically.
2390
symbol_versioning.warn('Root entry should be supplied to'
2391
' record_entry_contents, as of bzr 0.10.',
2392
DeprecationWarning, stacklevel=2)
2393
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2394
'', tree, tree.path_content_summary(''))
2396
# In this revision format, root entries have no knit or weave When
2397
# serializing out to disk and back in root.revision is always
2399
ie.revision = self._new_revision_id
2401
def record_entry_contents(self, ie, parent_invs, path, tree,
2403
"""Record the content of ie from tree into the commit if needed.
2405
Side effect: sets ie.revision when unchanged
2407
:param ie: An inventory entry present in the commit.
2408
:param parent_invs: The inventories of the parent revisions of the
2410
:param path: The path the entry is at in the tree.
2411
:param tree: The tree which contains this entry and should be used to
2413
:param content_summary: Summary data from the tree about the paths
2414
content - stat, length, exec, sha/link target. This is only
2415
accessed when the entry has a revision of None - that is when it is
2416
a candidate to commit.
2418
if self.new_inventory.root is None:
2419
self._check_root(ie, parent_invs, tree)
2420
if ie.revision is None:
2421
kind = content_summary[0]
2423
# ie is carried over from a prior commit
2425
# XXX: repository specific check for nested tree support goes here - if
2426
# the repo doesn't want nested trees we skip it ?
2427
if (kind == 'tree-reference' and
2428
not self.repository._format.supports_tree_reference):
2429
# mismatch between commit builder logic and repository:
2430
# this needs the entry creation pushed down into the builder.
2431
raise NotImplementedError
2432
# transitional assert only, will remove before release.
2433
assert ie.kind == kind
2434
self.new_inventory.add(ie)
2436
# ie.revision is always None if the InventoryEntry is considered
2437
# for committing. ie.snapshot will record the correct revision
2438
# which may be the sole parent if it is untouched.
2439
if ie.revision is not None:
2442
# XXX: Friction: parent_candidates should return a list not a dict
2443
# so that we don't have to walk the inventories again.
2444
parent_candiate_entries = ie.parent_candidates(parent_invs)
2445
head_set = self.repository.get_graph().heads(parent_candiate_entries.keys())
2447
for inv in parent_invs:
2448
if ie.file_id in inv:
2449
old_rev = inv[ie.file_id].revision
2450
if old_rev in head_set:
2451
heads.append(inv[ie.file_id].revision)
2452
head_set.remove(inv[ie.file_id].revision)
2455
# now we check to see if we need to write a new record to the
2457
# We write a new entry unless there is one head to the ancestors, and
2458
# the kind-derived content is unchanged.
2460
# Cheapest check first: no ancestors, or more the one head in the
2461
# ancestors, we write a new node.
2465
# There is a single head, look it up for comparison
2466
parent_entry = parent_candiate_entries[heads[0]]
2467
# if the non-content specific data has changed, we'll be writing a
2469
if (parent_entry.parent_id != ie.parent_id or
2470
parent_entry.name != ie.name):
2472
# now we need to do content specific checks:
2474
# if the kind changed the content obviously has
2475
if kind != parent_entry.kind:
2479
if (# if the file length changed we have to store:
2480
parent_entry.text_size != content_summary[1] or
2481
# if the exec bit has changed we have to store:
2482
parent_entry.executable != content_summary[2]):
2484
elif parent_entry.text_sha1 == content_summary[3]:
2485
# all meta and content is unchanged (using a hash cache
2486
# hit to check the sha)
2487
ie.revision = parent_entry.revision
2488
ie.text_size = parent_entry.text_size
2489
ie.text_sha1 = parent_entry.text_sha1
2490
ie.executable = parent_entry.executable
2493
# Either there is only a hash change(no hash cache entry,
2494
# or same size content change), or there is no change on
2496
# There is a race condition when inserting content into the
2497
# knit though that can result in different content being
2498
# inserted so even though we may have had a hash cache hit
2499
# here we still tell the store the hash we would *not*
2500
# store a new text on, which means that it can avoid for us
2501
# without a race condition and without double-shaing the
2503
nostore_sha = parent_entry.text_sha1
2507
ie.executable = content_summary[2]
2508
lines = tree.get_file(ie.file_id, path).readlines()
2509
ie.text_sha1, ie.text_size = self._add_text_to_weave(
2510
ie.file_id, lines, heads, nostore_sha)
2511
except errors.ExistingContent:
2512
# we are not going to store a new file graph node as it turns
2513
# out to be unchanged.
2514
ie.revision = parent_entry.revision
2515
ie.text_size = parent_entry.text_size
2516
ie.text_sha1 = parent_entry.text_sha1
2517
ie.executable = parent_entry.executable
2519
elif kind == 'directory':
2521
# all data is meta here, nothing specific to directory, so
2523
ie.revision = parent_entry.revision
2526
self._add_text_to_weave(ie.file_id, lines, heads, None)
2527
elif kind == 'symlink':
2528
current_link_target = content_summary[3]
2530
# symmlink target is not generic metadata, check if it has
2532
if current_link_target != parent_entry.symlink_target:
2535
# unchanged, carry over.
2536
ie.revision = parent_entry.revision
2537
ie.symlink_target = parent_entry.symlink_target
2539
ie.symlink_target = current_link_target
2541
self._add_text_to_weave(ie.file_id, lines, heads, None)
2542
elif kind == 'tree-reference':
2544
if content_summary[3] != parent_entry.reference_revision:
2547
# unchanged, carry over.
2548
ie.reference_revision = parent_entry.reference_revision
2549
ie.revision = parent_entry.revision
2551
ie.reference_revision = content_summary[3]
2553
self._add_text_to_weave(ie.file_id, lines, heads, None)
2555
raise NotImplementedError('unknown kind')
2556
ie.revision = self._new_revision_id
2558
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
2559
versionedfile = self.repository.weave_store.get_weave_or_empty(
2560
file_id, self.repository.get_transaction())
2561
# Don't change this to add_lines - add_lines_with_ghosts is cheaper
2562
# than add_lines, and allows committing when a parent is ghosted for
2565
return versionedfile.add_lines_with_ghosts(
2566
self._new_revision_id, parents, new_lines,
2567
nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
2569
versionedfile.clear_cache()
2572
class RootCommitBuilder(CommitBuilder):
2573
"""This commitbuilder actually records the root id"""
2575
def _check_root(self, ie, parent_invs, tree):
2576
"""Helper for record_entry_contents.
2578
:param ie: An entry being added.
2579
:param parent_invs: The inventories of the parent revisions of the
2581
:param tree: The tree that is being committed.
2583
# ie must be root for this builder
2584
assert ie.parent_id is None
2587
2593
_unescape_map = {