109
109
def _note(self, format, *args):
110
110
"""Output a message.
112
Messages are output by writing directly to stderr instead of
113
using bzrlib.trace.note(). The latter constantly updates the
114
log file as we go causing an unnecessary performance hit.
116
Subclasses may choose to override this method but need to be aware
117
of its potential impact on performance.
112
Subclasses may choose to override this method.
119
bzrlib.ui.ui_factory.clear_term()
120
sys.stderr.write((format + "\n") % args)
122
116
def snapshot_change(self, change, path):
123
117
if change == 'unchanged':
269
263
if self.config is None:
270
264
self.config = self.branch.get_config()
272
self.work_inv = self.work_tree.inventory
273
self.basis_inv = self.basis_tree.inventory
266
# If provided, ensure the specified files are versioned
274
267
if specific_files is not None:
275
# Ensure specified files are versioned
276
# (We don't actually need the ids here)
268
# Note: We don't actually need the IDs here. This routine
269
# is being called because it raises PathNotVerisonedError
270
# as a side effect of finding the IDs.
277
271
# XXX: Dont we have filter_unversioned to do this more
279
273
tree.find_ids_across_trees(specific_files,
280
274
[self.basis_tree, self.work_tree])
282
# Setup the progress bar ...
283
# one to finish, one for rev and inventory, and one for each
284
# inventory entry, and the same for the new inventory.
285
# note that this estimate is too long when we do a partial tree
286
# commit which excludes some new files from being considered.
287
# The estimate is corrected when we populate the new inv.
288
self.pb_total = len(self.work_inv) + 5
276
# Setup the progress bar. As the number of files that need to be
277
# committed in unknown, progress is reported as stages.
278
# We keep track of entries separately though and include that
279
# information in the progress bar during the relevant stages.
280
self.pb_stage_name = ""
281
self.pb_stage_count = 0
282
self.pb_stage_total = 4
283
if self.bound_branch:
284
self.pb_stage_total += 1
285
self.pb.show_pct = False
286
self.pb.show_spinner = False
287
self.pb.show_eta = False
288
self.pb.show_count = True
289
self.pb.show_bar = False
291
# After a merge, a selected file commit is not supported.
292
# See 'bzr help merge' for an explanation as to why.
293
self.basis_inv = self.basis_tree.inventory
291
294
self._gather_parents()
292
295
if len(self.parents) > 1 and self.specific_files:
293
296
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
295
# Build the new inventory
298
# Collect the changes
299
self._emit_progress_set_stage("Collecting changes", show_entries=True)
296
300
self.builder = self.branch.get_commit_builder(self.parents,
297
301
self.config, timestamp, timezone, committer, revprops, rev_id)
298
self._remove_deleted()
299
self._populate_new_inv()
300
self._report_deletes()
302
self._update_builder_with_changes()
301
303
self._check_pointless()
302
self._emit_progress_update()
304
# TODO: Now the new inventory is known, check for conflicts and
305
# prompt the user for a commit message.
305
# TODO: Now the new inventory is known, check for conflicts.
306
306
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
307
307
# weave lines, because nothing should be recorded until it is known
308
308
# that commit will succeed.
309
self._emit_progress_set_stage("Saving data locally")
309
310
self.builder.finish_inventory()
310
self._emit_progress_update()
312
# Prompt the user for a commit message if none provided
311
313
message = message_callback(self)
312
314
assert isinstance(message, unicode), type(message)
313
315
self.message = message
492
494
old_revid = bzrlib.revision.NULL_REVISION
493
495
for hook in Branch.hooks['post_commit']:
496
# show the running hook in the progress bar. As hooks may
497
# end up doing nothing (e.g. because they are not configured by
498
# the user) this is still showing progress, not showing overall
499
# actions - its up to each plugin to show a UI if it want's to
500
# (such as 'Emailing diff to foo@example.com').
501
self.pb_stage_name = "Running post commit hooks [%s]" % \
502
Branch.hooks.get_hook_name(hook)
503
self._emit_progress()
494
504
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
563
573
mutter('commit parent ghost revision {%s}', revision)
565
def _remove_deleted(self):
566
"""Remove deleted files from the working inventories.
568
This is done prior to taking the working inventory as the
569
basis for the new committed inventory.
571
This returns true if any files
572
*that existed in the basis inventory* were deleted.
573
Files that were added and deleted
574
in the working copy don't matter.
576
specific = self.specific_files
578
deleted_paths = set()
579
for path, ie in self.work_inv.iter_entries():
580
if is_inside_any(deleted_paths, path):
581
# The tree will delete the required ids recursively.
583
if specific and not is_inside_any(specific, path):
585
if not self.work_tree.has_filename(path):
586
deleted_paths.add(path)
587
self.reporter.missing(path)
588
deleted_ids.append(ie.file_id)
589
self.work_tree.unversion(deleted_ids)
591
def _populate_new_inv(self):
592
"""Build revision inventory.
594
This creates a new empty inventory. Depending on
595
which files are selected for commit, and what is present in the
596
current tree, the new inventory is populated. inventory entries
597
which are candidates for modification have their revision set to
598
None; inventory entries that are carried over untouched have their
599
revision set to their prior value.
575
def _update_builder_with_changes(self):
576
"""Update the commit builder with the data about what has changed.
578
# Build the revision inventory.
580
# This starts by creating a new empty inventory. Depending on
581
# which files are selected for commit, and what is present in the
582
# current tree, the new inventory is populated. inventory entries
583
# which are candidates for modification have their revision set to
584
# None; inventory entries that are carried over untouched have their
585
# revision set to their prior value.
601
587
# ESEPARATIONOFCONCERNS: this function is diffing and using the diff
602
588
# results to create a new inventory at the same time, which results
603
589
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
604
590
# ADHB 11-07-2006
605
mutter("Selecting files for commit with filter %s", self.specific_files)
606
assert self.work_inv.root is not None
607
entries = self.work_inv.iter_entries()
592
specific_files = self.specific_files
593
mutter("Selecting files for commit with filter %s", specific_files)
594
work_inv = self.work_tree.inventory
595
assert work_inv.root is not None
596
self.pb_entries_total = len(work_inv)
598
# Check and warn about old CommitBuilders
599
entries = work_inv.iter_entries()
608
600
if not self.builder.record_root_entry:
609
601
symbol_versioning.warn('CommitBuilders should support recording'
610
602
' the root entry as of bzr 0.10.', DeprecationWarning,
612
604
self.builder.new_inventory.add(self.basis_inv.root.copy())
614
self._emit_progress_update()
608
deleted_paths = set()
615
609
for path, new_ie in entries:
616
self._emit_progress_update()
610
self._emit_progress_next_entry()
617
611
file_id = new_ie.file_id
613
# Skip files that have been deleted from the working tree.
614
# The deleted files/directories are also recorded so they
615
# can be explicitly unversioned later. Note that when a
616
# filter of specific files is given, we must only skip/record
617
# deleted files matching that filter.
618
if is_inside_any(deleted_paths, path):
620
if not specific_files or is_inside_any(specific_files, path):
621
if not self.work_tree.has_filename(path):
622
deleted_paths.add(path)
623
self.reporter.missing(path)
624
deleted_ids.append(file_id)
619
627
kind = self.work_tree.kind(file_id)
620
628
if kind == 'tree-reference' and self.recursive == 'down':
677
685
self.reporter.snapshot_change(change, path)
679
if not self.specific_files:
682
# ignore removals that don't match filespec
683
for path, new_ie in self.basis_inv.iter_entries():
684
if new_ie.file_id in self.work_inv:
686
if is_inside_any(self.specific_files, path):
690
self.builder.record_entry_contents(ie, self.parent_invs, path,
693
def _emit_progress_update(self):
694
"""Emit an update to the progress bar."""
695
self.pb.update("Committing", self.pb_count, self.pb_total)
698
def _report_deletes(self):
687
# Unversion IDs that were found to be deleted
688
self.work_tree.unversion(deleted_ids)
690
# If specific files/directories were nominated, it is possible
691
# that some data from outside those needs to be preserved from
692
# the basis tree. For example, if a file x is moved from out of
693
# directory foo into directory bar and the user requests
694
# ``commit foo``, then information about bar/x must also be
697
for path, new_ie in self.basis_inv.iter_entries():
698
if new_ie.file_id in work_inv:
700
if is_inside_any(specific_files, path):
704
self.builder.record_entry_contents(ie, self.parent_invs, path,
707
# Report what was deleted. We could skip this when no deletes are
708
# detected to gain a performance win, but it arguably serves as a
709
# 'safety check' by informing the user whenever anything disappears.
699
710
for path, ie in self.basis_inv.iter_entries():
700
711
if ie.file_id not in self.builder.new_inventory:
701
712
self.reporter.deleted(path)
714
def _emit_progress_set_stage(self, name, show_entries=False):
715
"""Set the progress stage and emit an update to the progress bar."""
716
self.pb_stage_name = name
717
self.pb_stage_count += 1
718
self.pb_entries_show = show_entries
720
self.pb_entries_count = 0
721
self.pb_entries_total = '?'
722
self._emit_progress()
724
def _emit_progress_next_entry(self):
725
"""Emit an update to the progress bar and increment the file count."""
726
self.pb_entries_count += 1
727
self._emit_progress()
729
def _emit_progress(self):
730
if self.pb_entries_show:
731
text = "%s [Entry %d/%s] - Stage" % (self.pb_stage_name,
732
self.pb_entries_count,str(self.pb_entries_total))
734
text = "%s - Stage" % (self.pb_stage_name)
735
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)