~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2006-06-10 23:16:19 UTC
  • mfrom: (1759 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1761.
  • Revision ID: mbp@sourcefrog.net-20060610231619-05b997deeb005d02
[merge] bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from binascii import hexlify
17
18
from copy import deepcopy
18
19
from cStringIO import StringIO
 
20
import re
 
21
import time
19
22
from unittest import TestSuite
20
23
 
21
24
import bzrlib.bzrdir as bzrdir
25
28
import bzrlib.gpg as gpg
26
29
from bzrlib.graph import Graph
27
30
from bzrlib.inter import InterObject
 
31
from bzrlib.inventory import Inventory
28
32
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
29
33
from bzrlib.lockable_files import LockableFiles, TransportLock
30
34
from bzrlib.lockdir import LockDir
31
 
from bzrlib.osutils import safe_unicode
32
 
from bzrlib.revision import NULL_REVISION
 
35
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date, 
 
36
                            local_time_offset)
 
37
from bzrlib.revision import NULL_REVISION, Revision
33
38
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
34
39
from bzrlib.store.text import TextStore
35
40
from bzrlib.symbol_versioning import *
64
69
 
65
70
        returns the sha1 of the serialized inventory.
66
71
        """
 
72
        assert inv.revision_id is None or inv.revision_id == revid, \
 
73
            "Mismatch between inventory revision" \
 
74
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
67
75
        inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
68
76
        inv_sha1 = bzrlib.osutils.sha_string(inv_text)
69
77
        inv_vf = self.control_weaves.get_weave('inventory',
70
78
                                               self.get_transaction())
71
 
        inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
 
79
        self._inventory_add_lines(inv_vf, revid, parents, bzrlib.osutils.split_lines(inv_text))
72
80
        return inv_sha1
73
81
 
 
82
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
83
        final_parents = []
 
84
        for parent in parents:
 
85
            if parent in inv_vf:
 
86
                final_parents.append(parent)
 
87
 
 
88
        inv_vf.add_lines(revid, final_parents, lines)
 
89
 
74
90
    @needs_write_lock
75
91
    def add_revision(self, rev_id, rev, inv=None, config=None):
76
92
        """Add rev to the revision store as rev_id.
229
245
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
230
246
                                                       pb=pb)
231
247
 
 
248
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
249
                           timezone=None, committer=None, revprops=None, 
 
250
                           revision_id=None):
 
251
        """Obtain a CommitBuilder for this repository.
 
252
        
 
253
        :param branch: Branch to commit to.
 
254
        :param parents: Revision ids of the parents of the new revision.
 
255
        :param config: Configuration to use.
 
256
        :param timestamp: Optional timestamp recorded for commit.
 
257
        :param timezone: Optional timezone for timestamp.
 
258
        :param committer: Optional committer to set for commit.
 
259
        :param revprops: Optional dictionary of revision properties.
 
260
        :param revision_id: Optional revision id.
 
261
        """
 
262
        return CommitBuilder(self, parents, config, timestamp, timezone,
 
263
                             committer, revprops, revision_id)
 
264
 
232
265
    def unlock(self):
233
266
        self.control_files.unlock()
234
267
 
370
403
    @needs_read_lock
371
404
    def get_inventory(self, revision_id):
372
405
        """Get Inventory object by hash."""
373
 
        xml = self.get_inventory_xml(revision_id)
 
406
        return self.deserialise_inventory(
 
407
            revision_id, self.get_inventory_xml(revision_id))
 
408
 
 
409
    def deserialise_inventory(self, revision_id, xml):
 
410
        """Transform the xml into an inventory object. 
 
411
 
 
412
        :param revision_id: The expected revision id of the inventory.
 
413
        :param xml: A serialised inventory.
 
414
        """
374
415
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
375
416
 
376
417
    @needs_read_lock
523
564
        # use inventory as it was in that revision
524
565
        file_id = tree.inventory.path2id(file)
525
566
        if not file_id:
526
 
            raise BzrError("%r is not present in revision %s" % (file, revno))
527
 
            try:
528
 
                revno = self.revision_id_to_revno(revision_id)
529
 
            except errors.NoSuchRevision:
530
 
                # TODO: This should not be BzrError,
531
 
                # but NoSuchFile doesn't fit either
532
 
                raise BzrError('%r is not present in revision %s' 
533
 
                                % (file, revision_id))
534
 
            else:
535
 
                raise BzrError('%r is not present in revision %s'
536
 
                                % (file, revno))
 
567
            # TODO: jam 20060427 Write a test for this code path
 
568
            #       it had a bug in it, and was raising the wrong
 
569
            #       exception.
 
570
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
537
571
        tree.print_file(file_id)
538
572
 
539
573
    def get_transaction(self):
650
684
        return True
651
685
 
652
686
 
 
687
def install_revision(repository, rev, revision_tree):
 
688
    """Install all revision data into a repository."""
 
689
    present_parents = []
 
690
    parent_trees = {}
 
