478
434
return self.working_tree().unknowns()
481
def commit(self, message, timestamp=None, timezone=None,
484
"""Commit working copy as a new revision.
486
The basic approach is to add all the file texts into the
487
store, then the inventory, then make a new revision pointing
488
to that inventory and store that.
490
This is not quite safe if the working copy changes during the
491
commit; for the moment that is simply not allowed. A better
492
approach is to make a temporary copy of the files before
493
computing their hashes, and then add those hashes in turn to
494
the inventory. This should mean at least that there are no
495
broken hash pointers. There is no way we can get a snapshot
496
of the whole directory at an instant. This would also have to
497
be robust against files disappearing, moving, etc. So the
498
whole thing is a bit hard.
500
timestamp -- if not None, seconds-since-epoch for a
501
postdated/predated commit.
503
self._need_writelock()
505
## TODO: Show branch names
507
# TODO: Don't commit if there are no changes, unless forced?
509
# First walk over the working inventory; and both update that
510
# and also build a new revision inventory. The revision
511
# inventory needs to hold the text-id, sha1 and size of the
512
# actual file versions committed in the revision. (These are
513
# not present in the working inventory.) We also need to
514
# detect missing/deleted files, and remove them from the
517
work_inv = self.read_working_inventory()
519
basis = self.basis_tree()
520
basis_inv = basis.inventory
522
for path, entry in work_inv.iter_entries():
523
## TODO: Cope with files that have gone missing.
525
## TODO: Check that the file kind has not changed from the previous
526
## revision of this file (if any).
530
p = self.abspath(path)
531
file_id = entry.file_id
532
mutter('commit prep file %s, id %r ' % (p, file_id))
534
if not os.path.exists(p):
535
mutter(" file is missing, removing from inventory")
537
show_status('D', entry.kind, quotefn(path))
538
missing_ids.append(file_id)
541
# TODO: Handle files that have been deleted
543
# TODO: Maybe a special case for empty files? Seems a
544
# waste to store them many times.
548
if basis_inv.has_id(file_id):
549
old_kind = basis_inv[file_id].kind
550
if old_kind != entry.kind:
551
bailout("entry %r changed kind from %r to %r"
552
% (file_id, old_kind, entry.kind))
554
if entry.kind == 'directory':
556
bailout("%s is entered as directory but not a directory" % quotefn(p))
557
elif entry.kind == 'file':
559
bailout("%s is entered as file but is not a file" % quotefn(p))
561
content = file(p, 'rb').read()
563
entry.text_sha1 = sha_string(content)
564
entry.text_size = len(content)
566
old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
568
and (old_ie.text_size == entry.text_size)
569
and (old_ie.text_sha1 == entry.text_sha1)):
570
## assert content == basis.get_file(file_id).read()
571
entry.text_id = basis_inv[file_id].text_id
572
mutter(' unchanged from previous text_id {%s}' %
576
entry.text_id = gen_file_id(entry.name)
577
self.text_store.add(content, entry.text_id)
578
mutter(' stored with text_id {%s}' % entry.text_id)
582
elif (old_ie.name == entry.name
583
and old_ie.parent_id == entry.parent_id):
588
show_status(state, entry.kind, quotefn(path))
590
for file_id in missing_ids:
591
# have to do this later so we don't mess up the iterator.
592
# since parents may be removed before their children we
595
# FIXME: There's probably a better way to do this; perhaps
596
# the workingtree should know how to filter itself.
597
if work_inv.has_id(file_id):
598
del work_inv[file_id]
601
inv_id = rev_id = _gen_revision_id(time.time())
603
inv_tmp = tempfile.TemporaryFile()
604
inv.write_xml(inv_tmp)
606
self.inventory_store.add(inv_tmp, inv_id)
607
mutter('new inventory_id is {%s}' % inv_id)
609
self._write_inventory(work_inv)
611
if timestamp == None:
612
timestamp = time.time()
614
if committer == None:
615
committer = username()
618
timezone = local_time_offset()
620
mutter("building commit log message")
621
rev = Revision(timestamp=timestamp,
624
precursor = self.last_patch(),
629
rev_tmp = tempfile.TemporaryFile()
630
rev.write_xml(rev_tmp)
632
self.revision_store.add(rev_tmp, rev_id)
633
mutter("new revision_id is {%s}" % rev_id)
635
## XXX: Everything up to here can simply be orphaned if we abort
636
## the commit; it will leave junk files behind but that doesn't
639
## TODO: Read back the just-generated changeset, and make sure it
640
## applies and recreates the right state.
642
## TODO: Also calculate and store the inventory SHA1
643
mutter("committing patch r%d" % (self.revno() + 1))
646
self.append_revision(rev_id)
649
note("commited r%d" % self.revno())
652
437
def append_revision(self, revision_id):
653
438
mutter("add {%s} to revision-history" % revision_id)
654
439
rev_history = self.revision_history()