56
46
# merges from, then it should still be reported as newly added
57
47
# relative to the basis revision.
59
# TODO: Do checks that the tree can be committed *before* running the
60
# editor; this should include checks for a pointless commit and for
61
# unknown or missing files.
63
# TODO: If commit fails, leave the message in a file somewhere.
49
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
# the rest of the code; add a deprecation of the old name.
72
from binascii import hexlify
73
57
from cStringIO import StringIO
75
from bzrlib.atomicfile import AtomicFile
76
from bzrlib.osutils import (local_time_offset,
77
rand_bytes, compact_date,
78
kind_marker, is_inside_any, quotefn,
79
sha_file, isdir, isfile,
64
from bzrlib.branch import Branch
81
65
import bzrlib.config
82
import bzrlib.errors as errors
83
66
from bzrlib.errors import (BzrError, PointlessCommit,
88
from bzrlib.revision import Revision
70
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
71
is_inside_or_parent_of_any,
72
quotefn, sha_file, split_lines)
89
73
from bzrlib.testament import Testament
90
74
from bzrlib.trace import mutter, note, warning
91
75
from bzrlib.xml5 import serializer_v5
92
from bzrlib.inventory import Inventory, ROOT_ID
93
from bzrlib.symbol_versioning import *
76
from bzrlib.inventory import Inventory, InventoryEntry
77
from bzrlib import symbol_versioning
78
from bzrlib.symbol_versioning import (deprecated_passed,
94
81
from bzrlib.workingtree import WorkingTree
97
@deprecated_function(zero_seven)
98
def commit(*args, **kwargs):
99
"""Commit a new revision to a branch.
101
Function-style interface for convenience of old callers.
103
New code should use the Commit class instead.
105
## XXX: Remove this in favor of Branch.commit?
106
Commit().commit(*args, **kwargs)
109
85
class NullCommitReporter(object):
124
100
def missing(self, path):
103
def renamed(self, change, old_path, new_path):
128
107
class ReportCommitToLog(NullCommitReporter):
109
def _note(self, format, *args):
112
Subclasses may choose to override this method.
130
116
def snapshot_change(self, change, path):
131
117
if change == 'unchanged':
133
note("%s %s", change, path)
119
if change == 'added' and path == '':
121
self._note("%s %s", change, path)
135
123
def completed(self, revno, rev_id):
136
note('Committed revision %d.', revno)
124
self._note('Committed revision %d.', revno)
138
126
def deleted(self, file_id):
139
note('deleted %s', file_id)
127
self._note('deleted %s', file_id)
141
129
def escaped(self, escape_count, message):
142
note("replaced %d control characters in message", escape_count)
130
self._note("replaced %d control characters in message", escape_count)
144
132
def missing(self, path):
145
note('missing %s', path)
133
self._note('missing %s', path)
135
def renamed(self, change, old_path, new_path):
136
self._note('%s %s => %s', change, old_path, new_path)
148
139
class Commit(object):
183
171
working_tree=None,
175
message_callback=None,
187
177
"""Commit working copy as a new revision.
189
branch -- the deprecated branch to commit to. New callers should pass in
192
message -- the commit message, a mandatory parameter
194
timestamp -- if not None, seconds-since-epoch for a
195
postdated/predated commit.
197
specific_files -- If true, commit only those files.
199
rev_id -- If set, use this as the new revision id.
179
:param message: the commit message (it or message_callback is required)
181
:param timestamp: if not None, seconds-since-epoch for a
182
postdated/predated commit.
184
:param specific_files: If true, commit only those files.
186
:param rev_id: If set, use this as the new revision id.
200
187
Useful for test or import commands that need to tightly
201
188
control what revisions are assigned. If you duplicate
202
189
a revision id that exists elsewhere it is your own fault.
203
190
If null (default), a time/random revision id is generated.
205
allow_pointless -- If true (default), commit even if nothing
192
:param allow_pointless: If true (default), commit even if nothing
206
193
has changed and no merges are recorded.
208
strict -- If true, don't allow a commit if the working tree
195
:param strict: If true, don't allow a commit if the working tree
209
196
contains unknown files.
211
revprops -- Properties for new revision
198
:param revprops: Properties for new revision
212
199
:param local: Perform a local only commit.
200
:param recursive: If set to 'down', commit in any subtrees that have
201
pending changes of any sort during this commit.
214
203
mutter('preparing to commit')
216
if deprecated_passed(branch):
217
warn("Commit.commit (branch, ...): The branch parameter is "
218
"deprecated as of bzr 0.8. Please use working_tree= instead.",
219
DeprecationWarning, stacklevel=2)
221
self.work_tree = self.branch.bzrdir.open_workingtree()
222
elif working_tree is None:
223
raise BzrError("One of branch and working_tree must be passed into commit().")
205
if working_tree is None:
206
raise BzrError("working_tree must be passed into commit().")
225
208
self.work_tree = working_tree
226
209
self.branch = self.work_tree.branch
228
raise BzrError("The message keyword parameter is required for commit().")
210
if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
211
if not self.branch.repository.supports_rich_root():
212
raise errors.RootNotRich()
213
if message_callback is None:
214
if message is not None:
215
if isinstance(message, str):
216
message = message.decode(bzrlib.user_encoding)
217
message_callback = lambda x: message
219
raise BzrError("The message or message_callback keyword"
220
" parameter is required for commit().")
230
self.weave_store = self.branch.repository.weave_store
231
222
self.bound_branch = None
232
223
self.local = local
233
224
self.master_branch = None
234
225
self.master_locked = False
236
227
self.specific_files = specific_files
237
228
self.allow_pointless = allow_pointless
239
if revprops is not None:
240
self.revprops.update(revprops)
229
self.recursive = recursive
230
self.revprops = revprops
231
self.message_callback = message_callback
232
self.timestamp = timestamp
233
self.timezone = timezone
234
self.committer = committer
236
self.verbose = verbose
242
238
if reporter is None and self.reporter is None:
243
239
self.reporter = NullCommitReporter()
245
241
self.reporter = reporter
247
243
self.work_tree.lock_write()
244
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
245
self.basis_tree = self.work_tree.basis_tree()
246
self.basis_tree.lock_read()
249
# setup the bound branch variables as needed.
248
# Cannot commit with conflicts present.
249
if len(self.work_tree.conflicts()) > 0:
250
raise ConflictsInTree
252
# Setup the bound branch variables as needed.
250
253
self._check_bound_branch()
252
# check for out of date working trees
253
# if we are bound, then self.branch is the master branch and this
254
# test is thus all we need.
255
if self.work_tree.last_revision() != self.master_branch.last_revision():
256
raise errors.OutOfDateTree(self.work_tree)
255
# Check that the working tree is up to date
256
old_revno,new_revno = self._check_out_of_date_tree()
259
259
# raise an exception as soon as we find a single unknown.
260
260
for unknown in self.work_tree.unknowns():
261
261
raise StrictCommitFailed()
263
if timestamp is None:
264
self.timestamp = time.time()
266
self.timestamp = long(timestamp)
268
263
if self.config is None:
269
self.config = bzrlib.config.BranchConfig(self.branch)
272
self.rev_id = _gen_revision_id(self.config, self.timestamp)
276
if committer is None:
277
self.committer = self.config.username()
279
assert isinstance(committer, basestring), type(committer)
280
self.committer = committer
283
self.timezone = local_time_offset()
285
self.timezone = int(timezone)
287
if isinstance(message, str):
288
message = message.decode(bzrlib.user_encoding)
289
assert isinstance(message, unicode), type(message)
290
self.message = message
291
self._escape_commit_message()
264
self.config = self.branch.get_config()
293
266
self.work_inv = self.work_tree.inventory
294
self.basis_tree = self.work_tree.basis_tree()
295
267
self.basis_inv = self.basis_tree.inventory
268
if specific_files is not None:
269
# Ensure specified files are versioned
270
# (We don't actually need the ids here)
271
# XXX: Dont we have filter_unversioned to do this more
273
tree.find_ids_across_trees(specific_files,
274
[self.basis_tree, self.work_tree])
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
297
291
self._gather_parents()
298
292
if len(self.parents) > 1 and self.specific_files:
299
raise NotImplementedError('selected-file commit of merges is not supported yet')
300
self._check_parents_present()
293
raise errors.CannotCommitSelectedFileMerge(self.specific_files)
295
# Build the new inventory
296
self._emit_progress_set_stage("Collecting changes", show_entries=True)
297
self.builder = self.branch.get_commit_builder(self.parents,
298
self.config, timestamp, timezone, committer, revprops, rev_id)
302
299
self._remove_deleted()
303
300
self._populate_new_inv()
304
self._store_snapshot()
305
301
self._report_deletes()
307
if not (self.allow_pointless
308
or len(self.parents) > 1
309
or self.new_inv != self.basis_inv):
310
raise PointlessCommit()
312
if len(self.work_tree.conflicts())>0:
313
raise ConflictsInTree
315
self.inv_sha1 = self.branch.repository.add_inventory(
320
self._make_revision()
321
# revision data is in the local branch now.
302
self._check_pointless()
304
# TODO: Now the new inventory is known, check for conflicts and
305
# prompt the user for a commit message.
306
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
307
# weave lines, because nothing should be recorded until it is known
308
# that commit will succeed.
309
self._emit_progress_set_stage("Saving data locally")
310
self.builder.finish_inventory()
311
message = message_callback(self)
312
assert isinstance(message, unicode), type(message)
313
self.message = message
314
self._escape_commit_message()
316
# Add revision data to the local branch
317
self.rev_id = self.builder.commit(self.message)
323
# upload revision data to the master.
324
# this will propogate merged revisions too if needed.
319
# Upload revision data to the master.
320
# this will propagate merged revisions too if needed.
325
321
if self.bound_branch:
322
self._emit_progress_set_stage("Uploading data to master branch")
326
323
self.master_branch.repository.fetch(self.branch.repository,
327
324
revision_id=self.rev_id)
328
325
# now the master has the revision data
329
# 'commit' to the master first so a timeout here causes the local
330
# branch to be out of date
331
self.master_branch.append_revision(self.rev_id)
326
# 'commit' to the master first so a timeout here causes the
327
# local branch to be out of date
328
self.master_branch.set_last_revision_info(new_revno,
333
331
# and now do the commit locally.
334
self.branch.append_revision(self.rev_id)
336
self.work_tree.set_pending_merges([])
337
self.work_tree.set_last_revision(self.rev_id)
338
# now the work tree is up to date with the branch
340
self.reporter.completed(self.branch.revno(), self.rev_id)
341
if self.config.post_commit() is not None:
342
hooks = self.config.post_commit().split(' ')
343
# this would be nicer with twisted.python.reflect.namedAny
345
result = eval(hook + '(branch, rev_id)',
346
{'branch':self.branch,
348
'rev_id':self.rev_id})
332
self.branch.set_last_revision_info(new_revno, self.rev_id)
334
# Make the working tree up to date with the branch
335
self._emit_progress_set_stage("Updating the working tree")
336
rev_tree = self.builder.revision_tree()
337
self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
338
self.reporter.completed(new_revno, self.rev_id)
340
# Process the post commit hooks, if any
341
self._emit_progress_set_stage("Running post commit hooks")
342
self._process_hooks(old_revno, new_revno)
350
self._cleanup_bound_branch()
351
self.work_tree.unlock()
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
382
def _check_pointless(self):
383
if self.allow_pointless:
385
# A merge with no effect on files
386
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:
391
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()):
400
raise PointlessCommit()
353
402
def _check_bound_branch(self):
354
403
"""Check to see if the local branch is bound.
391
440
self.bound_branch = self.branch
392
441
self.master_branch.lock_write()
393
442
self.master_locked = True
395
#### # Check to see if we have any pending merges. If we do
396
#### # those need to be pushed into the master branch
397
#### pending_merges = self.work_tree.pending_merges()
398
#### if pending_merges:
399
#### for revision_id in pending_merges:
400
#### self.master_branch.repository.fetch(self.bound_branch.repository,
401
#### revision_id=revision_id)
444
def _check_out_of_date_tree(self):
445
"""Check that the working tree is up to date.
447
:return: old_revision_number,new_revision_number tuple
450
first_tree_parent = self.work_tree.get_parent_ids()[0]
452
# if there are no parents, treat our parent as 'None'
453
# this is so that we still consider the master branch
454
# - in a checkout scenario the tree may have no
455
# parents but the branch may do.
456
first_tree_parent = bzrlib.revision.NULL_REVISION
457
old_revno, master_last = self.master_branch.last_revision_info()
458
if master_last != first_tree_parent:
459
if master_last != bzrlib.revision.NULL_REVISION:
460
raise errors.OutOfDateTree(self.work_tree)
461
if self.branch.repository.has_revision(first_tree_parent):
462
new_revno = old_revno + 1
464
# ghost parents never appear in revision history.
466
return old_revno,new_revno
468
def _process_hooks(self, old_revno, new_revno):
469
"""Process any registered commit hooks."""
470
# old style commit hooks - should be deprecated ? (obsoleted in
472
if self.config.post_commit() is not None:
473
hooks = self.config.post_commit().split(' ')
474
# this would be nicer with twisted.python.reflect.namedAny
476
result = eval(hook + '(branch, rev_id)',
477
{'branch':self.branch,
479
'rev_id':self.rev_id})
480
# new style commit hooks:
481
if not self.bound_branch:
482
hook_master = self.branch
485
hook_master = self.master_branch
486
hook_local = self.branch
487
# With bound branches, when the master is behind the local branch,
488
# the 'old_revno' and old_revid values here are incorrect.
489
# XXX: FIXME ^. RBC 20060206
491
old_revid = self.parents[0]
493
old_revid = bzrlib.revision.NULL_REVISION
494
for hook in Branch.hooks['post_commit']:
495
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
499
"""Cleanup any open locks, progress bars etc."""
500
cleanups = [self._cleanup_bound_branch,
501
self.basis_tree.unlock,
502
self.work_tree.unlock,
504
found_exception = None
505
for cleanup in cleanups:
508
# we want every cleanup to run no matter what.
509
# so we have a catchall here, but we will raise the
510
# last encountered exception up the stack: and
511
# typically this will be useful enough.
514
if found_exception is not None:
515
# don't do a plan raise, because the last exception may have been
516
# trashed, e is our sure-to-work exception even though it loses the
517
# full traceback. XXX: RBC 20060421 perhaps we could check the
518
# exc_info and if its the same one do a plain raise otherwise
519
# 'raise e' as we do now.
403
522
def _cleanup_bound_branch(self):
404
523
"""Executed at the end of a try/finally to cleanup a bound branch.
428
552
def _gather_parents(self):
429
553
"""Record the parents of a merge for merge detection."""
430
pending_merges = self.work_tree.pending_merges()
432
self.parent_invs = []
433
self.present_parents = []
434
precursor_id = self.branch.last_revision()
436
self.parents.append(precursor_id)
437
self.parents += pending_merges
438
for revision in self.parents:
554
# TODO: Make sure that this list doesn't contain duplicate
555
# entries and the order is preserved when doing this.
556
self.parents = self.work_tree.get_parent_ids()
557
self.parent_invs = [self.basis_inv]
558
for revision in self.parents[1:]:
439
559
if self.branch.repository.has_revision(revision):
560
mutter('commit parent revision {%s}', revision)
440
561
inventory = self.branch.repository.get_inventory(revision)
441
562
self.parent_invs.append(inventory)
442
self.present_parents.append(revision)
444
def _check_parents_present(self):
445
for parent_id in self.parents:
446
mutter('commit parent revision {%s}', parent_id)
447
if not self.branch.repository.has_revision(parent_id):
448
if parent_id == self.branch.last_revision():
449
warning("parent is missing %r", parent_id)
450
raise HistoryMissing(self.branch, 'revision', parent_id)
452
mutter("commit will ghost revision %r", parent_id)
454
def _make_revision(self):
455
"""Record a new revision object for this commit."""
456
rev = Revision(timestamp=self.timestamp,
457
timezone=self.timezone,
458
committer=self.committer,
459
message=self.message,
460
inventory_sha1=self.inv_sha1,
461
revision_id=self.rev_id,
462
properties=self.revprops)
463
rev.parent_ids = self.parents
464
self.branch.repository.add_revision(self.rev_id, rev, self.new_inv, self.config)
564
mutter('commit parent ghost revision {%s}', revision)
466
566
def _remove_deleted(self):
467
567
"""Remove deleted files from the working inventories.
477
577
specific = self.specific_files
579
deleted_paths = set()
479
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.
480
584
if specific and not is_inside_any(specific, path):
482
586
if not self.work_tree.has_filename(path):
587
deleted_paths.add(path)
483
588
self.reporter.missing(path)
484
deleted_ids.append((path, ie.file_id))
486
deleted_ids.sort(reverse=True)
487
for path, file_id in deleted_ids:
488
del self.work_inv[file_id]
489
self.work_tree._write_inventory(self.work_inv)
491
def _store_snapshot(self):
492
"""Pass over inventory and record a snapshot.
494
Entries get a new revision when they are modified in
495
any way, which includes a merge with a new set of
496
parents that have the same entry.
498
# XXX: Need to think more here about when the user has
499
# made a specific decision on a particular value -- c.f.
501
for path, ie in self.new_inv.iter_entries():
502
previous_entries = ie.find_previous_heads(
505
self.branch.repository.get_transaction())
506
if ie.revision is None:
507
change = ie.snapshot(self.rev_id, path, previous_entries,
508
self.work_tree, self.weave_store,
509
self.branch.get_transaction())
512
self.reporter.snapshot_change(change, path)
589
deleted_ids.append(ie.file_id)
590
self.work_tree.unversion(deleted_ids)
514
592
def _populate_new_inv(self):
515
593
"""Build revision inventory.
521
599
None; inventory entries that are carried over untouched have their
522
600
revision set to their prior value.
602
# ESEPARATIONOFCONCERNS: this function is diffing and using the diff
603
# results to create a new inventory at the same time, which results
604
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
524
606
mutter("Selecting files for commit with filter %s", self.specific_files)
525
self.new_inv = Inventory(revision_id=self.rev_id)
526
for path, new_ie in self.work_inv.iter_entries():
607
assert self.work_inv.root is not None
608
entries = self.work_inv.iter_entries()
609
if not self.builder.record_root_entry:
610
symbol_versioning.warn('CommitBuilders should support recording'
611
' the root entry as of bzr 0.10.', DeprecationWarning,
613
self.builder.new_inventory.add(self.basis_inv.root.copy())
615
self.pb_entries_total = len(self.work_inv)
616
for path, new_ie in entries:
617
self._emit_progress_next_entry()
527
618
file_id = new_ie.file_id
528
mutter('check %s {%s}', path, new_ie.file_id)
529
if self.specific_files:
530
if not is_inside_any(self.specific_files, path):
531
mutter('%s not selected for commit', path)
532
self._carry_entry(file_id)
620
kind = self.work_tree.kind(file_id)
621
if kind == 'tree-reference' and self.recursive == 'down':
622
# nested tree: commit in it
623
sub_tree = WorkingTree.open(self.work_tree.abspath(path))
624
# FIXME: be more comprehensive here:
625
# this works when both trees are in --trees repository,
626
# but when both are bound to a different repository,
627
# it fails; a better way of approaching this is to
628
# finally implement the explicit-caches approach design
629
# a while back - RBC 20070306.
630
if (sub_tree.branch.repository.bzrdir.root_transport.base
632
self.work_tree.branch.repository.bzrdir.root_transport.base):
633
sub_tree.branch.repository = \
634
self.work_tree.branch.repository
636
sub_tree.commit(message=None, revprops=self.revprops,
637
recursive=self.recursive,
638
message_callback=self.message_callback,
639
timestamp=self.timestamp, timezone=self.timezone,
640
committer=self.committer,
641
allow_pointless=self.allow_pointless,
642
strict=self.strict, verbose=self.verbose,
643
local=self.local, reporter=self.reporter)
644
except errors.PointlessCommit:
646
if kind != new_ie.kind:
647
new_ie = inventory.make_entry(kind, new_ie.name,
648
new_ie.parent_id, file_id)
649
except errors.NoSuchFile:
651
# mutter('check %s {%s}', path, file_id)
652
if (not self.specific_files or
653
is_inside_or_parent_of_any(self.specific_files, path)):
654
# mutter('%s selected for commit', path)
658
# mutter('%s not selected for commit', path)
659
if self.basis_inv.has_id(file_id):
660
ie = self.basis_inv[file_id].copy()
662
# this entry is new and not being committed
535
# this is selected, ensure its parents are too.
536
parent_id = new_ie.parent_id
537
while parent_id != ROOT_ID:
538
if not self.new_inv.has_id(parent_id):
539
ie = self._select_entry(self.work_inv[parent_id])
540
mutter('%s selected for commit because of %s',
541
self.new_inv.id2path(parent_id), path)
543
ie = self.new_inv[parent_id]
544
if ie.revision is not None:
546
mutter('%s selected for commit because of %s',
547
self.new_inv.id2path(parent_id), path)
548
parent_id = ie.parent_id
549
mutter('%s selected for commit', path)
550
self._select_entry(new_ie)
552
def _select_entry(self, new_ie):
553
"""Make new_ie be considered for committing."""
559
def _carry_entry(self, file_id):
560
"""Carry the file unchanged from the basis revision."""
561
if self.basis_inv.has_id(file_id):
562
self.new_inv.add(self.basis_inv[file_id].copy())
664
self.builder.record_entry_contents(ie, self.parent_invs,
665
path, self.work_tree)
666
# describe the nature of the change that has occurred relative to
667
# the basis inventory.
668
if (self.basis_inv.has_id(ie.file_id)):
669
basis_ie = self.basis_inv[ie.file_id]
672
change = ie.describe_change(basis_ie, ie)
673
if change in (InventoryEntry.RENAMED,
674
InventoryEntry.MODIFIED_AND_RENAMED):
675
old_path = self.basis_inv.id2path(ie.file_id)
676
self.reporter.renamed(change, old_path, path)
678
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,
694
def _emit_progress_set_stage(self, name, show_entries=False):
695
"""Set the progress stage and emit an update to the progress bar."""
696
self.pb_stage_name = name
697
self.pb_stage_count += 1
698
self.pb_entries_show = show_entries
700
self.pb_entries_count = 0
701
self.pb_entries_total = '?'
702
self._emit_progress()
704
def _emit_progress_next_entry(self):
705
"""Emit an update to the progress bar and increment the file count."""
706
self.pb_entries_count += 1
707
self._emit_progress()
709
def _emit_progress(self):
710
if self.pb_entries_show:
711
text = "%s [Entry %d/%s] - Stage" % (self.pb_stage_name,
712
self.pb_entries_count,str(self.pb_entries_total))
714
text = "%s - Stage" % (self.pb_stage_name)
715
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
564
717
def _report_deletes(self):
565
for file_id in self.basis_inv:
566
if file_id not in self.new_inv:
567
self.reporter.deleted(self.basis_inv.id2path(file_id))
718
for path, ie in self.basis_inv.iter_entries():
719
if ie.file_id not in self.builder.new_inventory:
720
self.reporter.deleted(path)
569
def _gen_revision_id(config, when):
570
"""Return new revision-id."""
571
s = '%s-%s-' % (config.user_email(), compact_date(when))
572
s += hexlify(rand_bytes(8))