295
310
self._gather_parents()
296
311
if len(self.parents) > 1 and self.specific_files:
297
312
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
299
314
# Collect the changes
300
self._emit_progress_set_stage("Collecting changes", show_entries=True)
315
self._set_progress_stage("Collecting changes",
316
entries_title="Directory")
301
317
self.builder = self.branch.get_commit_builder(self.parents,
302
318
self.config, timestamp, timezone, committer, revprops, rev_id)
303
self._update_builder_with_changes()
304
self._check_pointless()
306
# TODO: Now the new inventory is known, check for conflicts.
307
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
308
# weave lines, because nothing should be recorded until it is known
309
# that commit will succeed.
310
self._emit_progress_set_stage("Saving data locally")
311
self.builder.finish_inventory()
313
# Prompt the user for a commit message if none provided
314
message = message_callback(self)
315
assert isinstance(message, unicode), type(message)
316
self.message = message
317
self._escape_commit_message()
319
# Add revision data to the local branch
320
self.rev_id = self.builder.commit(self.message)
321
# find the location being committed to
322
if self.bound_branch:
323
master_location = self.master_branch.base
325
master_location = self.branch.base
327
# report the start of the commit
328
self.reporter.started(new_revno, self.rev_id, master_location)
330
self._update_builder_with_changes()
331
self._check_pointless()
333
# TODO: Now the new inventory is known, check for conflicts.
334
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
335
# weave lines, because nothing should be recorded until it is known
336
# that commit will succeed.
337
self._set_progress_stage("Saving data locally")
338
self.builder.finish_inventory()
340
# Prompt the user for a commit message if none provided
341
message = message_callback(self)
342
assert isinstance(message, unicode), type(message)
343
self.message = message
344
self._escape_commit_message()
346
# Add revision data to the local branch
347
self.rev_id = self.builder.commit(self.message)
353
self._process_pre_hooks(old_revno, new_revno)
322
355
# Upload revision data to the master.
323
356
# this will propagate merged revisions too if needed.
324
357
if self.bound_branch:
325
self._emit_progress_set_stage("Uploading data to master branch")
358
self._set_progress_stage("Uploading data to master branch")
326
359
self.master_branch.repository.fetch(self.branch.repository,
327
360
revision_id=self.rev_id)
328
361
# now the master has the revision data
595
657
specific_files = self.specific_files
596
658
mutter("Selecting files for commit with filter %s", specific_files)
597
work_inv = self.work_tree.inventory
598
assert work_inv.root is not None
599
self.pb_entries_total = len(work_inv)
601
660
# Check and warn about old CommitBuilders
602
entries = work_inv.iter_entries()
603
661
if not self.builder.record_root_entry:
604
662
symbol_versioning.warn('CommitBuilders should support recording'
605
663
' the root entry as of bzr 0.10.', DeprecationWarning,
607
665
self.builder.new_inventory.add(self.basis_inv.root.copy())
667
# Build the new inventory
668
self._populate_from_inventory(specific_files)
670
# If specific files are selected, then all un-selected files must be
671
# recorded in their previous state. For more details, see
672
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
674
for path, new_ie in self.basis_inv.iter_entries():
675
if new_ie.file_id in self.builder.new_inventory:
677
if is_inside_any(specific_files, path):
681
self.builder.record_entry_contents(ie, self.parent_invs, path,
684
# Report what was deleted.
685
if self.reporter.is_verbose():
686
for path, ie in self.basis_inv.iter_entries():
687
if ie.file_id not in self.builder.new_inventory:
688
self.reporter.deleted(path)
690
def _populate_from_inventory(self, specific_files):
691
"""Populate the CommitBuilder by walking the working tree inventory."""
693
# raise an exception as soon as we find a single unknown.
694
for unknown in self.work_tree.unknowns():
695
raise StrictCommitFailed()
697
report_changes = self.reporter.is_verbose()
611
699
deleted_paths = set()
612
for path, new_ie in entries:
613
self._emit_progress_next_entry()
614
file_id = new_ie.file_id
700
work_inv = self.work_tree.inventory
701
assert work_inv.root is not None
702
entries = work_inv.iter_entries()
703
if not self.builder.record_root_entry:
705
for path, existing_ie in entries:
706
file_id = existing_ie.file_id
707
name = existing_ie.name
708
parent_id = existing_ie.parent_id
709
kind = existing_ie.kind
710
if kind == 'directory':
711
self._next_progress_entry()
616
713
# Skip files that have been deleted from the working tree.
617
714
# The deleted files/directories are also recorded so they
630
727
kind = self.work_tree.kind(file_id)
728
# TODO: specific_files filtering before nested tree processing
631
729
if kind == 'tree-reference' and self.recursive == 'down':
632
# nested tree: commit in it
633
sub_tree = WorkingTree.open(self.work_tree.abspath(path))
634
# FIXME: be more comprehensive here:
635
# this works when both trees are in --trees repository,
636
# but when both are bound to a different repository,
637
# it fails; a better way of approaching this is to
638
# finally implement the explicit-caches approach design
639
# a while back - RBC 20070306.
640
if (sub_tree.branch.repository.bzrdir.root_transport.base
642
self.work_tree.branch.repository.bzrdir.root_transport.base):
643
sub_tree.branch.repository = \
644
self.work_tree.branch.repository
646
sub_tree.commit(message=None, revprops=self.revprops,
647
recursive=self.recursive,
648
message_callback=self.message_callback,
649
timestamp=self.timestamp, timezone=self.timezone,
650
committer=self.committer,
651
allow_pointless=self.allow_pointless,
652
strict=self.strict, verbose=self.verbose,
653
local=self.local, reporter=self.reporter)
654
except errors.PointlessCommit:
656
if kind != new_ie.kind:
657
new_ie = inventory.make_entry(kind, new_ie.name,
658
new_ie.parent_id, file_id)
730
self._commit_nested_tree(file_id, path)
659
731
except errors.NoSuchFile:
661
# mutter('check %s {%s}', path, file_id)
662
if (not specific_files or
663
is_inside_or_parent_of_any(specific_files, path)):
664
# mutter('%s selected for commit', path)
734
# Record an entry for this item
735
# Note: I don't particularly want to have the existing_ie
736
# parameter but the test suite currently (28-Jun-07) breaks
737
# without it thanks to a unicode normalisation issue. :-(
738
definitely_changed = kind != existing_ie.kind
739
self._record_entry(path, file_id, specific_files, kind, name,
740
parent_id, definitely_changed, existing_ie, report_changes)
742
# Unversion IDs that were found to be deleted
743
self.work_tree.unversion(deleted_ids)
745
def _commit_nested_tree(self, file_id, path):
746
"Commit a nested tree."
747
sub_tree = self.work_tree.get_nested_tree(file_id, path)
748
# FIXME: be more comprehensive here:
749
# this works when both trees are in --trees repository,
750
# but when both are bound to a different repository,
751
# it fails; a better way of approaching this is to
752
# finally implement the explicit-caches approach design
753
# a while back - RBC 20070306.
754
if sub_tree.branch.repository.has_same_location(
755
self.work_tree.branch.repository):
756
sub_tree.branch.repository = \
757
self.work_tree.branch.repository
759
sub_tree.commit(message=None, revprops=self.revprops,
760
recursive=self.recursive,
761
message_callback=self.message_callback,
762
timestamp=self.timestamp, timezone=self.timezone,
763
committer=self.committer,
764
allow_pointless=self.allow_pointless,
765
strict=self.strict, verbose=self.verbose,
766
local=self.local, reporter=self.reporter)
767
except errors.PointlessCommit:
770
def _record_entry(self, path, file_id, specific_files, kind, name,
771
parent_id, definitely_changed, existing_ie=None,
772
report_changes=True):
773
"Record the new inventory entry for a path if any."
774
# mutter('check %s {%s}', path, file_id)
775
if (not specific_files or
776
is_inside_or_parent_of_any(specific_files, path)):
777
# mutter('%s selected for commit', path)
778
if definitely_changed or existing_ie is None:
779
ie = inventory.make_entry(kind, name, parent_id, file_id)
781
ie = existing_ie.copy()
666
782
ie.revision = None
784
# mutter('%s not selected for commit', path)
785
if self.basis_inv.has_id(file_id):
786
ie = self.basis_inv[file_id].copy()
668
# mutter('%s not selected for commit', path)
669
if self.basis_inv.has_id(file_id):
670
ie = self.basis_inv[file_id].copy()
672
# this entry is new and not being committed
788
# this entry is new and not being committed
674
791
self.builder.record_entry_contents(ie, self.parent_invs,
675
792
path, self.work_tree)
676
# describe the nature of the change that has occurred relative to
677
# the basis inventory.
678
if (self.basis_inv.has_id(ie.file_id)):
679
basis_ie = self.basis_inv[ie.file_id]
682
change = ie.describe_change(basis_ie, ie)
683
if change in (InventoryEntry.RENAMED,
684
InventoryEntry.MODIFIED_AND_RENAMED):
685
old_path = self.basis_inv.id2path(ie.file_id)
686
self.reporter.renamed(change, old_path, path)
688
self.reporter.snapshot_change(change, path)
690
# Unversion IDs that were found to be deleted
691
self.work_tree.unversion(deleted_ids)
693
# If specific files/directories were nominated, it is possible
694
# that some data from outside those needs to be preserved from
695
# the basis tree. For example, if a file x is moved from out of
696
# directory foo into directory bar and the user requests
697
# ``commit foo``, then information about bar/x must also be
700
for path, new_ie in self.basis_inv.iter_entries():
701
if new_ie.file_id in work_inv:
703
if is_inside_any(specific_files, path):
707
self.builder.record_entry_contents(ie, self.parent_invs, path,
710
# Report what was deleted. We could skip this when no deletes are
711
# detected to gain a performance win, but it arguably serves as a
712
# 'safety check' by informing the user whenever anything disappears.
713
for path, ie in self.basis_inv.iter_entries():
714
if ie.file_id not in self.builder.new_inventory:
715
self.reporter.deleted(path)
717
def _emit_progress_set_stage(self, name, show_entries=False):
794
self._report_change(ie, path)
797
def _report_change(self, ie, path):
798
"""Report a change to the user.
800
The change that has occurred is described relative to the basis
803
if (self.basis_inv.has_id(ie.file_id)):
804
basis_ie = self.basis_inv[ie.file_id]
807
change = ie.describe_change(basis_ie, ie)
808
if change in (InventoryEntry.RENAMED,
809
InventoryEntry.MODIFIED_AND_RENAMED):
810
old_path = self.basis_inv.id2path(ie.file_id)
811
self.reporter.renamed(change, old_path, path)
813
self.reporter.snapshot_change(change, path)
815
def _set_progress_stage(self, name, entries_title=None):
718
816
"""Set the progress stage and emit an update to the progress bar."""
719
817
self.pb_stage_name = name
720
818
self.pb_stage_count += 1
721
self.pb_entries_show = show_entries
819
self.pb_entries_title = entries_title
820
if entries_title is not None:
723
821
self.pb_entries_count = 0
724
822
self.pb_entries_total = '?'
725
823
self._emit_progress()
727
def _emit_progress_next_entry(self):
728
"""Emit an update to the progress bar and increment the file count."""
825
def _next_progress_entry(self):
826
"""Emit an update to the progress bar and increment the entry count."""
729
827
self.pb_entries_count += 1
730
828
self._emit_progress()
732
830
def _emit_progress(self):
733
if self.pb_entries_show:
734
text = "%s [Entry %d/%s] - Stage" % (self.pb_stage_name,
735
self.pb_entries_count,str(self.pb_entries_total))
831
if self.pb_entries_title:
832
if self.pb_entries_total == '?':
833
text = "%s [%s %d] - Stage" % (self.pb_stage_name,
834
self.pb_entries_title, self.pb_entries_count)
836
text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
837
self.pb_entries_title, self.pb_entries_count,
838
str(self.pb_entries_total))
737
840
text = "%s - Stage" % (self.pb_stage_name)
738
841
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)