691
    for p_id in rev.parent_ids:
 
692
        if repository.has_revision(p_id):
 
693
            present_parents.append(p_id)
 
694
            parent_trees[p_id] = repository.revision_tree(p_id)
 
695
        else:
 
696
            parent_trees[p_id] = EmptyTree()
 
697
 
 
698
    inv = revision_tree.inventory
 
699
    
 
700
    # Add the texts that are not already present
 
701
    for path, ie in inv.iter_entries():
 
702
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
703
                repository.get_transaction())
 
704
        if ie.revision not in w:
 
705
            text_parents = []
 
706
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
707
            # with inventoryEntry.find_previous_heads(). if it is, then there
 
708
            # is a latent bug here where the parents may have ancestors of each
 
709
            # other. RBC, AB
 
710
            for revision, tree in parent_trees.iteritems():
 
711
                if ie.file_id not in tree:
 
712
                    continue
 
713
                parent_id = tree.inventory[ie.file_id].revision
 
714
                if parent_id in text_parents:
 
715
                    continue
 
716
                text_parents.append(parent_id)
 
717
                    
 
718
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
719
                repository.get_transaction())
 
720
            lines = revision_tree.get_file(ie.file_id).readlines()
 
721
            vfile.add_lines(rev.revision_id, text_parents, lines)
 
722
    try:
 
723
        # install the inventory
 
724
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
725
    except errors.RevisionAlreadyPresent:
 
726
        pass
 
727
    repository.add_revision(rev.revision_id, rev, inv)
 
728
 
 
729
 
653
730
class MetaDirRepository(Repository):
654
731
    """Repositories in the new meta-dir layout."""
655
732
 
695
772
class KnitRepository(MetaDirRepository):
696
773
    """Knit format repository."""
697
774
 
 
775
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
776
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
777
 
698
778
    @needs_read_lock
699
779
    def _all_revision_ids(self):
700
780
        """See Repository.all_revision_ids()."""
1808
1888
        self.pb.update(message, self.count, self.total)
1809
1889
 
1810
1890
 
 
1891
class CommitBuilder(object):
 
1892
    """Provides an interface to build up a commit.
 
1893
 
 
1894
    This allows describing a tree to be committed without needing to 
 
1895
    know the internals of the format of the repository.
 
1896
    """
 
1897
    def __init__(self, repository, parents, config, timestamp=None, 
 
1898
                 timezone=None, committer=None, revprops=None, 
 
1899
                 revision_id=None):
 
1900
        """Initiate a CommitBuilder.
 
1901
 
 
1902
        :param repository: Repository to commit to.
 
1903
        :param parents: Revision ids of the parents of the new revision.
 
1904
        :param config: Configuration to use.
 
1905
        :param timestamp: Optional timestamp recorded for commit.
 
1906
        :param timezone: Optional timezone for timestamp.
 
1907
        :param committer: Optional committer to set for commit.
 
1908
        :param revprops: Optional dictionary of revision properties.
 
1909
        :param revision_id: Optional revision id.
 
1910
        """
 
1911
        self._config = config
 
1912
 
 
1913
        if committer is None:
 
1914
            self._committer = self._config.username()
 
1915
        else:
 
1916
            assert isinstance(committer, basestring), type(committer)
 
1917
            self._committer = committer
 
1918
 
 
1919
        self.new_inventory = Inventory()
 
1920
        self._new_revision_id = revision_id
 
1921
        self.parents = parents
 
1922
        self.repository = repository
 
1923
 
 
1924
        self._revprops = {}
 
1925
        if revprops is not None:
 
1926
            self._revprops.update(revprops)
 
1927
 
 
1928
        if timestamp is None:
 
1929
            self._timestamp = time.time()
 
1930
        else:
 
1931
            self._timestamp = long(timestamp)
 
1932
 
 
1933
        if timezone is None:
 
1934
            self._timezone = local_time_offset()
 
1935
        else:
 
1936
            self._timezone = int(timezone)
 
1937
 
 
1938
        self._generate_revision_if_needed()
 
1939
 
 
1940
    def commit(self, message):
 
1941
        """Make the actual commit.
 
1942
 
 
1943
        :return: The revision id of the recorded revision.
 
1944
        """
 
1945
        rev = Revision(timestamp=self._timestamp,
 
1946
                       timezone=self._timezone,
 
1947
                       committer=self._committer,
 
1948
                       message=message,
 
1949
                       inventory_sha1=self.inv_sha1,
 
1950
                       revision_id=self._new_revision_id,
 
1951
                       properties=self._revprops)
 
1952
        rev.parent_ids = self.parents
 
1953
        self.repository.add_revision(self._new_revision_id, rev, 
 
1954
            self.new_inventory, self._config)
 
1955
        return self._new_revision_id
 
1956
 
 
1957
    def finish_inventory(self):
 
1958
        """Tell the builder that the inventory is finished."""
 
1959
        self.new_inventory.revision_id = self._new_revision_id
 
