72
from bzrlib.osutils import (get_user_encoding,
73
kind_marker, isdir,isfile, is_inside_any,
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
74
72
is_inside_or_parent_of_any,
75
73
minimum_path_selection,
76
74
quotefn, sha_file, split_lines,
79
77
from bzrlib.testament import Testament
80
78
from bzrlib.trace import mutter, note, warning, is_quiet
81
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
79
from bzrlib.xml5 import serializer_v5
80
from bzrlib.inventory import InventoryEntry, make_entry
82
81
from bzrlib import symbol_versioning
83
82
from bzrlib.symbol_versioning import (deprecated_passed,
84
83
deprecated_function,
337
329
self.pb.show_count = True
338
330
self.pb.show_bar = True
332
# After a merge, a selected file commit is not supported.
333
# See 'bzr help merge' for an explanation as to why.
340
334
self.basis_inv = self.basis_tree.inventory
341
335
self._gather_parents()
342
# After a merge, a selected file commit is not supported.
343
# See 'bzr help merge' for an explanation as to why.
344
336
if len(self.parents) > 1 and self.specific_files:
345
337
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
346
# Excludes are a form of selected file commit.
347
if len(self.parents) > 1 and self.exclude:
348
raise errors.CannotCommitSelectedFileMerge(self.exclude)
350
339
# Collect the changes
351
340
self._set_progress_stage("Collecting changes",
352
341
entries_title="Directory")
353
342
self.builder = self.branch.get_commit_builder(self.parents,
354
343
self.config, timestamp, timezone, committer, revprops, rev_id)
357
self.builder.will_record_deletes()
358
346
# find the location being committed to
359
347
if self.bound_branch:
360
348
master_location = self.master_branch.base
378
366
# Prompt the user for a commit message if none provided
379
367
message = message_callback(self)
368
assert isinstance(message, unicode), type(message)
380
369
self.message = message
381
370
self._escape_commit_message()
383
372
# Add revision data to the local branch
384
373
self.rev_id = self.builder.commit(self.message)
387
mutter("aborting commit write group because of exception:")
388
trace.log_exception_quietly()
389
note("aborting commit write group: %r" % (e,))
390
376
self.builder.abort()
395
381
# Upload revision data to the master.
396
382
# this will propagate merged revisions too if needed.
397
383
if self.bound_branch:
398
if not self.master_branch.repository.has_same_location(
399
self.branch.repository):
400
self._set_progress_stage("Uploading data to master branch")
401
self.master_branch.repository.fetch(self.branch.repository,
402
revision_id=self.rev_id)
384
self._set_progress_stage("Uploading data to master branch")
385
self.master_branch.repository.fetch(self.branch.repository,
386
revision_id=self.rev_id)
403
387
# now the master has the revision data
404
388
# 'commit' to the master first so a timeout here causes the
405
389
# local branch to be out of date
664
647
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
665
648
# ADHB 11-07-2006
667
exclude = self.exclude
668
specific_files = self.specific_files or []
650
specific_files = self.specific_files
669
651
mutter("Selecting files for commit with filter %s", specific_files)
671
653
# Build the new inventory
672
self._populate_from_inventory()
654
self._populate_from_inventory(specific_files)
674
656
# If specific files are selected, then all un-selected files must be
675
657
# recorded in their previous state. For more details, see
676
658
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
677
if specific_files or exclude:
678
660
for path, old_ie in self.basis_inv.iter_entries():
679
661
if old_ie.file_id in self.builder.new_inventory:
680
662
# already added - skip.
682
if (is_inside_any(specific_files, path)
683
and not is_inside_any(exclude, path)):
684
# was inside the selected path, and not excluded - if not
685
# present it has been deleted so skip.
664
if is_inside_any(specific_files, path):
665
# was inside the selected path, if not present it has been
687
# From here down it was either not selected, or was excluded:
688
668
if old_ie.kind == 'directory':
689
669
self._next_progress_entry()
690
# We preserve the entry unaltered.
670
# not in final inv yet, was not in the selected files, so is an
671
# entry to be preserved unaltered.
691
672
ie = old_ie.copy()
692
673
# Note: specific file commits after a merge are currently
693
674
# prohibited. This test is for sanity/safety in case it's
694
675
# required after that changes.
695
676
if len(self.parents) > 1:
696
677
ie.revision = None
697
_, version_recorded, _ = self.builder.record_entry_contents(
678
delta, version_recorded = self.builder.record_entry_contents(
698
679
ie, self.parent_invs, path, self.basis_tree, None)
699
680
if version_recorded:
700
681
self.any_entries_changed = True
682
if delta: self._basis_delta.append(delta)
702
684
def _report_and_accumulate_deletes(self):
703
685
# XXX: Could the list of deleted paths and ids be instead taken from
704
686
# _populate_from_inventory?
705
if (isinstance(self.basis_inv, Inventory)
706
and isinstance(self.builder.new_inventory, Inventory)):
707
# the older Inventory classes provide a _byid dict, and building a
708
# set from the keys of this dict is substantially faster than even
709
# getting a set of ids from the inventory
711
# <lifeless> set(dict) is roughly the same speed as
712
# set(iter(dict)) and both are significantly slower than
714
deleted_ids = set(self.basis_inv._byid.keys()) - \
715
set(self.builder.new_inventory._byid.keys())
717
deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
687
deleted_ids = set(self.basis_inv._byid.keys()) - \
688
set(self.builder.new_inventory._byid.keys())
719
690
self.any_entries_deleted = True
720
691
deleted = [(self.basis_tree.id2path(file_id), file_id)
723
694
# XXX: this is not quite directory-order sorting
724
695
for path, file_id in deleted:
725
self.builder.record_delete(path, file_id)
696
self._basis_delta.append((path, None, file_id, None))
726
697
self.reporter.deleted(path)
728
def _populate_from_inventory(self):
699
def _populate_from_inventory(self, specific_files):
729
700
"""Populate the CommitBuilder by walking the working tree inventory."""
731
702
# raise an exception as soon as we find a single unknown.
732
703
for unknown in self.work_tree.unknowns():
733
704
raise StrictCommitFailed()
735
specific_files = self.specific_files
736
exclude = self.exclude
737
706
report_changes = self.reporter.is_verbose()
739
708
# A tree of paths that have been deleted. E.g. if foo/bar has been
742
711
# XXX: Note that entries may have the wrong kind because the entry does
743
712
# not reflect the status on disk.
744
713
work_inv = self.work_tree.inventory
745
# NB: entries will include entries within the excluded ids/paths
746
# because iter_entries_by_dir has no 'exclude' facility today.
747
714
entries = work_inv.iter_entries_by_dir(
748
715
specific_file_ids=self.specific_file_ids, yield_parents=True)
749
716
for path, existing_ie in entries:
771
738
if deleted_dict is not None:
772
739
# the path has a deleted parent, do not add it.
774
if exclude and is_inside_any(exclude, path):
775
# Skip excluded paths. Excluded paths are processed by
776
# _update_builder_with_changes.
778
741
content_summary = self.work_tree.path_content_summary(path)
779
742
# Note that when a filter of specific files is given, we must only
780
743
# skip/record deleted files matching that filter.
858
821
ie = existing_ie.copy()
859
822
ie.revision = None
860
# For carried over entries we don't care about the fs hash - the repo
861
# isn't generating a sha, so we're not saving computation time.
862
_, version_recorded, fs_hash = self.builder.record_entry_contents(
863
ie, self.parent_invs, path, self.work_tree, content_summary)
823
delta, version_recorded = self.builder.record_entry_contents(ie,
824
self.parent_invs, path, self.work_tree, content_summary)
826
self._basis_delta.append(delta)
864
827
if version_recorded:
865
828
self.any_entries_changed = True
866
829
if report_changes:
867
830
self._report_change(ie, path)
869
self.work_tree._observed_sha1(ie.file_id, path, fs_hash)
872
833
def _report_change(self, ie, path):