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
135
139
def completed(self, revno, rev_id):
136
140
self._note('Committed revision %d.', revno)
138
142
def deleted(self, file_id):
139
143
self._note('deleted %s', file_id)
147
151
def renamed(self, change, old_path, new_path):
148
152
self._note('%s %s => %s', change, old_path, new_path)
154
def is_verbose(self):
151
158
class Commit(object):
152
159
"""Task of committing a new revision.
210
218
:param revprops: Properties for new revision
211
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
212
222
:param recursive: If set to 'down', commit in any subtrees that have
213
223
pending changes of any sort during this commit.
232
242
" parameter is required for commit().")
234
244
self.bound_branch = None
245
self.any_entries_changed = False
246
self.any_entries_deleted = False
235
247
self.local = local
236
248
self.master_branch = None
237
249
self.master_locked = False
238
250
self.rev_id = None
239
self.specific_files = specific_files
251
if specific_files is not None:
252
self.specific_files = sorted(
253
minimum_path_selection(specific_files))
255
self.specific_files = None
256
self.specific_file_ids = None
240
257
self.allow_pointless = allow_pointless
241
258
self.recursive = recursive
242
259
self.revprops = revprops
247
264
self.strict = strict
248
265
self.verbose = verbose
250
if reporter is None and self.reporter is None:
251
self.reporter = NullCommitReporter()
252
elif reporter is not None:
253
self.reporter = reporter
255
267
self.work_tree.lock_write()
256
268
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
257
269
self.basis_tree = self.work_tree.basis_tree()
267
279
# Check that the working tree is up to date
268
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()
270
287
if self.config is None:
271
288
self.config = self.branch.get_config()
273
290
# If provided, ensure the specified files are versioned
274
if specific_files is not None:
275
# Note: We don't actually need the IDs here. This routine
291
if self.specific_files is not None:
276
293
# is being called because it raises PathNotVerisonedError
277
# as a side effect of finding the IDs.
294
# as a side effect of finding the IDs. We later use the ids we
295
# found as input to the working tree inventory iterator, so we
296
# only consider those ids rather than examining the whole tree
278
298
# XXX: Dont we have filter_unversioned to do this more
280
tree.find_ids_across_trees(specific_files,
281
[self.basis_tree, self.work_tree])
300
self.specific_file_ids = tree.find_ids_across_trees(
301
specific_files, [self.basis_tree, self.work_tree])
283
303
# Setup the progress bar. As the number of files that need to be
284
304
# committed in unknown, progress is reported as stages.
369
389
return self.rev_id
371
def _any_real_changes(self):
372
"""Are there real changes between new_inventory and basis?
374
For trees without rich roots, inv.root.revision changes every commit.
375
But if that is the only change, we want to treat it as though there
378
new_entries = self.builder.new_inventory.iter_entries()
379
basis_entries = self.basis_inv.iter_entries()
380
new_path, new_root_ie = new_entries.next()
381
basis_path, basis_root_ie = basis_entries.next()
383
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
384
def ie_equal_no_revision(this, other):
385
return ((this.file_id == other.file_id)
386
and (this.name == other.name)
387
and (this.symlink_target == other.symlink_target)
388
and (this.text_sha1 == other.text_sha1)
389
and (this.text_size == other.text_size)
390
and (this.text_id == other.text_id)
391
and (this.parent_id == other.parent_id)
392
and (this.kind == other.kind)
393
and (this.executable == other.executable)
394
and (this.reference_revision == other.reference_revision)
396
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
399
for new_ie, basis_ie in zip(new_entries, basis_entries):
400
if new_ie != basis_ie:
403
# No actual changes present
391
def _select_reporter(self):
392
"""Select the CommitReporter to use."""
394
return NullCommitReporter()
395
return ReportCommitToLog()
406
397
def _check_pointless(self):
407
398
if self.allow_pointless:
420
411
# If length == 1, then we only have the root entry. Which means
421
412
# that there is no real difference (only the root could be different)
422
if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
413
if len(self.builder.new_inventory) != 1 and (self.any_entries_changed
414
or self.any_entries_deleted):
424
416
raise PointlessCommit()
656
648
# recorded in their previous state. For more details, see
657
649
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
658
650
if specific_files:
659
for path, new_ie in self.basis_inv.iter_entries():
660
if new_ie.file_id in self.builder.new_inventory:
651
for path, old_ie in self.basis_inv.iter_entries():
652
if old_ie.file_id in self.builder.new_inventory:
662
654
if is_inside_any(specific_files, path):
666
self.builder.record_entry_contents(ie, self.parent_invs, path,
656
if old_ie.kind == 'directory':
657
self._next_progress_entry()
659
# Note: specific file commits after a merge are currently
660
# prohibited. This test is for sanity/safety in case it's
661
# required after that changes.
662
if len(self.parents) > 1:
664
if self.builder.record_entry_contents(ie, self.parent_invs, path,
666
self.any_entries_changed = True
669
# Report what was deleted. We could skip this when no deletes are
670
# detected to gain a performance win, but it arguably serves as a
671
# 'safety check' by informing the user whenever anything disappears.
672
for path, ie in self.basis_inv.iter_entries():
673
if ie.file_id not in self.builder.new_inventory:
674
self.reporter.deleted(path)
668
# note that deletes have occurred
669
if set(self.basis_inv._byid.keys()) - set(self.builder.new_inventory._byid.keys()):
670
self.any_entries_deleted = True
671
# Report what was deleted.
672
if self.any_entries_deleted and self.reporter.is_verbose():
673
for path, ie in self.basis_inv.iter_entries():
674
if ie.file_id not in self.builder.new_inventory:
675
self.reporter.deleted(path)
676
677
def _populate_from_inventory(self, specific_files):
677
678
"""Populate the CommitBuilder by walking the working tree inventory."""
680
681
for unknown in self.work_tree.unknowns():
681
682
raise StrictCommitFailed()
684
report_changes = self.reporter.is_verbose()
684
686
deleted_paths = set()
685
687
work_inv = self.work_tree.inventory
686
688
assert work_inv.root is not None
687
entries = work_inv.iter_entries()
689
entries = work_inv.iter_entries_by_dir(
690
specific_file_ids=self.specific_file_ids, yield_parents=True)
688
691
if not self.builder.record_root_entry:
690
693
for path, existing_ie in entries:
702
704
# deleted files matching that filter.
703
705
if is_inside_any(deleted_paths, path):
705
if not specific_files or is_inside_any(specific_files, path):
706
if not self.work_tree.has_filename(path):
707
deleted_paths.add(path)
708
self.reporter.missing(path)
709
deleted_ids.append(file_id)
707
if not self.work_tree.has_filename(path):
708
deleted_paths.add(path)
709
self.reporter.missing(path)
710
deleted_ids.append(file_id)
712
713
kind = self.work_tree.kind(file_id)
713
714
# TODO: specific_files filtering before nested tree processing
720
721
# Note: I don't particularly want to have the existing_ie
721
722
# parameter but the test suite currently (28-Jun-07) breaks
722
723
# without it thanks to a unicode normalisation issue. :-(
723
definitely_changed = kind != existing_ie.kind
724
definitely_changed = kind != existing_ie.kind
724
725
self._record_entry(path, file_id, specific_files, kind, name,
725
parent_id, definitely_changed, existing_ie)
726
parent_id, definitely_changed, existing_ie, report_changes)
727
728
# Unversion IDs that were found to be deleted
728
729
self.work_tree.unversion(deleted_ids)
755
756
def _record_entry(self, path, file_id, specific_files, kind, name,
756
parent_id, definitely_changed, existing_ie=None):
757
parent_id, definitely_changed, existing_ie=None,
758
report_changes=True):
757
759
"Record the new inventory entry for a path if any."
758
760
# mutter('check %s {%s}', path, file_id)
759
if (not specific_files or
760
is_inside_or_parent_of_any(specific_files, path)):
761
# mutter('%s selected for commit', path)
762
if definitely_changed or existing_ie is None:
763
ie = inventory.make_entry(kind, name, parent_id, file_id)
765
ie = existing_ie.copy()
761
# mutter('%s selected for commit', path)
762
if definitely_changed or existing_ie is None:
763
ie = inventory.make_entry(kind, name, parent_id, file_id)
768
# mutter('%s not selected for commit', path)
769
if self.basis_inv.has_id(file_id):
770
ie = self.basis_inv[file_id].copy()
772
# this entry is new and not being committed
775
self.builder.record_entry_contents(ie, self.parent_invs,
776
path, self.work_tree)
765
ie = existing_ie.copy()
767
if self.builder.record_entry_contents(ie, self.parent_invs,
768
path, self.work_tree):
769
self.any_entries_changed = True
777
771
self._report_change(ie, path)