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