1960
        self.inv_sha1 = self.repository.add_inventory(
 
1961
            self._new_revision_id,
 
1962
            self.new_inventory,
 
1963
            self.parents
 
1964
            )
 
1965
 
 
1966
    def _gen_revision_id(self):
 
1967
        """Return new revision-id."""
 
1968
        s = '%s-%s-' % (self._config.user_email(), 
 
1969
                        compact_date(self._timestamp))
 
1970
        s += hexlify(rand_bytes(8))
 
1971
        return s
 
1972
 
 
1973
    def _generate_revision_if_needed(self):
 
1974
        """Create a revision id if None was supplied.
 
1975
        
 
1976
        If the repository can not support user-specified revision ids
 
1977
        they should override this function and raise UnsupportedOperation
 
1978
        if _new_revision_id is not None.
 
1979
 
 
1980
        :raises: UnsupportedOperation
 
1981
        """
 
1982
        if self._new_revision_id is None:
 
1983
            self._new_revision_id = self._gen_revision_id()
 
1984
 
 
1985
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
1986
        """Record the content of ie from tree into the commit if needed.
 
1987
 
 
1988
        :param ie: An inventory entry present in the commit.
 
1989
        :param parent_invs: The inventories of the parent revisions of the
 
1990
            commit.
 
1991
        :param path: The path the entry is at in the tree.
 
1992
        :param tree: The tree which contains this entry and should be used to 
 
1993
        obtain content.
 
1994
        """
 
1995
        self.new_inventory.add(ie)
 
1996
 
 
1997
        # ie.revision is always None if the InventoryEntry is considered
 
1998
        # for committing. ie.snapshot will record the correct revision 
 
1999
        # which may be the sole parent if it is untouched.
 
2000
        if ie.revision is not None:
 
2001
            return
 
2002
        previous_entries = ie.find_previous_heads(
 
2003
            parent_invs,
 
2004
            self.repository.weave_store,
 
2005
            self.repository.get_transaction())
 
2006
        # we are creating a new revision for ie in the history store
 
2007
        # and inventory.
 
2008
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
 
2009
 
 
2010
    def modified_directory(self, file_id, file_parents):
 
2011
        """Record the presence of a symbolic link.
 
2012
 
 
2013
        :param file_id: The file_id of the link to record.
 
2014
        :param file_parents: The per-file parent revision ids.
 
2015
        """
 
2016
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2017
    
 
2018
    def modified_file_text(self, file_id, file_parents,
 
2019
                           get_content_byte_lines, text_sha1=None,
 
2020
                           text_size=None):
 
2021
        """Record the text of file file_id
 
2022
 
 
2023
        :param file_id: The file_id of the file to record the text of.
 
2024
        :param file_parents: The per-file parent revision ids.
 
2025
        :param get_content_byte_lines: A callable which will return the byte
 
2026
            lines for the file.
 
2027
        :param text_sha1: Optional SHA1 of the file contents.
 
2028
        :param text_size: Optional size of the file contents.
 
2029
        """
 
2030
        mutter('storing text of file {%s} in revision {%s} into %r',
 
2031
               file_id, self._new_revision_id, self.repository.weave_store)
 
2032
        # special case to avoid diffing on renames or 
 
2033
        # reparenting
 
2034
        if (len(file_parents) == 1
 
2035
            and text_sha1 == file_parents.values()[0].text_sha1
 
2036
            and text_size == file_parents.values()[0].text_size):
 
2037
            previous_ie = file_parents.values()[0]
 
2038
            versionedfile = self.repository.weave_store.get_weave(file_id, 
 
2039
                self.repository.get_transaction())
 
2040
            versionedfile.clone_text(self._new_revision_id, 
 
2041
                previous_ie.revision, file_parents.keys())
 
2042
            return text_sha1, text_size
 
2043
        else:
 
2044
            new_lines = get_content_byte_lines()
 
2045
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
 
2046
            # should return the SHA1 and size
 
2047
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
 
2048
            return bzrlib.osutils.sha_strings(new_lines), \
 
2049
                sum(map(len, new_lines))
 
2050
 
 
2051
    def modified_link(self, file_id, file_parents, link_target):
 
2052
        """Record the presence of a symbolic link.
 
2053
 
 
2054
        :param file_id: The file_id of the link to record.
 
2055
        :param file_parents: The per-file parent revision ids.
 
2056
        :param link_target: Target location of this link.
 
2057
        """
 
2058
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2059
 
 
2060
    def _add_text_to_weave(self, file_id, new_lines, parents):
 
2061
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
2062
            file_id, self.repository.get_transaction())
 
2063
        versionedfile.add_lines(self._new_revision_id, parents, new_lines)
 
2064
        versionedfile.clear_cache()
 
2065
 
 
2066
 
1811
2067
# Copied from xml.sax.saxutils
1812
2068
def _unescape_xml(data):
1813
2069
    """Unescape &, <, and > in a string of data.