72
from binascii import hexlify
73
72
from cStringIO import StringIO
75
74
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,
81
75
import bzrlib.config
82
76
import bzrlib.errors as errors
83
77
from bzrlib.errors import (BzrError, PointlessCommit,
87
from bzrlib.revision import Revision
81
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
82
is_inside_or_parent_of_any,
83
quotefn, sha_file, split_lines)
88
84
from bzrlib.testament import Testament
89
85
from bzrlib.trace import mutter, note, warning
90
86
from bzrlib.xml5 import serializer_v5
274
266
# raise an exception as soon as we find a single unknown.
275
267
for unknown in self.work_tree.unknowns():
276
268
raise StrictCommitFailed()
278
if timestamp is None:
279
self.timestamp = time.time()
281
self.timestamp = long(timestamp)
283
270
if self.config is None:
284
271
self.config = bzrlib.config.BranchConfig(self.branch)
287
self.rev_id = _gen_revision_id(self.config, self.timestamp)
291
if committer is None:
292
self.committer = self.config.username()
294
assert isinstance(committer, basestring), type(committer)
295
self.committer = committer
298
self.timezone = local_time_offset()
300
self.timezone = int(timezone)
302
273
if isinstance(message, str):
303
274
message = message.decode(bzrlib.user_encoding)
304
275
assert isinstance(message, unicode), type(message)
321
292
raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
322
293
self.specific_files)
323
294
self._check_parents_present()
295
self.builder = self.branch.get_commit_builder(self.parents,
296
self.config, timestamp, timezone, committer, revprops, rev_id)
325
298
self._remove_deleted()
326
299
self._populate_new_inv()
327
self._store_snapshot()
328
300
self._report_deletes()
330
302
if not (self.allow_pointless
331
303
or len(self.parents) > 1
332
or self.new_inv != self.basis_inv):
304
or self.builder.new_inventory != self.basis_inv):
333
305
raise PointlessCommit()
335
307
self._emit_progress_update()
336
self.inv_sha1 = self.branch.repository.add_inventory(
341
self._emit_progress_update()
342
self._make_revision()
308
# TODO: Now the new inventory is known, check for conflicts and prompt the
309
# user for a commit message.
310
self.builder.finish_inventory()
311
self._emit_progress_update()
312
self.rev_id = self.builder.commit(self.message)
313
self._emit_progress_update()
343
314
# revision data is in the local branch now.
345
316
# upload revision data to the master.
346
# this will propogate merged revisions too if needed.
317
# this will propagate merged revisions too if needed.
347
318
if self.bound_branch:
348
319
self.master_branch.repository.fetch(self.branch.repository,
349
320
revision_id=self.rev_id)
478
442
def _gather_parents(self):
479
443
"""Record the parents of a merge for merge detection."""
480
pending_merges = self.work_tree.pending_merges()
444
# TODO: Make sure that this list doesn't contain duplicate
445
# entries and the order is preserved when doing this.
446
self.parents = self.work_tree.get_parent_ids()
482
447
self.parent_invs = []
483
self.present_parents = []
484
precursor_id = self.branch.last_revision()
486
self.parents.append(precursor_id)
487
self.parents += pending_merges
488
448
for revision in self.parents:
489
449
if self.branch.repository.has_revision(revision):
490
450
inventory = self.branch.repository.get_inventory(revision)
491
451
self.parent_invs.append(inventory)
492
self.present_parents.append(revision)
494
453
def _check_parents_present(self):
495
454
for parent_id in self.parents:
500
459
raise BzrCheckError("branch %s is missing revision {%s}"
501
460
% (self.branch, parent_id))
503
def _make_revision(self):
504
"""Record a new revision object for this commit."""
505
rev = Revision(timestamp=self.timestamp,
506
timezone=self.timezone,
507
committer=self.committer,
508
message=self.message,
509
inventory_sha1=self.inv_sha1,
510
revision_id=self.rev_id,
511
properties=self.revprops)
512
rev.parent_ids = self.parents
513
self.branch.repository.add_revision(self.rev_id, rev, self.new_inv, self.config)
515
462
def _remove_deleted(self):
516
463
"""Remove deleted files from the working inventories.
537
484
del self.work_inv[file_id]
538
485
self.work_tree._write_inventory(self.work_inv)
540
def _store_snapshot(self):
541
"""Pass over inventory and record a snapshot.
543
Entries get a new revision when they are modified in
544
any way, which includes a merge with a new set of
545
parents that have the same entry.
547
# XXX: Need to think more here about when the user has
548
# made a specific decision on a particular value -- c.f.
551
# iter_entries does not visit the ROOT_ID node so we need to call
552
# self._emit_progress_update once by hand.
553
self._emit_progress_update()
554
for path, ie in self.new_inv.iter_entries():
555
self._emit_progress_update()
556
previous_entries = ie.find_previous_heads(
559
self.branch.repository.get_transaction())
560
if ie.revision is None:
561
# we are creating a new revision for ie in the history store
563
ie.snapshot(self.rev_id, path, previous_entries,
564
self.work_tree, self.weave_store,
565
self.branch.repository.get_transaction())
566
# describe the nature of the change that has occured relative to
567
# the basis inventory.
568
if (self.basis_inv.has_id(ie.file_id)):
569
basis_ie = self.basis_inv[ie.file_id]
572
change = ie.describe_change(basis_ie, ie)
573
if change in (InventoryEntry.RENAMED,
574
InventoryEntry.MODIFIED_AND_RENAMED):
575
old_path = self.basis_inv.id2path(ie.file_id)
576
self.reporter.renamed(change, old_path, path)
578
self.reporter.snapshot_change(change, path)
580
487
def _populate_new_inv(self):
581
488
"""Build revision inventory.
588
495
revision set to their prior value.
590
497
mutter("Selecting files for commit with filter %s", self.specific_files)
591
self.new_inv = Inventory(revision_id=self.rev_id)
592
498
# iter_entries does not visit the ROOT_ID node so we need to call
593
499
# self._emit_progress_update once by hand.
594
500
self._emit_progress_update()
595
501
for path, new_ie in self.work_inv.iter_entries():
596
502
self._emit_progress_update()
597
503
file_id = new_ie.file_id
598
mutter('check %s {%s}', path, new_ie.file_id)
599
if self.specific_files:
600
if not is_inside_any(self.specific_files, path):
601
mutter('%s not selected for commit', path)
602
self._carry_entry(file_id)
504
mutter('check %s {%s}', path, file_id)
505
if (not self.specific_files or
506
is_inside_or_parent_of_any(self.specific_files, path)):
507
mutter('%s selected for commit', path)
511
mutter('%s not selected for commit', path)
512
if self.basis_inv.has_id(file_id):
513
ie = self.basis_inv[file_id].copy()
515
# this entry is new and not being committed
605
# this is selected, ensure its parents are too.
606
parent_id = new_ie.parent_id
607
while parent_id != ROOT_ID:
608
if not self.new_inv.has_id(parent_id):
609
ie = self._select_entry(self.work_inv[parent_id])
610
mutter('%s selected for commit because of %s',
611
self.new_inv.id2path(parent_id), path)
613
ie = self.new_inv[parent_id]
614
if ie.revision is not None:
616
mutter('%s selected for commit because of %s',
617
self.new_inv.id2path(parent_id), path)
618
parent_id = ie.parent_id
619
mutter('%s selected for commit', path)
620
self._select_entry(new_ie)
518
self.builder.record_entry_contents(ie, self.parent_invs,
519
path, self.work_tree)
520
# describe the nature of the change that has occurred relative to
521
# the basis inventory.
522
if (self.basis_inv.has_id(ie.file_id)):
523
basis_ie = self.basis_inv[ie.file_id]
526
change = ie.describe_change(basis_ie, ie)
527
if change in (InventoryEntry.RENAMED,
528
InventoryEntry.MODIFIED_AND_RENAMED):
529
old_path = self.basis_inv.id2path(ie.file_id)
530
self.reporter.renamed(change, old_path, path)
532
self.reporter.snapshot_change(change, path)
622
534
def _emit_progress_update(self):
623
535
"""Emit an update to the progress bar."""
624
536
self.pb.update("Committing", self.pb_count, self.pb_total)
625
537
self.pb_count += 1
627
def _select_entry(self, new_ie):
628
"""Make new_ie be considered for committing."""
634
def _carry_entry(self, file_id):
635
"""Carry the file unchanged from the basis revision."""
636
if self.basis_inv.has_id(file_id):
637
self.new_inv.add(self.basis_inv[file_id].copy())
639
# this entry is new and not being committed
642
539
def _report_deletes(self):
643
540
for path, ie in self.basis_inv.iter_entries():
644
if ie.file_id not in self.new_inv:
541
if ie.file_id not in self.builder.new_inventory:
645
542
self.reporter.deleted(path)
647
def _gen_revision_id(config, when):
648
"""Return new revision-id."""
649
s = '%s-%s-' % (config.user_email(), compact_date(when))
650
s += hexlify(rand_bytes(8))