1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
89
89
from bzrlib.testament import Testament
90
90
from bzrlib.trace import mutter, note, warning
91
91
from bzrlib.xml5 import serializer_v5
92
from bzrlib.inventory import Inventory, ROOT_ID
92
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
93
93
from bzrlib.symbol_versioning import *
94
94
from bzrlib.workingtree import WorkingTree
124
124
def missing(self, path):
127
def renamed(self, change, old_path, new_path):
128
131
class ReportCommitToLog(NullCommitReporter):
133
# this may be more useful if 'note' was replaced by an overridable
134
# method on self, which would allow more trivial subclassing.
135
# alternative, a callable could be passed in, allowing really trivial
136
# reuse for some uis. RBC 20060511
130
138
def snapshot_change(self, change, path):
131
139
if change == 'unchanged':
144
152
def missing(self, path):
145
153
note('missing %s', path)
155
def renamed(self, change, old_path, new_path):
156
note('%s %s => %s', change, old_path, new_path)
148
159
class Commit(object):
149
160
"""Task of committing a new revision.
245
256
self.reporter = reporter
247
258
self.work_tree.lock_write()
259
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
261
# Cannot commit with conflicts present.
262
if len(self.work_tree.conflicts())>0:
263
raise ConflictsInTree
249
265
# setup the bound branch variables as needed.
250
266
self._check_bound_branch()
293
309
self.work_inv = self.work_tree.inventory
294
310
self.basis_tree = self.work_tree.basis_tree()
295
311
self.basis_inv = self.basis_tree.inventory
312
# one to finish, one for rev and inventory, and one for each
313
# inventory entry, and the same for the new inventory.
314
# note that this estimate is too long when we do a partial tree
315
# commit which excludes some new files from being considered.
316
# The estimate is corrected when we populate the new inv.
317
self.pb_total = len(self.basis_inv) + len(self.work_inv) + 3 - 1
297
320
self._gather_parents()
298
321
if len(self.parents) > 1 and self.specific_files:
299
raise NotImplementedError('selected-file commit of merges is not supported yet')
322
raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
300
324
self._check_parents_present()
302
326
self._remove_deleted()
309
333
or self.new_inv != self.basis_inv):
310
334
raise PointlessCommit()
312
if len(self.work_tree.conflicts())>0:
313
raise ConflictsInTree
336
self._emit_progress_update()
315
337
self.inv_sha1 = self.branch.repository.add_inventory(
318
340
self.present_parents
342
self._emit_progress_update()
320
343
self._make_revision()
321
344
# revision data is in the local branch now.
400
423
#### self.master_branch.repository.fetch(self.bound_branch.repository,
401
424
#### revision_id=revision_id)
427
"""Cleanup any open locks, progress bars etc."""
428
cleanups = [self._cleanup_bound_branch,
429
self.work_tree.unlock,
431
found_exception = None
432
for cleanup in cleanups:
435
# we want every cleanup to run no matter what.
436
# so we have a catchall here, but we will raise the
437
# last encountered exception up the stack: and
438
# typically this will be useful enough.
441
if found_exception is not None:
442
# dont do a plan raise, because the last exception may have been
443
# trashed, e is our sure-to-work exception even though it loses the
444
# full traceback. XXX: RBC 20060421 perhaps we could check the
445
# exc_info and if its the same one do a plain raise otherwise
446
# 'raise e' as we do now.
403
449
def _cleanup_bound_branch(self):
404
450
"""Executed at the end of a try/finally to cleanup a bound branch.
503
549
# XXX: Need to think more here about when the user has
504
550
# made a specific decision on a particular value -- c.f.
553
# iter_entries does not visit the ROOT_ID node so we need to call
554
# self._emit_progress_update once by hand.
555
self._emit_progress_update()
506
556
for path, ie in self.new_inv.iter_entries():
557
self._emit_progress_update()
507
558
previous_entries = ie.find_previous_heads(
508
559
self.parent_invs,
509
560
self.weave_store,
510
561
self.branch.repository.get_transaction())
511
562
if ie.revision is None:
512
change = ie.snapshot(self.rev_id, path, previous_entries,
513
self.work_tree, self.weave_store,
514
self.branch.get_transaction())
517
self.reporter.snapshot_change(change, path)
563
# we are creating a new revision for ie in the history store
565
ie.snapshot(self.rev_id, path, previous_entries,
566
self.work_tree, self.weave_store,
567
self.branch.repository.get_transaction())
568
# describe the nature of the change that has occured relative to
569
# the basis inventory.
570
if (self.basis_inv.has_id(ie.file_id)):
571
basis_ie = self.basis_inv[ie.file_id]
574
change = ie.describe_change(basis_ie, ie)
575
if change in (InventoryEntry.RENAMED,
576
InventoryEntry.MODIFIED_AND_RENAMED):
577
old_path = self.basis_inv.id2path(ie.file_id)
578
self.reporter.renamed(change, old_path, path)
580
self.reporter.snapshot_change(change, path)
519
582
def _populate_new_inv(self):
520
583
"""Build revision inventory.
529
592
mutter("Selecting files for commit with filter %s", self.specific_files)
530
593
self.new_inv = Inventory(revision_id=self.rev_id)
594
# iter_entries does not visit the ROOT_ID node so we need to call
595
# self._emit_progress_update once by hand.
596
self._emit_progress_update()
531
597
for path, new_ie in self.work_inv.iter_entries():
598
self._emit_progress_update()
532
599
file_id = new_ie.file_id
533
600
mutter('check %s {%s}', path, new_ie.file_id)
534
601
if self.specific_files:
554
621
mutter('%s selected for commit', path)
555
622
self._select_entry(new_ie)
624
def _emit_progress_update(self):
625
"""Emit an update to the progress bar."""
626
self.pb.update("Committing", self.pb_count, self.pb_total)
557
629
def _select_entry(self, new_ie):
558
630
"""Make new_ie be considered for committing."""
559
631
ie = new_ie.copy()
565
637
"""Carry the file unchanged from the basis revision."""
566
638
if self.basis_inv.has_id(file_id):
567
639
self.new_inv.add(self.basis_inv[file_id].copy())
641
# this entry is new and not being committed
569
644
def _report_deletes(self):
570
for file_id in self.basis_inv:
571
if file_id not in self.new_inv:
572
self.reporter.deleted(self.basis_inv.id2path(file_id))
645
for path, ie in self.basis_inv.iter_entries():
646
if ie.file_id not in self.new_inv:
647
self.reporter.deleted(path)
574
649
def _gen_revision_id(config, when):
575
650
"""Return new revision-id."""