358
357
for tree in self.parent_trees:
359
358
if file_id in tree.inventory:
360
359
ie = tree.inventory[file_id]
361
assert ie.kind == 'file'
362
360
assert ie.file_id == file_id
363
if ie.text_version in r:
364
assert r[ie.text_version] == ie
362
assert r[ie.revision] == ie
366
r[ie.text_version] = ie
370
def _set_revisions(self):
371
"""Pass over inventory and mark new revisions as needed.
367
def _snapshot_entry(self, path, ie, previous_entries):
368
"""Store a single possibly changed inventory entry in the branch."""
369
mutter('parents of %s are %r', path, previous_entries)
370
if ie.revision is not None:
371
# not selected for commit
373
if ie.kind == 'symlink':
374
ie.read_symlink_target(self.branch.abspath(path))
375
if len(previous_entries) == 1:
376
# cannot be unchanged unless there is only one parent file rev.
377
parent_ie = previous_entries.values()[0]
378
unchanged = ie.unchanged(parent_ie, self.work_tree)
380
mutter("found unchanged entry")
381
ie.revision = parent_ie.revision
382
self.report_entry_status(previous_entries, path, ie)
384
mutter('new revision for {%s}', ie.file_id)
385
ie.revision = self.rev_id
386
if ie.kind != 'file':
387
self.report_entry_status(previous_entries, path, ie)
389
# file is either new, or a file merge; need to record
391
self.report_entry_status(previous_entries, path, ie)
393
self._commit_file(ie, previous_entries)
395
def report_entry_status(self, previous_entries, path, ie):
396
if len(previous_entries) > 1:
397
note('merged %s', path)
398
elif len(previous_entries) == 0:
399
note('added %s', path)
400
elif ie.revision == self.rev_id:
401
note('modified/renamed/reparented%s', path)
403
note('unchanged %s', path)
405
def _store_snapshot(self):
406
"""Pass over inventory and record a snapshot.
373
408
Entries get a new revision when they are modified in
374
409
any way, which includes a merge with a new set of
375
410
parents that have the same entry. Currently we do not
376
411
check for that set being ancestors of each other - and
377
412
we should - only parallel children should count for this
378
test. I.e. if we are merging in revision FOO, and our
413
test see find_entry_parents to correct this. FIXME <---
414
I.e. if we are merging in revision FOO, and our
379
415
copy of file id BAR is identical to FOO.BAR, we should
380
416
generate a new revision of BAR IF and only IF FOO is
381
417
neither a child of our current tip, nor an ancestor of
387
423
# made a specific decision on a particular value -- c.f.
389
425
for path, ie in self.new_inv.iter_entries():
390
compatible_priors = set()
392
for previous_tree in self.parent_trees:
394
previous_inv = previous_tree.inventory
395
if file_id not in previous_inv:
397
previous_ie = previous_inv[file_id]
398
if ie.compatible_for_commit(previous_ie):
399
mutter("found compatible previous entry")
400
compatible_priors.add(previous_ie.revision)
401
if len(compatible_priors) != 1:
402
mutter('new revision for {%s}', file_id)
403
ie.revision = self.rev_id
405
ie.revision = compatible_priors.pop()
406
mutter('revision for {%s} inherited as {%s}',
407
file_id, ie.revision)
409
def _store_entries(self):
410
"""Build revision inventory and store modified files.
412
This is called with new_inv a new empty inventory. Depending on
413
which files are selected for commit, and which ones have
414
been modified or merged, new inventory entries are built
415
based on the working and parent inventories.
417
As a side-effect this stores new text versions for committed
418
files with text changes or merges.
420
Each entry can have one of several things happen:
422
carry_file -- carried from the previous version (if not
425
commit_nonfile -- no text to worry about
427
commit_old_text -- same text, may have moved
429
commit_file -- new text version
426
previous_entries = self._find_entry_parents(ie. file_id)
427
self._snapshot_entry(path, ie, previous_entries)
429
def _populate_new_inv(self):
430
"""Build revision inventory.
432
This creates a new empty inventory. Depending on
433
which files are selected for commit, and what is present in the
434
current tree, the new inventory is populated. inventory entries
435
which are candidates for modification have their revision set to
436
None; inventory entries that are carried over untouched have their
437
revision set to their prior value.
439
mutter("Selecting files for commit with filter %s", self.specific_files)
440
self.new_inv = Inventory()
431
441
for path, new_ie in self.work_inv.iter_entries():
432
442
file_id = new_ie.file_id
433
443
mutter('check %s {%s}', path, new_ie.file_id)
436
446
mutter('%s not selected for commit', path)
437
447
self._carry_file(file_id)
439
if new_ie.kind == 'symlink':
440
new_ie.read_symlink_target(self.branch.abspath(path))
441
if new_ie.kind != 'file':
442
self._commit_nonfile(file_id)
445
file_parents = self._find_file_parents(file_id)
446
mutter('parents of %s are %r', path, file_parents)
447
if len(file_parents) == 1:
448
parent_ie = file_parents.values()[0]
449
wc_sha1 = self.work_tree.get_file_sha1(file_id)
450
if parent_ie.text_sha1 == wc_sha1:
451
# text not changed or merged
452
self._commit_old_text(file_id, parent_ie)
454
# file is either new, or a file merge; need to record
456
if len(file_parents) > 1:
457
note('merged %s', path)
458
elif len(file_parents) == 0:
459
note('added %s', path)
461
note('modified %s', path)
462
self._commit_file(new_ie, file_id, file_parents)
465
def _commit_nonfile(self, file_id):
466
self.new_inv.add(self.work_inv[file_id].copy())
449
mutter('%s selected for commit', path)
469
454
def _carry_file(self, file_id):
470
455
"""Carry the file unchanged from the basis revision."""
471
456
if self.basis_inv.has_id(file_id):
472
457
self.new_inv.add(self.basis_inv[file_id].copy())
475
def _commit_old_text(self, file_id, parent_ie):
476
"""Keep the same text as last time, but possibly a different name."""
477
ie = self.work_inv[file_id].copy()
478
ie.text_version = parent_ie.text_version
479
ie.text_size = parent_ie.text_size
480
ie.text_sha1 = parent_ie.text_sha1
484
459
def _report_deletes(self):
485
460
for file_id in self.basis_inv:
486
461
if file_id not in self.new_inv:
487
462
note('deleted %s', self.basis_inv.id2path(file_id))
490
def _commit_file(self, new_ie, file_id, file_parents):
491
mutter('store new text for {%s} in revision {%s}',
492
file_id, self.rev_id)
493
new_lines = self.work_tree.get_file(file_id).readlines()
494
self._add_text_to_weave(file_id, new_lines, file_parents)
495
new_ie.text_version = self.rev_id
496
new_ie.text_sha1 = sha_strings(new_lines)
497
new_ie.text_size = sum(map(len, new_lines))
498
self.new_inv.add(new_ie)
464
def _commit_file(self, new_ie, file_parents):
465
mutter('storing file {%s} in revision {%s}',
466
new_ie.file_id, new_ie.revision)
467
# special case to avoid diffing on renames or
469
if (len(file_parents) == 1
470
and new_ie.text_sha1 == file_parents.values()[0].text_sha1
471
and new_ie.text_size == file_parents.values()[0].text_size):
472
previous_ie = file_parents.values()[0]
473
self.weave_store.add_identical_text(
474
new_ie.file_id, previous_ie.revision,
475
new_ie.revision, file_parents)
477
new_lines = self.work_tree.get_file(new_ie.file_id).readlines()
478
self._add_text_to_weave(new_ie.file_id, new_lines, file_parents)
479
new_ie.text_sha1 = sha_strings(new_lines)
480
new_ie.text_size = sum(map(len, new_lines))
501
482
def _add_text_to_weave(self, file_id, new_lines, parents):
502
483
self.weave_store.add_text(file_id, self.rev_id, new_lines, parents)
505
485
def _gen_revision_id(branch, when):
506
486
"""Return new revision-id."""
507
487
s = '%s-%s-' % (user_email(branch), compact_date(when))