218
225
:param path: The path the entry is at in the tree.
219
226
:param tree: The tree which contains this entry and should be used to
221
:return: True if a new version of the entry has been recorded.
222
(Committing a merge where a file was only changed on the other side
223
will not return True.)
228
:param content_summary: Summary data from the tree about the paths
229
content - stat, length, exec, sha/link target. This is only
230
accessed when the entry has a revision of None - that is when it is
231
a candidate to commit.
232
:return: A tuple (change_delta, version_recorded). change_delta is
233
an inventory_delta change for this entry against the basis tree of
234
the commit, or None if no change occured against the basis tree.
235
version_recorded is True if a new version of the entry has been
236
recorded. For instance, committing a merge where a file was only
237
changed on the other side will return (delta, False).
225
239
if self.new_inventory.root is None:
240
if ie.parent_id is not None:
241
raise errors.RootMissing()
226
242
self._check_root(ie, parent_invs, tree)
243
if ie.revision is None:
244
kind = content_summary[0]
246
# ie is carried over from a prior commit
248
# XXX: repository specific check for nested tree support goes here - if
249
# the repo doesn't want nested trees we skip it ?
250
if (kind == 'tree-reference' and
251
not self.repository._format.supports_tree_reference):
252
# mismatch between commit builder logic and repository:
253
# this needs the entry creation pushed down into the builder.
254
raise NotImplementedError('Missing repository subtree support.')
255
# transitional assert only, will remove before release.
256
assert ie.kind == kind
227
257
self.new_inventory.add(ie)
259
# TODO: slow, take it out of the inner loop.
261
basis_inv = parent_invs[0]
263
basis_inv = Inventory(root_id=None)
229
265
# ie.revision is always None if the InventoryEntry is considered
230
# for committing. ie.snapshot will record the correct revision
231
# which may be the sole parent if it is untouched.
266
# for committing. We may record the previous parents revision if the
267
# content is actually unchanged against a sole head.
232
268
if ie.revision is not None:
233
return ie.revision == self._new_revision_id and (path != '' or
269
if self._versioned_root or path != '':
270
# not considered for commit
273
# repositories that do not version the root set the root's
274
# revision to the new commit even when no change occurs, and
275
# this masks when a change may have occurred against the basis,
276
# so calculate if one happened.
277
if ie.file_id not in basis_inv:
279
delta = (None, path, ie.file_id, ie)
281
basis_id = basis_inv[ie.file_id]
282
if basis_id.name != '':
284
delta = (basis_inv.id2path(ie.file_id), path,
289
# not considered for commit, OR, for non-rich-root
290
return delta, ie.revision == self._new_revision_id and (path != '' or
234
291
self._versioned_root)
293
# XXX: Friction: parent_candidates should return a list not a dict
294
# so that we don't have to walk the inventories again.
236
295
parent_candiate_entries = ie.parent_candidates(parent_invs)
237
heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
238
# XXX: Note that this is unordered - and this is tolerable because
239
# the previous code was also unordered.
240
previous_entries = dict((head, parent_candiate_entries[head]) for head
242
# we are creating a new revision for ie in the history store and
244
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
245
return ie.revision == self._new_revision_id and (path != '' or
246
self._versioned_root)
248
def modified_directory(self, file_id, file_parents):
249
"""Record the presence of a symbolic link.
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_reference(self, file_id, file_parents):
257
"""Record the modification of a reference.
259
:param file_id: The file_id of the link to record.
260
:param file_parents: The per-file parent revision ids.
262
self._add_text_to_weave(file_id, [], file_parents.keys())
264
def modified_file_text(self, file_id, file_parents,
265
get_content_byte_lines, text_sha1=None,
267
"""Record the text of file file_id
269
:param file_id: The file_id of the file to record the text of.
270
:param file_parents: The per-file parent revision ids.
271
:param get_content_byte_lines: A callable which will return the byte
273
:param text_sha1: Optional SHA1 of the file contents.
274
:param text_size: Optional size of the file contents.
276
# mutter('storing text of file {%s} in revision {%s} into %r',
277
# file_id, self._new_revision_id, self.repository.weave_store)
278
# special case to avoid diffing on renames or
280
if (len(file_parents) == 1
281
and text_sha1 == file_parents.values()[0].text_sha1
282
and text_size == file_parents.values()[0].text_size):
283
previous_ie = file_parents.values()[0]
284
versionedfile = self.repository.weave_store.get_weave(file_id,
285
self.repository.get_transaction())
286
versionedfile.clone_text(self._new_revision_id,
287
previous_ie.revision, file_parents.keys())
288
return text_sha1, text_size
296
head_set = self._repo_graph.heads(parent_candiate_entries.keys())
298
for inv in parent_invs:
299
if ie.file_id in inv:
300
old_rev = inv[ie.file_id].revision
301
if old_rev in head_set:
302
heads.append(inv[ie.file_id].revision)
303
head_set.remove(inv[ie.file_id].revision)
306
# now we check to see if we need to write a new record to the
308
# We write a new entry unless there is one head to the ancestors, and
309
# the kind-derived content is unchanged.
311
# Cheapest check first: no ancestors, or more the one head in the
312
# ancestors, we write a new node.
316
# There is a single head, look it up for comparison
317
parent_entry = parent_candiate_entries[heads[0]]
318
# if the non-content specific data has changed, we'll be writing a
320
if (parent_entry.parent_id != ie.parent_id or
321
parent_entry.name != ie.name):
323
# now we need to do content specific checks:
325
# if the kind changed the content obviously has
326
if kind != parent_entry.kind:
330
if (# if the file length changed we have to store:
331
parent_entry.text_size != content_summary[1] or
332
# if the exec bit has changed we have to store:
333
parent_entry.executable != content_summary[2]):
335
elif parent_entry.text_sha1 == content_summary[3]:
336
# all meta and content is unchanged (using a hash cache
337
# hit to check the sha)
338
ie.revision = parent_entry.revision
339
ie.text_size = parent_entry.text_size
340
ie.text_sha1 = parent_entry.text_sha1
341
ie.executable = parent_entry.executable
342
return self._get_delta(ie, basis_inv, path), False
344
# Either there is only a hash change(no hash cache entry,
345
# or same size content change), or there is no change on
347
# Provide the parent's hash to the store layer, so that the
348
# content is unchanged we will not store a new node.
349
nostore_sha = parent_entry.text_sha1
351
# We want to record a new node regardless of the presence or
352
# absence of a content change in the file.
354
ie.executable = content_summary[2]
355
lines = tree.get_file(ie.file_id, path).readlines()
357
ie.text_sha1, ie.text_size = self._add_text_to_weave(
358
ie.file_id, lines, heads, nostore_sha)
359
except errors.ExistingContent:
360
# Turns out that the file content was unchanged, and we were
361
# only going to store a new node if it was changed. Carry over
363
ie.revision = parent_entry.revision
364
ie.text_size = parent_entry.text_size
365
ie.text_sha1 = parent_entry.text_sha1
366
ie.executable = parent_entry.executable
367
return self._get_delta(ie, basis_inv, path), False
368
elif kind == 'directory':
370
# all data is meta here, nothing specific to directory, so
372
ie.revision = parent_entry.revision
373
return self._get_delta(ie, basis_inv, path), False
375
self._add_text_to_weave(ie.file_id, lines, heads, None)
376
elif kind == 'symlink':
377
current_link_target = content_summary[3]
379
# symlink target is not generic metadata, check if it has
381
if current_link_target != parent_entry.symlink_target:
384
# unchanged, carry over.
385
ie.revision = parent_entry.revision
386
ie.symlink_target = parent_entry.symlink_target
387
return self._get_delta(ie, basis_inv, path), False
388
ie.symlink_target = current_link_target
390
self._add_text_to_weave(ie.file_id, lines, heads, None)
391
elif kind == 'tree-reference':
393
if content_summary[3] != parent_entry.reference_revision:
396
# unchanged, carry over.
397
ie.reference_revision = parent_entry.reference_revision
398
ie.revision = parent_entry.revision
399
return self._get_delta(ie, basis_inv, path), False
400
ie.reference_revision = content_summary[3]
402
self._add_text_to_weave(ie.file_id, lines, heads, None)
290
new_lines = get_content_byte_lines()
291
return self._add_text_to_weave(file_id, new_lines,
294
def modified_link(self, file_id, file_parents, link_target):
295
"""Record the presence of a symbolic link.
297
:param file_id: The file_id of the link to record.
298
:param file_parents: The per-file parent revision ids.
299
:param link_target: Target location of this link.
301
self._add_text_to_weave(file_id, [], file_parents.keys())
303
def _add_text_to_weave(self, file_id, new_lines, parents):
404
raise NotImplementedError('unknown kind')
405
ie.revision = self._new_revision_id
406
return self._get_delta(ie, basis_inv, path), True
408
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
304
409
versionedfile = self.repository.weave_store.get_weave_or_empty(
305
410
file_id, self.repository.get_transaction())
306
411
# Don't change this to add_lines - add_lines_with_ghosts is cheaper