150
153
def deleted(self, path):
151
154
self._note('deleted %s', path)
156
def escaped(self, escape_count, message):
157
self._note("replaced %d control characters in message", escape_count)
153
159
def missing(self, path):
154
160
self._note('missing %s', path)
204
210
"""Commit working copy as a new revision.
206
212
:param message: the commit message (it or message_callback is required)
207
:param message_callback: A callback: message = message_callback(cmt_obj)
209
214
:param timestamp: if not None, seconds-since-epoch for a
210
215
postdated/predated commit.
212
:param specific_files: If not None, commit only those files. An empty
213
list means 'commit no files'.
217
:param specific_files: If true, commit only those files.
215
219
:param rev_id: If set, use this as the new revision id.
216
220
Useful for test or import commands that need to tightly
265
269
self.master_locked = False
266
270
self.recursive = recursive
267
271
self.rev_id = None
268
# self.specific_files is None to indicate no filter, or any iterable to
269
# indicate a filter - [] means no files at all, as per iter_changes.
270
272
if specific_files is not None:
271
273
self.specific_files = sorted(
272
274
minimum_path_selection(specific_files))
288
290
# the command line parameters, and the repository has fast delta
289
291
# generation. See bug 347649.
290
292
self.use_record_iter_changes = (
293
not self.specific_files and
291
294
not self.exclude and
292
295
not self.branch.repository._format.supports_tree_reference and
293
296
(self.branch.repository._format.fast_deltas or
335
338
self._gather_parents()
336
339
# After a merge, a selected file commit is not supported.
337
340
# See 'bzr help merge' for an explanation as to why.
338
if len(self.parents) > 1 and self.specific_files is not None:
341
if len(self.parents) > 1 and self.specific_files:
339
342
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
340
343
# Excludes are a form of selected file commit.
341
344
if len(self.parents) > 1 and self.exclude:
370
373
# Prompt the user for a commit message if none provided
371
374
message = message_callback(self)
372
375
self.message = message
376
self._escape_commit_message()
374
378
# Add revision data to the local branch
375
379
self.rev_id = self.builder.commit(self.message)
395
399
# and now do the commit locally.
396
400
self.branch.set_last_revision_info(new_revno, self.rev_id)
398
# Make the working tree be up to date with the branch. This
399
# includes automatic changes scheduled to be made to the tree, such
400
# as updating its basis and unversioning paths that were missing.
401
self.work_tree.unversion(self.deleted_ids)
402
# Make the working tree up to date with the branch
402
403
self._set_progress_stage("Updating the working tree")
403
404
self.work_tree.update_basis_by_delta(self.rev_id,
404
405
self.builder.get_basis_delta())
601
602
if self.master_locked:
602
603
self.master_branch.unlock()
605
def _escape_commit_message(self):
606
"""Replace xml-incompatible control characters."""
607
# FIXME: RBC 20060419 this should be done by the revision
608
# serialiser not by commit. Then we can also add an unescaper
609
# in the deserializer and start roundtripping revision messages
610
# precisely. See repository_implementations/test_repository.py
611
self.message, escape_count = xml_serializer.escape_invalid_chars(
614
self.reporter.escaped(escape_count, self.message)
604
616
def _gather_parents(self):
605
617
"""Record the parents of a merge for merge detection."""
606
618
# TODO: Make sure that this list doesn't contain duplicate
621
633
"""Update the commit builder with the data about what has changed.
623
635
exclude = self.exclude
624
specific_files = self.specific_files
636
specific_files = self.specific_files or []
625
637
mutter("Selecting files for commit with filter %s", specific_files)
627
639
self._check_strict()
628
640
if self.use_record_iter_changes:
629
iter_changes = self.work_tree.iter_changes(self.basis_tree,
630
specific_files=specific_files)
641
iter_changes = self.work_tree.iter_changes(self.basis_tree)
631
642
iter_changes = self._filter_iter_changes(iter_changes)
632
643
for file_id, path, fs_hash in self.builder.record_iter_changes(
633
644
self.work_tree, self.basis_revid, iter_changes):
686
697
reporter.snapshot_change('modified', new_path)
687
698
self._next_progress_entry()
688
699
# Unversion IDs that were found to be deleted
689
self.deleted_ids = deleted_ids
700
self.work_tree.unversion(deleted_ids)
691
702
def _record_unselected(self):
692
703
# If specific files are selected, then all un-selected files must be
805
816
# _update_builder_with_changes.
807
818
content_summary = self.work_tree.path_content_summary(path)
808
kind = content_summary[0]
809
819
# Note that when a filter of specific files is given, we must only
810
820
# skip/record deleted files matching that filter.
811
821
if not specific_files or is_inside_any(specific_files, path):
812
if kind == 'missing':
822
if content_summary[0] == 'missing':
813
823
if not deleted_paths:
814
824
# path won't have been split yet.
815
825
path_segments = splitpath(path)
823
833
# TODO: have the builder do the nested commit just-in-time IF and
824
834
# only if needed.
825
if kind == 'tree-reference':
835
if content_summary[0] == 'tree-reference':
826
836
# enforce repository nested tree policy.
827
837
if (not self.work_tree.supports_tree_reference() or
828
838
# repository does not support it either.
829
839
not self.branch.repository._format.supports_tree_reference):
831
content_summary = (kind, None, None, None)
832
elif self.recursive == 'down':
840
content_summary = ('directory',) + content_summary[1:]
841
kind = content_summary[0]
842
# TODO: specific_files filtering before nested tree processing
843
if kind == 'tree-reference':
844
if self.recursive == 'down':
833
845
nested_revision_id = self._commit_nested_tree(
835
content_summary = (kind, None, None, nested_revision_id)
847
content_summary = content_summary[:3] + (
837
nested_revision_id = self.work_tree.get_reference_revision(file_id)
838
content_summary = (kind, None, None, nested_revision_id)
850
content_summary = content_summary[:3] + (
851
self.work_tree.get_reference_revision(file_id),)
840
853
# Record an entry for this item
841
854
# Note: I don't particularly want to have the existing_ie
849
862
# Unversion IDs that were found to be deleted
850
self.deleted_ids = deleted_ids
863
self.work_tree.unversion(deleted_ids)
852
865
def _commit_nested_tree(self, file_id, path):
853
866
"Commit a nested tree."