71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
72
from bzrlib.osutils import (get_user_encoding,
73
kind_marker, isdir,isfile, is_inside_any,
72
74
is_inside_or_parent_of_any,
73
75
minimum_path_selection,
74
76
quotefn, sha_file, split_lines,
77
79
from bzrlib.testament import Testament
78
80
from bzrlib.trace import mutter, note, warning, is_quiet
79
from bzrlib.xml5 import serializer_v5
80
from bzrlib.inventory import InventoryEntry, make_entry
81
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
81
82
from bzrlib import symbol_versioning
82
83
from bzrlib.symbol_versioning import (deprecated_passed,
83
84
deprecated_function,
329
337
self.pb.show_count = True
330
338
self.pb.show_bar = True
340
self.basis_inv = self.basis_tree.inventory
341
self._gather_parents()
332
342
# After a merge, a selected file commit is not supported.
333
343
# See 'bzr help merge' for an explanation as to why.
334
self.basis_inv = self.basis_tree.inventory
335
self._gather_parents()
336
344
if len(self.parents) > 1 and self.specific_files:
337
345
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)
339
350
# Collect the changes
340
351
self._set_progress_stage("Collecting changes",
341
352
entries_title="Directory")
342
353
self.builder = self.branch.get_commit_builder(self.parents,
343
354
self.config, timestamp, timezone, committer, revprops, rev_id)
357
self.builder.will_record_deletes()
346
358
# find the location being committed to
347
359
if self.bound_branch:
348
360
master_location = self.master_branch.base
380
395
# Upload revision data to the master.
381
396
# this will propagate merged revisions too if needed.
382
397
if self.bound_branch:
383
if not self.master_branch.repository.has_same_location(
384
self.branch.repository):
385
self._set_progress_stage("Uploading data to master branch")
386
self.master_branch.repository.fetch(self.branch.repository,
387
revision_id=self.rev_id)
388
# now the master has the revision data
398
self._set_progress_stage("Uploading data to master branch")
389
399
# 'commit' to the master first so a timeout here causes the
390
400
# local branch to be out of date
391
self.master_branch.set_last_revision_info(new_revno,
401
self.master_branch.import_last_revision_info(
402
self.branch.repository, new_revno, self.rev_id)
394
404
# and now do the commit locally.
395
405
self.branch.set_last_revision_info(new_revno, self.rev_id)
426
436
# If length == 1, then we only have the root entry. Which means
427
437
# that there is no real difference (only the root could be different)
428
438
# unless deletes occured, in which case the length is irrelevant.
429
if (self.any_entries_deleted or
439
if (self.any_entries_deleted or
430
440
(len(self.builder.new_inventory) != 1 and
431
441
self.any_entries_changed)):
433
443
raise PointlessCommit()
435
def _check_bound_branch(self):
445
def _check_bound_branch(self, possible_master_transports=None):
436
446
"""Check to see if the local branch is bound.
438
448
If it is bound, then most of the commit will actually be
579
590
# typically this will be useful enough.
580
591
except Exception, e:
581
592
found_exception = e
582
if found_exception is not None:
593
if found_exception is not None:
583
594
# don't do a plan raise, because the last exception may have been
584
595
# trashed, e is our sure-to-work exception even though it loses the
585
596
# full traceback. XXX: RBC 20060421 perhaps we could check the
586
# exc_info and if its the same one do a plain raise otherwise
597
# exc_info and if its the same one do a plain raise otherwise
587
598
# 'raise e' as we do now.
648
659
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
649
660
# ADHB 11-07-2006
651
specific_files = self.specific_files
662
exclude = self.exclude
663
specific_files = self.specific_files or []
652
664
mutter("Selecting files for commit with filter %s", specific_files)
654
666
# Build the new inventory
655
self._populate_from_inventory(specific_files)
667
self._populate_from_inventory()
657
669
# If specific files are selected, then all un-selected files must be
658
670
# recorded in their previous state. For more details, see
659
671
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
672
if specific_files or exclude:
661
673
for path, old_ie in self.basis_inv.iter_entries():
662
674
if old_ie.file_id in self.builder.new_inventory:
663
675
# already added - skip.
665
if is_inside_any(specific_files, path):
666
# was inside the selected path, if not present it has been
677
if (is_inside_any(specific_files, path)
678
and not is_inside_any(exclude, path)):
679
# was inside the selected path, and not excluded - if not
680
# present it has been deleted so skip.
682
# From here down it was either not selected, or was excluded:
669
683
if old_ie.kind == 'directory':
670
684
self._next_progress_entry()
671
# not in final inv yet, was not in the selected files, so is an
672
# entry to be preserved unaltered.
685
# We preserve the entry unaltered.
673
686
ie = old_ie.copy()
674
687
# Note: specific file commits after a merge are currently
675
688
# prohibited. This test is for sanity/safety in case it's
676
689
# required after that changes.
677
690
if len(self.parents) > 1:
678
691
ie.revision = None
679
delta, version_recorded = self.builder.record_entry_contents(
692
_, version_recorded, _ = self.builder.record_entry_contents(
680
693
ie, self.parent_invs, path, self.basis_tree, None)
681
694
if version_recorded:
682
695
self.any_entries_changed = True
683
if delta: self._basis_delta.append(delta)
685
697
def _report_and_accumulate_deletes(self):
686
698
# XXX: Could the list of deleted paths and ids be instead taken from
687
699
# _populate_from_inventory?
688
deleted_ids = set(self.basis_inv._byid.keys()) - \
689
set(self.builder.new_inventory._byid.keys())
700
if (isinstance(self.basis_inv, Inventory)
701
and isinstance(self.builder.new_inventory, Inventory)):
702
# the older Inventory classes provide a _byid dict, and building a
703
# set from the keys of this dict is substantially faster than even
704
# getting a set of ids from the inventory
706
# <lifeless> set(dict) is roughly the same speed as
707
# set(iter(dict)) and both are significantly slower than
709
deleted_ids = set(self.basis_inv._byid.keys()) - \
710
set(self.builder.new_inventory._byid.keys())
712
deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
691
714
self.any_entries_deleted = True
692
715
deleted = [(self.basis_tree.id2path(file_id), file_id)
695
718
# XXX: this is not quite directory-order sorting
696
719
for path, file_id in deleted:
697
self._basis_delta.append((path, None, file_id, None))
720
self.builder.record_delete(path, file_id)
698
721
self.reporter.deleted(path)
700
def _populate_from_inventory(self, specific_files):
723
def _populate_from_inventory(self):
701
724
"""Populate the CommitBuilder by walking the working tree inventory."""
703
726
# raise an exception as soon as we find a single unknown.
704
727
for unknown in self.work_tree.unknowns():
705
728
raise StrictCommitFailed()
730
specific_files = self.specific_files
731
exclude = self.exclude
707
732
report_changes = self.reporter.is_verbose()
709
734
# A tree of paths that have been deleted. E.g. if foo/bar has been
712
737
# XXX: Note that entries may have the wrong kind because the entry does
713
738
# not reflect the status on disk.
714
739
work_inv = self.work_tree.inventory
740
# NB: entries will include entries within the excluded ids/paths
741
# because iter_entries_by_dir has no 'exclude' facility today.
715
742
entries = work_inv.iter_entries_by_dir(
716
743
specific_file_ids=self.specific_file_ids, yield_parents=True)
717
744
for path, existing_ie in entries:
739
766
if deleted_dict is not None:
740
767
# the path has a deleted parent, do not add it.
769
if exclude and is_inside_any(exclude, path):
770
# Skip excluded paths. Excluded paths are processed by
771
# _update_builder_with_changes.
742
773
content_summary = self.work_tree.path_content_summary(path)
743
774
# Note that when a filter of specific files is given, we must only
744
775
# skip/record deleted files matching that filter.
822
853
ie = existing_ie.copy()
823
854
ie.revision = None
824
delta, version_recorded = self.builder.record_entry_contents(ie,
825
self.parent_invs, path, self.work_tree, content_summary)
827
self._basis_delta.append(delta)
855
# For carried over entries we don't care about the fs hash - the repo
856
# isn't generating a sha, so we're not saving computation time.
857
_, version_recorded, fs_hash = self.builder.record_entry_contents(
858
ie, self.parent_invs, path, self.work_tree, content_summary)
828
859
if version_recorded:
829
860
self.any_entries_changed = True
830
861
if report_changes:
831
862
self._report_change(ie, path)
864
self.work_tree._observed_sha1(ie.file_id, path, fs_hash)
834
867
def _report_change(self, ie, path):