71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
72
72
is_inside_or_parent_of_any,
73
minimum_path_selection,
73
74
quotefn, sha_file, split_lines)
74
75
from bzrlib.testament import Testament
75
from bzrlib.trace import mutter, note, warning
76
from bzrlib.trace import mutter, note, warning, is_quiet
76
77
from bzrlib.xml5 import serializer_v5
77
78
from bzrlib.inventory import Inventory, InventoryEntry
78
79
from bzrlib import symbol_versioning
80
81
deprecated_function,
81
82
DEPRECATED_PARAMETER)
82
83
from bzrlib.workingtree import WorkingTree
84
from bzrlib.urlutils import unescape_for_display
86
88
class NullCommitReporter(object):
87
89
"""I report on progress of a commit."""
91
def started(self, revno, revid, location=None):
89
94
def snapshot_change(self, change, path):
122
130
self._note("%s %s", change, path)
132
def started(self, revno, rev_id, location=None):
133
if location is not None:
134
location = ' to "' + unescape_for_display(location, 'utf-8') + '"'
137
self._note('Committing revision %d%s.', revno, location)
124
139
def completed(self, revno, rev_id):
125
140
self._note('Committed revision %d.', revno)
127
142
def deleted(self, file_id):
128
143
self._note('deleted %s', file_id)
199
218
:param revprops: Properties for new revision
200
219
:param local: Perform a local only commit.
220
:param reporter: the reporter to use or None for the default
221
:param verbose: if True and the reporter is not None, report everything
201
222
:param recursive: If set to 'down', commit in any subtrees that have
202
223
pending changes of any sort during this commit.
221
242
" parameter is required for commit().")
223
244
self.bound_branch = None
245
self.any_entries_changed = False
246
self.any_entries_deleted = False
224
247
self.local = local
225
248
self.master_branch = None
226
249
self.master_locked = False
250
self.recursive = recursive
227
251
self.rev_id = None
228
self.specific_files = specific_files
252
if specific_files is not None:
253
self.specific_files = sorted(
254
minimum_path_selection(specific_files))
256
self.specific_files = None
257
self.specific_file_ids = None
229
258
self.allow_pointless = allow_pointless
230
self.recursive = recursive
231
259
self.revprops = revprops
232
260
self.message_callback = message_callback
233
261
self.timestamp = timestamp
236
264
self.strict = strict
237
265
self.verbose = verbose
239
if reporter is None and self.reporter is None:
240
self.reporter = NullCommitReporter()
241
elif reporter is not None:
242
self.reporter = reporter
244
267
self.work_tree.lock_write()
245
268
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
246
269
self.basis_tree = self.work_tree.basis_tree()
256
279
# Check that the working tree is up to date
257
280
old_revno, new_revno = self._check_out_of_date_tree()
282
# Complete configuration setup
283
if reporter is not None:
284
self.reporter = reporter
285
elif self.reporter is None:
286
self.reporter = self._select_reporter()
259
287
if self.config is None:
260
288
self.config = self.branch.get_config()
262
290
# If provided, ensure the specified files are versioned
263
if specific_files is not None:
264
# Note: We don't actually need the IDs here. This routine
265
# is being called because it raises PathNotVerisonedError
266
# as a side effect of finding the IDs.
291
if self.specific_files is not None:
292
# Note: This routine is being called because it raises
293
# PathNotVersionedError as a side effect of finding the IDs. We
294
# later use the ids we found as input to the working tree
295
# inventory iterator, so we only consider those ids rather than
296
# examining the whole tree again.
267
297
# XXX: Dont we have filter_unversioned to do this more
269
tree.find_ids_across_trees(specific_files,
270
[self.basis_tree, self.work_tree])
299
self.specific_file_ids = tree.find_ids_across_trees(
300
specific_files, [self.basis_tree, self.work_tree])
272
302
# Setup the progress bar. As the number of files that need to be
273
303
# committed in unknown, progress is reported as stages.
298
328
self.config, timestamp, timezone, committer, revprops, rev_id)
331
# find the location being committed to
332
if self.bound_branch:
333
master_location = self.master_branch.base
335
master_location = self.branch.base
337
# report the start of the commit
338
self.reporter.started(new_revno, self.rev_id, master_location)
301
340
self._update_builder_with_changes()
302
341
self._check_pointless()
349
388
return self.rev_id
351
def _any_real_changes(self):
352
"""Are there real changes between new_inventory and basis?
354
For trees without rich roots, inv.root.revision changes every commit.
355
But if that is the only change, we want to treat it as though there
358
new_entries = self.builder.new_inventory.iter_entries()
359
basis_entries = self.basis_inv.iter_entries()
360
new_path, new_root_ie = new_entries.next()
361
basis_path, basis_root_ie = basis_entries.next()
363
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
364
def ie_equal_no_revision(this, other):
365
return ((this.file_id == other.file_id)
366
and (this.name == other.name)
367
and (this.symlink_target == other.symlink_target)
368
and (this.text_sha1 == other.text_sha1)
369
and (this.text_size == other.text_size)
370
and (this.text_id == other.text_id)
371
and (this.parent_id == other.parent_id)
372
and (this.kind == other.kind)
373
and (this.executable == other.executable)
374
and (this.reference_revision == other.reference_revision)
376
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
379
for new_ie, basis_ie in zip(new_entries, basis_entries):
380
if new_ie != basis_ie:
383
# No actual changes present
390
def _select_reporter(self):
391
"""Select the CommitReporter to use."""
393
return NullCommitReporter()
394
return ReportCommitToLog()
386
396
def _check_pointless(self):
387
397
if self.allow_pointless:
636
647
# recorded in their previous state. For more details, see
637
648
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
638
649
if specific_files:
639
for path, new_ie in self.basis_inv.iter_entries():
640
if new_ie.file_id in self.builder.new_inventory:
650
for path, old_ie in self.basis_inv.iter_entries():
651
if old_ie.file_id in self.builder.new_inventory:
652
# already added - skip.
642
654
if is_inside_any(specific_files, path):
655
# was inside the selected path, if not present it has been
646
self.builder.record_entry_contents(ie, self.parent_invs, path,
658
if old_ie.kind == 'directory':
659
self._next_progress_entry()
660
# not in final inv yet, was not in the selected files, so is an
661
# entry to be preserved unaltered.
663
# Note: specific file commits after a merge are currently
664
# prohibited. This test is for sanity/safety in case it's
665
# required after that changes.
666
if len(self.parents) > 1:
668
delta, version_recorded = self.builder.record_entry_contents(
669
ie, self.parent_invs, path, self.basis_tree, None)
671
self.any_entries_changed = True
649
# Report what was deleted. We could skip this when no deletes are
650
# detected to gain a performance win, but it arguably serves as a
651
# 'safety check' by informing the user whenever anything disappears.
652
for path, ie in self.basis_inv.iter_entries():
653
if ie.file_id not in self.builder.new_inventory:
654
self.reporter.deleted(path)
673
# note that deletes have occurred
674
if set(self.basis_inv._byid.keys()) - set(self.builder.new_inventory._byid.keys()):
675
self.any_entries_deleted = True
676
# Report what was deleted.
677
if self.any_entries_deleted and self.reporter.is_verbose():
678
for path, ie in self.basis_inv.iter_entries():
679
if ie.file_id not in self.builder.new_inventory:
680
self.reporter.deleted(path)
656
682
def _populate_from_inventory(self, specific_files):
657
683
"""Populate the CommitBuilder by walking the working tree inventory."""
660
686
for unknown in self.work_tree.unknowns():
661
687
raise StrictCommitFailed()
689
report_changes = self.reporter.is_verbose()
664
691
deleted_paths = set()
665
692
work_inv = self.work_tree.inventory
666
693
assert work_inv.root is not None
667
entries = work_inv.iter_entries()
694
# XXX: Note that entries may have the wrong kind.
695
entries = work_inv.iter_entries_by_dir(
696
specific_file_ids=self.specific_file_ids, yield_parents=True)
668
697
if not self.builder.record_root_entry:
670
699
for path, existing_ie in entries:
682
710
# deleted files matching that filter.
683
711
if is_inside_any(deleted_paths, path):
713
content_summary = self.work_tree.path_content_summary(path)
685
714
if not specific_files or is_inside_any(specific_files, path):
686
if not self.work_tree.has_filename(path):
715
if content_summary[0] == 'missing':
687
716
deleted_paths.add(path)
688
717
self.reporter.missing(path)
689
718
deleted_ids.append(file_id)
692
kind = self.work_tree.kind(file_id)
693
# TODO: specific_files filtering before nested tree processing
694
if kind == 'tree-reference' and self.recursive == 'down':
695
self._commit_nested_tree(file_id, path)
696
except errors.NoSuchFile:
720
# TODO: have the builder do the nested commit just-in-time IF and
722
if content_summary[0] == 'tree-reference':
723
# enforce repository nested tree policy.
724
if (not self.work_tree.supports_tree_reference() or
725
# repository does not support it either.
726
not self.branch.repository._format.supports_tree_reference):
727
content_summary = ('directory',) + content_summary[1:]
728
kind = content_summary[0]
729
# TODO: specific_files filtering before nested tree processing
730
if kind == 'tree-reference':
731
if self.recursive == 'down':
732
nested_revision_id = self._commit_nested_tree(
734
content_summary = content_summary[:3] + (
737
content_summary = content_summary[:3] + (
738
self.work_tree.get_reference_revision(file_id),)
699
740
# Record an entry for this item
700
741
# Note: I don't particularly want to have the existing_ie
701
742
# parameter but the test suite currently (28-Jun-07) breaks
702
743
# without it thanks to a unicode normalisation issue. :-(
703
definitely_changed = kind != existing_ie.kind
744
definitely_changed = kind != existing_ie.kind
704
745
self._record_entry(path, file_id, specific_files, kind, name,
705
parent_id, definitely_changed, existing_ie)
746
parent_id, definitely_changed, existing_ie, report_changes,
707
749
# Unversion IDs that were found to be deleted
708
750
self.work_tree.unversion(deleted_ids)
721
763
sub_tree.branch.repository = \
722
764
self.work_tree.branch.repository
724
sub_tree.commit(message=None, revprops=self.revprops,
766
return sub_tree.commit(message=None, revprops=self.revprops,
725
767
recursive=self.recursive,
726
768
message_callback=self.message_callback,
727
769
timestamp=self.timestamp, timezone=self.timezone,
730
772
strict=self.strict, verbose=self.verbose,
731
773
local=self.local, reporter=self.reporter)
732
774
except errors.PointlessCommit:
775
return self.work_tree.get_reference_revision(file_id)
735
777
def _record_entry(self, path, file_id, specific_files, kind, name,
736
parent_id, definitely_changed, existing_ie=None):
778
parent_id, definitely_changed, existing_ie, report_changes,
737
780
"Record the new inventory entry for a path if any."
738
781
# mutter('check %s {%s}', path, file_id)
739
if (not specific_files or
740
is_inside_or_parent_of_any(specific_files, path)):
741
# mutter('%s selected for commit', path)
742
if definitely_changed or existing_ie is None:
743
ie = inventory.make_entry(kind, name, parent_id, file_id)
745
ie = existing_ie.copy()
782
# mutter('%s selected for commit', path)
783
if definitely_changed or existing_ie is None:
784
ie = inventory.make_entry(kind, name, parent_id, file_id)
748
# mutter('%s not selected for commit', path)
749
if self.basis_inv.has_id(file_id):
750
ie = self.basis_inv[file_id].copy()
752
# this entry is new and not being committed
755
self.builder.record_entry_contents(ie, self.parent_invs,
756
path, self.work_tree)
786
ie = existing_ie.copy()
788
delta, version_recorded = self.builder.record_entry_contents(ie,
789
self.parent_invs, path, self.work_tree, content_summary)
791
self.any_entries_changed = True
757
793
self._report_change(ie, path)