263
264
if self.config is None:
264
265
self.config = self.branch.get_config()
266
self.work_inv = self.work_tree.inventory
267
self.basis_inv = self.basis_tree.inventory
267
# If provided, ensure the specified files are versioned
268
268
if specific_files is not None:
269
# Ensure specified files are versioned
270
# (We don't actually need the ids here)
269
# Note: We don't actually need the IDs here. This routine
270
# is being called because it raises PathNotVerisonedError
271
# as a side effect of finding the IDs.
271
272
# XXX: Dont we have filter_unversioned to do this more
273
274
tree.find_ids_across_trees(specific_files,
288
289
self.pb.show_count = True
289
290
self.pb.show_bar = False
292
# After a merge, a selected file commit is not supported.
293
# See 'bzr help merge' for an explanation as to why.
294
self.basis_inv = self.basis_tree.inventory
291
295
self._gather_parents()
292
296
if len(self.parents) > 1 and self.specific_files:
293
297
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
295
# Build the new inventory
299
# Collect the changes
296
300
self._emit_progress_set_stage("Collecting changes", show_entries=True)
297
301
self.builder = self.branch.get_commit_builder(self.parents,
298
302
self.config, timestamp, timezone, committer, revprops, rev_id)
299
self._remove_deleted()
300
self._populate_new_inv()
301
self._report_deletes()
303
self._update_builder_with_changes()
302
304
self._check_pointless()
304
# TODO: Now the new inventory is known, check for conflicts and
305
# prompt the user for a commit message.
306
# TODO: Now the new inventory is known, check for conflicts.
306
307
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
307
308
# weave lines, because nothing should be recorded until it is known
308
309
# that commit will succeed.
309
310
self._emit_progress_set_stage("Saving data locally")
310
311
self.builder.finish_inventory()
313
# Prompt the user for a commit message if none provided
311
314
message = message_callback(self)
312
315
assert isinstance(message, unicode), type(message)
313
316
self.message = message
493
495
old_revid = bzrlib.revision.NULL_REVISION
494
496
for hook in Branch.hooks['post_commit']:
497
# show the running hook in the progress bar. As hooks may
498
# end up doing nothing (e.g. because they are not configured by
499
# the user) this is still showing progress, not showing overall
500
# actions - its up to each plugin to show a UI if it want's to
501
# (such as 'Emailing diff to foo@example.com').
502
self.pb_stage_name = "Running post commit hooks [%s]" % \
503
Branch.hooks.get_hook_name(hook)
504
self._emit_progress()
505
if 'hooks' in debug.debug_flags:
506
mutter("Invoking commit hook: %r", hook)
495
507
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
564
576
mutter('commit parent ghost revision {%s}', revision)
566
def _remove_deleted(self):
567
"""Remove deleted files from the working inventories.
569
This is done prior to taking the working inventory as the
570
basis for the new committed inventory.
572
This returns true if any files
573
*that existed in the basis inventory* were deleted.
574
Files that were added and deleted
575
in the working copy don't matter.
577
specific = self.specific_files
579
deleted_paths = set()
580
for path, ie in self.work_inv.iter_entries():
581
if is_inside_any(deleted_paths, path):
582
# The tree will delete the required ids recursively.
584
if specific and not is_inside_any(specific, path):
586
if not self.work_tree.has_filename(path):
587
deleted_paths.add(path)
588
self.reporter.missing(path)
589
deleted_ids.append(ie.file_id)
590
self.work_tree.unversion(deleted_ids)
592
def _populate_new_inv(self):
593
"""Build revision inventory.
595
This creates a new empty inventory. Depending on
596
which files are selected for commit, and what is present in the
597
current tree, the new inventory is populated. inventory entries
598
which are candidates for modification have their revision set to
599
None; inventory entries that are carried over untouched have their
600
revision set to their prior value.
578
def _update_builder_with_changes(self):
579
"""Update the commit builder with the data about what has changed.
581
# Build the revision inventory.
583
# This starts by creating a new empty inventory. Depending on
584
# which files are selected for commit, and what is present in the
585
# current tree, the new inventory is populated. inventory entries
586
# which are candidates for modification have their revision set to
587
# None; inventory entries that are carried over untouched have their
588
# revision set to their prior value.
602
590
# ESEPARATIONOFCONCERNS: this function is diffing and using the diff
603
591
# results to create a new inventory at the same time, which results
604
592
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
605
593
# ADHB 11-07-2006
606
mutter("Selecting files for commit with filter %s", self.specific_files)
607
assert self.work_inv.root is not None
608
entries = self.work_inv.iter_entries()
595
specific_files = self.specific_files
596
mutter("Selecting files for commit with filter %s", specific_files)
597
work_inv = self.work_tree.inventory
598
assert work_inv.root is not None
599
self.pb_entries_total = len(work_inv)
601
# Check and warn about old CommitBuilders
602
entries = work_inv.iter_entries()
609
603
if not self.builder.record_root_entry:
610
604
symbol_versioning.warn('CommitBuilders should support recording'
611
605
' the root entry as of bzr 0.10.', DeprecationWarning,
613
607
self.builder.new_inventory.add(self.basis_inv.root.copy())
615
self.pb_entries_total = len(self.work_inv)
611
deleted_paths = set()
616
612
for path, new_ie in entries:
617
613
self._emit_progress_next_entry()
618
614
file_id = new_ie.file_id
616
# Skip files that have been deleted from the working tree.
617
# The deleted files/directories are also recorded so they
618
# can be explicitly unversioned later. Note that when a
619
# filter of specific files is given, we must only skip/record
620
# deleted files matching that filter.
621
if is_inside_any(deleted_paths, path):
623
if not specific_files or is_inside_any(specific_files, path):
624
if not self.work_tree.has_filename(path):
625
deleted_paths.add(path)
626
self.reporter.missing(path)
627
deleted_ids.append(file_id)
620
630
kind = self.work_tree.kind(file_id)
621
631
if kind == 'tree-reference' and self.recursive == 'down':
678
688
self.reporter.snapshot_change(change, path)
680
if not self.specific_files:
683
# ignore removals that don't match filespec
684
for path, new_ie in self.basis_inv.iter_entries():
685
if new_ie.file_id in self.work_inv:
687
if is_inside_any(self.specific_files, path):
691
self.builder.record_entry_contents(ie, self.parent_invs, path,
690
# Unversion IDs that were found to be deleted
691
self.work_tree.unversion(deleted_ids)
693
# If specific files/directories were nominated, it is possible
694
# that some data from outside those needs to be preserved from
695
# the basis tree. For example, if a file x is moved from out of
696
# directory foo into directory bar and the user requests
697
# ``commit foo``, then information about bar/x must also be
700
for path, new_ie in self.basis_inv.iter_entries():
701
if new_ie.file_id in work_inv:
703
if is_inside_any(specific_files, path):
707
self.builder.record_entry_contents(ie, self.parent_invs, path,
710
# Report what was deleted. We could skip this when no deletes are
711
# detected to gain a performance win, but it arguably serves as a
712
# 'safety check' by informing the user whenever anything disappears.
713
for path, ie in self.basis_inv.iter_entries():
714
if ie.file_id not in self.builder.new_inventory:
715
self.reporter.deleted(path)
694
717
def _emit_progress_set_stage(self, name, show_entries=False):
695
718
"""Set the progress stage and emit an update to the progress bar."""