71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
73
from bzrlib.osutils import (get_user_encoding,
74
kind_marker, isdir,isfile, is_inside_any,
72
75
is_inside_or_parent_of_any,
73
quotefn, sha_file, split_lines)
76
minimum_path_selection,
77
quotefn, sha_file, split_lines,
74
80
from bzrlib.testament import Testament
75
from bzrlib.trace import mutter, note, warning
76
from bzrlib.xml5 import serializer_v5
77
from bzrlib.inventory import Inventory, InventoryEntry
81
from bzrlib.trace import mutter, note, warning, is_quiet
82
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
78
83
from bzrlib import symbol_versioning
79
84
from bzrlib.symbol_versioning import (deprecated_passed,
80
85
deprecated_function,
81
86
DEPRECATED_PARAMETER)
82
87
from bzrlib.workingtree import WorkingTree
88
from bzrlib.urlutils import unescape_for_display
86
92
class NullCommitReporter(object):
87
93
"""I report on progress of a commit."""
95
def started(self, revno, revid, location=None):
97
symbol_versioning.warn("As of bzr 1.0 you must pass a location "
98
"to started.", DeprecationWarning,
89
102
def snapshot_change(self, change, path):
92
105
def completed(self, revno, rev_id):
95
def deleted(self, file_id):
98
def escaped(self, escape_count, message):
108
def deleted(self, path):
101
111
def missing(self, path):
115
128
note(format, *args)
117
130
def snapshot_change(self, change, path):
118
if change == 'unchanged':
120
if change == 'added' and path == '':
131
if path == '' and change in ('added', 'modified'):
122
133
self._note("%s %s", change, path)
135
def started(self, revno, rev_id, location=None):
136
if location is not None:
137
location = ' to: ' + unescape_for_display(location, 'utf-8')
139
# When started was added, location was only made optional by
140
# accident. Matt Nordhoff 20071129
141
symbol_versioning.warn("As of bzr 1.0 you must pass a location "
142
"to started.", DeprecationWarning,
145
self._note('Committing%s', location)
124
147
def completed(self, revno, rev_id):
125
148
self._note('Committed revision %d.', revno)
127
def deleted(self, file_id):
128
self._note('deleted %s', file_id)
130
def escaped(self, escape_count, message):
131
self._note("replaced %d control characters in message", escape_count)
150
def deleted(self, path):
151
self._note('deleted %s', path)
133
153
def missing(self, path):
134
154
self._note('missing %s', path)
251
300
raise ConflictsInTree
253
302
# Setup the bound branch variables as needed.
254
self._check_bound_branch()
303
self._check_bound_branch(possible_master_transports)
256
305
# Check that the working tree is up to date
257
old_revno,new_revno = self._check_out_of_date_tree()
306
old_revno, new_revno = self._check_out_of_date_tree()
308
# Complete configuration setup
309
if reporter is not None:
310
self.reporter = reporter
311
elif self.reporter is None:
312
self.reporter = self._select_reporter()
259
313
if self.config is None:
260
314
self.config = self.branch.get_config()
262
# 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.
267
# 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])
316
self._set_specific_file_ids()
272
318
# Setup the progress bar. As the number of files that need to be
273
319
# committed in unknown, progress is reported as stages.
284
330
self.pb.show_count = True
285
331
self.pb.show_bar = True
333
self._gather_parents()
287
334
# After a merge, a selected file commit is not supported.
288
335
# See 'bzr help merge' for an explanation as to why.
289
self.basis_inv = self.basis_tree.inventory
290
self._gather_parents()
291
336
if len(self.parents) > 1 and self.specific_files:
292
337
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
338
# Excludes are a form of selected file commit.
339
if len(self.parents) > 1 and self.exclude:
340
raise errors.CannotCommitSelectedFileMerge(self.exclude)
294
342
# Collect the changes
295
self._set_progress_stage("Collecting changes",
296
entries_title="Directory")
343
self._set_progress_stage("Collecting changes", counter=True)
297
344
self.builder = self.branch.get_commit_builder(self.parents,
298
345
self.config, timestamp, timezone, committer, revprops, rev_id)
348
self.builder.will_record_deletes()
349
# find the location being committed to
350
if self.bound_branch:
351
master_location = self.master_branch.base
353
master_location = self.branch.base
355
# report the start of the commit
356
self.reporter.started(new_revno, self.rev_id, master_location)
300
358
self._update_builder_with_changes()
301
359
self._check_pointless()
310
368
# Prompt the user for a commit message if none provided
311
369
message = message_callback(self)
312
assert isinstance(message, unicode), type(message)
313
370
self.message = message
314
self._escape_commit_message()
316
372
# Add revision data to the local branch
317
373
self.rev_id = self.builder.commit(self.message)
376
mutter("aborting commit write group because of exception:")
377
trace.log_exception_quietly()
378
note("aborting commit write group: %r" % (e,))
319
379
self.builder.abort()
382
self._process_pre_hooks(old_revno, new_revno)
322
384
# Upload revision data to the master.
323
385
# this will propagate merged revisions too if needed.
324
386
if self.bound_branch:
325
387
self._set_progress_stage("Uploading data to master branch")
326
self.master_branch.repository.fetch(self.branch.repository,
327
revision_id=self.rev_id)
328
# now the master has the revision data
329
388
# 'commit' to the master first so a timeout here causes the
330
389
# local branch to be out of date
331
self.master_branch.set_last_revision_info(new_revno,
390
self.master_branch.import_last_revision_info(
391
self.branch.repository, new_revno, self.rev_id)
334
393
# and now do the commit locally.
335
394
self.branch.set_last_revision_info(new_revno, self.rev_id)
337
# Make the working tree up to date with the branch
396
# Make the working tree be up to date with the branch. This
397
# includes automatic changes scheduled to be made to the tree, such
398
# as updating its basis and unversioning paths that were missing.
399
self.work_tree.unversion(self.deleted_ids)
338
400
self._set_progress_stage("Updating the working tree")
339
rev_tree = self.builder.revision_tree()
340
self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
401
self.work_tree.update_basis_by_delta(self.rev_id,
402
self.builder.get_basis_delta())
341
403
self.reporter.completed(new_revno, self.rev_id)
342
self._process_hooks(old_revno, new_revno)
404
self._process_post_hooks(old_revno, new_revno)
345
407
return self.rev_id
347
def _any_real_changes(self):
348
"""Are there real changes between new_inventory and basis?
350
For trees without rich roots, inv.root.revision changes every commit.
351
But if that is the only change, we want to treat it as though there
354
new_entries = self.builder.new_inventory.iter_entries()
355
basis_entries = self.basis_inv.iter_entries()
356
new_path, new_root_ie = new_entries.next()
357
basis_path, basis_root_ie = basis_entries.next()
359
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
360
def ie_equal_no_revision(this, other):
361
return ((this.file_id == other.file_id)
362
and (this.name == other.name)
363
and (this.symlink_target == other.symlink_target)
364
and (this.text_sha1 == other.text_sha1)
365
and (this.text_size == other.text_size)
366
and (this.text_id == other.text_id)
367
and (this.parent_id == other.parent_id)
368
and (this.kind == other.kind)
369
and (this.executable == other.executable)
370
and (this.reference_revision == other.reference_revision)
372
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
375
for new_ie, basis_ie in zip(new_entries, basis_entries):
376
if new_ie != basis_ie:
379
# No actual changes present
409
def _select_reporter(self):
410
"""Select the CommitReporter to use."""
412
return NullCommitReporter()
413
return ReportCommitToLog()
382
415
def _check_pointless(self):
383
416
if self.allow_pointless:
385
418
# A merge with no effect on files
386
419
if len(self.parents) > 1:
388
# work around the fact that a newly-initted tree does differ from its
390
if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
421
# TODO: we could simplify this by using self.builder.basis_delta.
423
# The initial commit adds a root directory, but this in itself is not
424
# a worthwhile commit.
425
if (self.basis_revid == revision.NULL_REVISION and
426
((self.builder.new_inventory is not None and
427
len(self.builder.new_inventory) == 1) or
428
len(self.builder._basis_delta) == 1)):
391
429
raise PointlessCommit()
392
# Shortcut, if the number of entries changes, then we obviously have
394
if len(self.builder.new_inventory) != len(self.basis_inv):
396
# If length == 1, then we only have the root entry. Which means
397
# that there is no real difference (only the root could be different)
398
if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
430
if self.builder.any_changes():
400
432
raise PointlessCommit()
402
def _check_bound_branch(self):
434
def _check_bound_branch(self, possible_master_transports=None):
403
435
"""Check to see if the local branch is bound.
405
437
If it is bound, then most of the commit will actually be
493
538
old_revid = self.parents[0]
495
540
old_revid = bzrlib.revision.NULL_REVISION
496
for hook in Branch.hooks['post_commit']:
542
if hook_name == "pre_commit":
543
future_tree = self.builder.revision_tree()
544
tree_delta = future_tree.changes_from(self.basis_tree,
547
for hook in Branch.hooks[hook_name]:
497
548
# show the running hook in the progress bar. As hooks may
498
549
# end up doing nothing (e.g. because they are not configured by
499
550
# the user) this is still showing progress, not showing overall
500
551
# actions - its up to each plugin to show a UI if it want's to
501
552
# (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)
553
self.pb_stage_name = "Running %s hooks [%s]" % \
554
(hook_name, Branch.hooks.get_hook_name(hook))
504
555
self._emit_progress()
505
556
if 'hooks' in debug.debug_flags:
506
557
mutter("Invoking commit hook: %r", hook)
507
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
558
if hook_name == "post_commit":
559
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
561
elif hook_name == "pre_commit":
562
hook(hook_local, hook_master,
563
old_revno, old_revid, new_revno, self.rev_id,
564
tree_delta, future_tree)
510
566
def _cleanup(self):
511
567
"""Cleanup any open locks, progress bars etc."""
543
599
if self.master_locked:
544
600
self.master_branch.unlock()
546
def _escape_commit_message(self):
547
"""Replace xml-incompatible control characters."""
548
# FIXME: RBC 20060419 this should be done by the revision
549
# serialiser not by commit. Then we can also add an unescaper
550
# in the deserializer and start roundtripping revision messages
551
# precisely. See repository_implementations/test_repository.py
553
# Python strings can include characters that can't be
554
# represented in well-formed XML; escape characters that
555
# aren't listed in the XML specification
556
# (http://www.w3.org/TR/REC-xml/#NT-Char).
557
self.message, escape_count = re.subn(
558
u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
559
lambda match: match.group(0).encode('unicode_escape'),
562
self.reporter.escaped(escape_count, self.message)
564
602
def _gather_parents(self):
565
603
"""Record the parents of a merge for merge detection."""
566
# TODO: Make sure that this list doesn't contain duplicate
604
# TODO: Make sure that this list doesn't contain duplicate
567
605
# entries and the order is preserved when doing this.
568
self.parents = self.work_tree.get_parent_ids()
606
if self.use_record_iter_changes:
608
self.basis_inv = self.basis_tree.inventory
569
609
self.parent_invs = [self.basis_inv]
570
610
for revision in self.parents[1:]:
571
611
if self.branch.repository.has_revision(revision):
578
618
def _update_builder_with_changes(self):
579
619
"""Update the commit builder with the data about what has changed.
621
exclude = self.exclude
622
specific_files = self.specific_files or []
623
mutter("Selecting files for commit with filter %s", specific_files)
626
if self.use_record_iter_changes:
627
iter_changes = self.work_tree.iter_changes(self.basis_tree)
628
iter_changes = self._filter_iter_changes(iter_changes)
629
for file_id, path, fs_hash in self.builder.record_iter_changes(
630
self.work_tree, self.basis_revid, iter_changes):
631
self.work_tree._observed_sha1(file_id, path, fs_hash)
633
# Build the new inventory
634
self._populate_from_inventory()
635
self._record_unselected()
636
self._report_and_accumulate_deletes()
638
def _filter_iter_changes(self, iter_changes):
639
"""Process iter_changes.
641
This method reports on the changes in iter_changes to the user, and
642
converts 'missing' entries in the iter_changes iterator to 'deleted'
643
entries. 'missing' entries have their
645
:param iter_changes: An iter_changes to process.
646
:return: A generator of changes.
648
reporter = self.reporter
649
report_changes = reporter.is_verbose()
651
for change in iter_changes:
653
old_path = change[1][0]
654
new_path = change[1][1]
655
versioned = change[3][1]
657
versioned = change[3][1]
658
if kind is None and versioned:
661
reporter.missing(new_path)
662
deleted_ids.append(change[0])
663
# Reset the new path (None) and new versioned flag (False)
664
change = (change[0], (change[1][0], None), change[2],
665
(change[3][0], False)) + change[4:]
666
elif kind == 'tree-reference':
667
if self.recursive == 'down':
668
self._commit_nested_tree(change[0], change[1][1])
669
if change[3][0] or change[3][1]:
673
reporter.deleted(old_path)
674
elif old_path is None:
675
reporter.snapshot_change('added', new_path)
676
elif old_path != new_path:
677
reporter.renamed('renamed', old_path, new_path)
680
self.work_tree.branch.repository._format.rich_root_data):
681
# Don't report on changes to '' in non rich root
683
reporter.snapshot_change('modified', new_path)
684
self._next_progress_entry()
685
# Unversion IDs that were found to be deleted
686
self.deleted_ids = deleted_ids
688
def _record_unselected(self):
689
# If specific files are selected, then all un-selected files must be
690
# recorded in their previous state. For more details, see
691
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
692
if self.specific_files or self.exclude:
693
specific_files = self.specific_files or []
694
for path, old_ie in self.basis_inv.iter_entries():
695
if old_ie.file_id in self.builder.new_inventory:
696
# already added - skip.
698
if (is_inside_any(specific_files, path)
699
and not is_inside_any(self.exclude, path)):
700
# was inside the selected path, and not excluded - if not
701
# present it has been deleted so skip.
703
# From here down it was either not selected, or was excluded:
704
# We preserve the entry unaltered.
706
# Note: specific file commits after a merge are currently
707
# prohibited. This test is for sanity/safety in case it's
708
# required after that changes.
709
if len(self.parents) > 1:
711
self.builder.record_entry_contents(ie, self.parent_invs, path,
712
self.basis_tree, None)
714
def _report_and_accumulate_deletes(self):
715
if (isinstance(self.basis_inv, Inventory)
716
and isinstance(self.builder.new_inventory, Inventory)):
717
# the older Inventory classes provide a _byid dict, and building a
718
# set from the keys of this dict is substantially faster than even
719
# getting a set of ids from the inventory
721
# <lifeless> set(dict) is roughly the same speed as
722
# set(iter(dict)) and both are significantly slower than
724
deleted_ids = set(self.basis_inv._byid.keys()) - \
725
set(self.builder.new_inventory._byid.keys())
727
deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
729
self.any_entries_deleted = True
730
deleted = [(self.basis_tree.id2path(file_id), file_id)
731
for file_id in deleted_ids]
733
# XXX: this is not quite directory-order sorting
734
for path, file_id in deleted:
735
self.builder.record_delete(path, file_id)
736
self.reporter.deleted(path)
738
def _check_strict(self):
739
# XXX: when we use iter_changes this would likely be faster if
740
# iter_changes would check for us (even in the presence of
743
# raise an exception as soon as we find a single unknown.
744
for unknown in self.work_tree.unknowns():
745
raise StrictCommitFailed()
747
def _populate_from_inventory(self):
748
"""Populate the CommitBuilder by walking the working tree inventory."""
581
749
# Build the revision inventory.
583
751
# This starts by creating a new empty inventory. Depending on
584
752
# which files are selected for commit, and what is present in the
585
# current tree, the new inventory is populated. inventory entries
753
# current tree, the new inventory is populated. inventory entries
586
754
# which are candidates for modification have their revision set to
587
755
# None; inventory entries that are carried over untouched have their
588
756
# revision set to their prior value.
593
761
# ADHB 11-07-2006
595
763
specific_files = self.specific_files
596
mutter("Selecting files for commit with filter %s", specific_files)
598
# Check and warn about old CommitBuilders
599
if not self.builder.record_root_entry:
600
symbol_versioning.warn('CommitBuilders should support recording'
601
' the root entry as of bzr 0.10.', DeprecationWarning,
603
self.builder.new_inventory.add(self.basis_inv.root.copy())
605
# Build the new inventory
606
self._populate_from_inventory(specific_files)
608
# If specific files are selected, then all un-selected files must be
609
# recorded in their previous state. For more details, see
610
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
612
for path, new_ie in self.basis_inv.iter_entries():
613
if new_ie.file_id in self.builder.new_inventory:
615
if is_inside_any(specific_files, path):
619
self.builder.record_entry_contents(ie, self.parent_invs, path,
622
# Report what was deleted. We could skip this when no deletes are
623
# detected to gain a performance win, but it arguably serves as a
624
# 'safety check' by informing the user whenever anything disappears.
625
for path, ie in self.basis_inv.iter_entries():
626
if ie.file_id not in self.builder.new_inventory:
627
self.reporter.deleted(path)
629
def _populate_from_inventory(self, specific_files):
630
"""Populate the CommitBuilder by walking the working tree inventory."""
632
# raise an exception as soon as we find a single unknown.
633
for unknown in self.work_tree.unknowns():
634
raise StrictCommitFailed()
764
exclude = self.exclude
765
report_changes = self.reporter.is_verbose()
637
deleted_paths = set()
767
# A tree of paths that have been deleted. E.g. if foo/bar has been
768
# deleted, then we have {'foo':{'bar':{}}}
770
# XXX: Note that entries may have the wrong kind because the entry does
771
# not reflect the status on disk.
638
772
work_inv = self.work_tree.inventory
639
assert work_inv.root is not None
640
entries = work_inv.iter_entries()
641
if not self.builder.record_root_entry:
773
# NB: entries will include entries within the excluded ids/paths
774
# because iter_entries_by_dir has no 'exclude' facility today.
775
entries = work_inv.iter_entries_by_dir(
776
specific_file_ids=self.specific_file_ids, yield_parents=True)
643
777
for path, existing_ie in entries:
644
778
file_id = existing_ie.file_id
645
779
name = existing_ie.name
646
780
parent_id = existing_ie.parent_id
647
781
kind = existing_ie.kind
648
if kind == 'directory':
649
self._next_progress_entry()
651
782
# Skip files that have been deleted from the working tree.
652
# The deleted files/directories are also recorded so they
653
# can be explicitly unversioned later. Note that when a
654
# filter of specific files is given, we must only skip/record
655
# deleted files matching that filter.
656
if is_inside_any(deleted_paths, path):
783
# The deleted path ids are also recorded so they can be explicitly
786
path_segments = splitpath(path)
787
deleted_dict = deleted_paths
788
for segment in path_segments:
789
deleted_dict = deleted_dict.get(segment, None)
791
# We either took a path not present in the dict
792
# (deleted_dict was None), or we've reached an empty
793
# child dir in the dict, so are now a sub-path.
797
if deleted_dict is not None:
798
# the path has a deleted parent, do not add it.
800
if exclude and is_inside_any(exclude, path):
801
# Skip excluded paths. Excluded paths are processed by
802
# _update_builder_with_changes.
804
content_summary = self.work_tree.path_content_summary(path)
805
# Note that when a filter of specific files is given, we must only
806
# skip/record deleted files matching that filter.
658
807
if not specific_files or is_inside_any(specific_files, path):
659
if not self.work_tree.has_filename(path):
660
deleted_paths.add(path)
808
if content_summary[0] == 'missing':
809
if not deleted_paths:
810
# path won't have been split yet.
811
path_segments = splitpath(path)
812
deleted_dict = deleted_paths
813
for segment in path_segments:
814
deleted_dict = deleted_dict.setdefault(segment, {})
661
815
self.reporter.missing(path)
816
self._next_progress_entry()
662
817
deleted_ids.append(file_id)
665
kind = self.work_tree.kind(file_id)
666
# TODO: specific_files filtering before nested tree processing
667
if kind == 'tree-reference' and self.recursive == 'down':
668
self._commit_nested_tree(file_id, path)
669
except errors.NoSuchFile:
819
# TODO: have the builder do the nested commit just-in-time IF and
821
if content_summary[0] == 'tree-reference':
822
# enforce repository nested tree policy.
823
if (not self.work_tree.supports_tree_reference() or
824
# repository does not support it either.
825
not self.branch.repository._format.supports_tree_reference):
826
content_summary = ('directory',) + content_summary[1:]
827
kind = content_summary[0]
828
# TODO: specific_files filtering before nested tree processing
829
if kind == 'tree-reference':
830
if self.recursive == 'down':
831
nested_revision_id = self._commit_nested_tree(
833
content_summary = content_summary[:3] + (
836
content_summary = content_summary[:3] + (
837
self.work_tree.get_reference_revision(file_id),)
672
839
# Record an entry for this item
673
840
# Note: I don't particularly want to have the existing_ie
674
841
# parameter but the test suite currently (28-Jun-07) breaks
675
842
# without it thanks to a unicode normalisation issue. :-(
676
definitely_changed = kind != existing_ie.kind
843
definitely_changed = kind != existing_ie.kind
677
844
self._record_entry(path, file_id, specific_files, kind, name,
678
parent_id, definitely_changed, existing_ie)
845
parent_id, definitely_changed, existing_ie, report_changes,
680
848
# Unversion IDs that were found to be deleted
681
self.work_tree.unversion(deleted_ids)
849
self.deleted_ids = deleted_ids
683
851
def _commit_nested_tree(self, file_id, path):
684
852
"Commit a nested tree."
703
871
strict=self.strict, verbose=self.verbose,
704
872
local=self.local, reporter=self.reporter)
705
873
except errors.PointlessCommit:
874
return self.work_tree.get_reference_revision(file_id)
708
876
def _record_entry(self, path, file_id, specific_files, kind, name,
709
parent_id, definitely_changed, existing_ie=None):
877
parent_id, definitely_changed, existing_ie, report_changes,
710
879
"Record the new inventory entry for a path if any."
711
880
# mutter('check %s {%s}', path, file_id)
712
if (not specific_files or
713
is_inside_or_parent_of_any(specific_files, path)):
714
# mutter('%s selected for commit', path)
715
if definitely_changed or existing_ie is None:
716
ie = inventory.make_entry(kind, name, parent_id, file_id)
718
ie = existing_ie.copy()
881
# mutter('%s selected for commit', path)
882
if definitely_changed or existing_ie is None:
883
ie = make_entry(kind, name, parent_id, file_id)
721
# mutter('%s not selected for commit', path)
722
if self.basis_inv.has_id(file_id):
723
ie = self.basis_inv[file_id].copy()
725
# this entry is new and not being committed
728
self.builder.record_entry_contents(ie, self.parent_invs,
729
path, self.work_tree)
885
ie = existing_ie.copy()
887
# For carried over entries we don't care about the fs hash - the repo
888
# isn't generating a sha, so we're not saving computation time.
889
_, _, fs_hash = self.builder.record_entry_contents(
890
ie, self.parent_invs, path, self.work_tree, content_summary)
730
892
self._report_change(ie, path)
894
self.work_tree._observed_sha1(ie.file_id, path, fs_hash)
733
897
def _report_change(self, ie, path):
743
907
change = ie.describe_change(basis_ie, ie)
744
if change in (InventoryEntry.RENAMED,
908
if change in (InventoryEntry.RENAMED,
745
909
InventoryEntry.MODIFIED_AND_RENAMED):
746
910
old_path = self.basis_inv.id2path(ie.file_id)
747
911
self.reporter.renamed(change, old_path, path)
912
self._next_progress_entry()
914
if change == 'unchanged':
749
916
self.reporter.snapshot_change(change, path)
917
self._next_progress_entry()
751
def _set_progress_stage(self, name, entries_title=None):
919
def _set_progress_stage(self, name, counter=False):
752
920
"""Set the progress stage and emit an update to the progress bar."""
753
921
self.pb_stage_name = name
754
922
self.pb_stage_count += 1
755
self.pb_entries_title = entries_title
756
if entries_title is not None:
757
924
self.pb_entries_count = 0
758
self.pb_entries_total = '?'
926
self.pb_entries_count = None
759
927
self._emit_progress()
761
929
def _next_progress_entry(self):
764
932
self._emit_progress()
766
934
def _emit_progress(self):
767
if self.pb_entries_title:
768
if self.pb_entries_total == '?':
769
text = "%s [%s %d] - Stage" % (self.pb_stage_name,
770
self.pb_entries_title, self.pb_entries_count)
772
text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
773
self.pb_entries_title, self.pb_entries_count,
774
str(self.pb_entries_total))
935
if self.pb_entries_count is not None:
936
text = "%s [%d] - Stage" % (self.pb_stage_name,
937
self.pb_entries_count)
776
text = "%s - Stage" % (self.pb_stage_name)
939
text = "%s - Stage" % (self.pb_stage_name, )
777
940
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
942
def _set_specific_file_ids(self):
943
"""populate self.specific_file_ids if we will use it."""
944
if not self.use_record_iter_changes:
945
# If provided, ensure the specified files are versioned
946
if self.specific_files is not None:
947
# Note: This routine is being called because it raises
948
# PathNotVersionedError as a side effect of finding the IDs. We
949
# later use the ids we found as input to the working tree
950
# inventory iterator, so we only consider those ids rather than
951
# examining the whole tree again.
952
# XXX: Dont we have filter_unversioned to do this more
954
self.specific_file_ids = tree.find_ids_across_trees(
955
self.specific_files, [self.basis_tree, self.work_tree])
957
self.specific_file_ids = None