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
17
from binascii import hexlify
17
18
from copy import deepcopy
18
19
from cStringIO import StringIO
19
22
from unittest import TestSuite
21
import bzrlib.bzrdir as bzrdir
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
22
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
23
import bzrlib.errors as errors
24
26
from bzrlib.errors import InvalidRevisionId
25
import bzrlib.gpg as gpg
26
27
from bzrlib.graph import Graph
27
28
from bzrlib.inter import InterObject
29
from bzrlib.inventory import Inventory
28
30
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
29
31
from bzrlib.lockable_files import LockableFiles, TransportLock
30
32
from bzrlib.lockdir import LockDir
31
from bzrlib.osutils import safe_unicode
32
from bzrlib.revision import NULL_REVISION
33
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date,
35
from bzrlib.revision import NULL_REVISION, Revision
33
36
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
34
37
from bzrlib.store.text import TextStore
35
from bzrlib.symbol_versioning import *
38
from bzrlib.symbol_versioning import (deprecated_method,
36
41
from bzrlib.trace import mutter, note
37
from bzrlib.tree import RevisionTree
42
from bzrlib.tree import RevisionTree, EmptyTree
38
43
from bzrlib.tsort import topo_sort
39
44
from bzrlib.testament import Testament
40
45
from bzrlib.tree import EmptyTree
42
46
from bzrlib.weave import WeaveFile
46
49
class Repository(object):
1836
1929
self.pb.update(message, self.count, self.total)
1932
class CommitBuilder(object):
1933
"""Provides an interface to build up a commit.
1935
This allows describing a tree to be committed without needing to
1936
know the internals of the format of the repository.
1938
def __init__(self, repository, parents, config, timestamp=None,
1939
timezone=None, committer=None, revprops=None,
1941
"""Initiate a CommitBuilder.
1943
:param repository: Repository to commit to.
1944
:param parents: Revision ids of the parents of the new revision.
1945
:param config: Configuration to use.
1946
:param timestamp: Optional timestamp recorded for commit.
1947
:param timezone: Optional timezone for timestamp.
1948
:param committer: Optional committer to set for commit.
1949
:param revprops: Optional dictionary of revision properties.
1950
:param revision_id: Optional revision id.
1952
self._config = config
1954
if committer is None:
1955
self._committer = self._config.username()
1957
assert isinstance(committer, basestring), type(committer)
1958
self._committer = committer
1960
self.new_inventory = Inventory()
1961
self._new_revision_id = revision_id
1962
self.parents = parents
1963
self.repository = repository
1966
if revprops is not None:
1967
self._revprops.update(revprops)
1969
if timestamp is None:
1970
self._timestamp = time.time()
1972
self._timestamp = long(timestamp)
1974
if timezone is None:
1975
self._timezone = local_time_offset()
1977
self._timezone = int(timezone)
1979
self._generate_revision_if_needed()
1981
def commit(self, message):
1982
"""Make the actual commit.
1984
:return: The revision id of the recorded revision.
1986
rev = Revision(timestamp=self._timestamp,
1987
timezone=self._timezone,
1988
committer=self._committer,
1990
inventory_sha1=self.inv_sha1,
1991
revision_id=self._new_revision_id,
1992
properties=self._revprops)
1993
rev.parent_ids = self.parents
1994
self.repository.add_revision(self._new_revision_id, rev,
1995
self.new_inventory, self._config)
1996
return self._new_revision_id
1998
def finish_inventory(self):
1999
"""Tell the builder that the inventory is finished."""
2000
self.new_inventory.revision_id = self._new_revision_id
2001
self.inv_sha1 = self.repository.add_inventory(
2002
self._new_revision_id,
2007
def _gen_revision_id(self):
2008
"""Return new revision-id."""
2009
s = '%s-%s-' % (self._config.user_email(),
2010
compact_date(self._timestamp))
2011
s += hexlify(rand_bytes(8))
2014
def _generate_revision_if_needed(self):
2015
"""Create a revision id if None was supplied.
2017
If the repository can not support user-specified revision ids
2018
they should override this function and raise UnsupportedOperation
2019
if _new_revision_id is not None.
2021
:raises: UnsupportedOperation
2023
if self._new_revision_id is None:
2024
self._new_revision_id = self._gen_revision_id()
2026
def record_entry_contents(self, ie, parent_invs, path, tree):
2027
"""Record the content of ie from tree into the commit if needed.
2029
:param ie: An inventory entry present in the commit.
2030
:param parent_invs: The inventories of the parent revisions of the
2032
:param path: The path the entry is at in the tree.
2033
:param tree: The tree which contains this entry and should be used to
2036
self.new_inventory.add(ie)
2038
# ie.revision is always None if the InventoryEntry is considered
2039
# for committing. ie.snapshot will record the correct revision
2040
# which may be the sole parent if it is untouched.
2041
if ie.revision is not None:
2043
previous_entries = ie.find_previous_heads(
2045
self.repository.weave_store,
2046
self.repository.get_transaction())
2047
# we are creating a new revision for ie in the history store
2049
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2051
def modified_directory(self, file_id, file_parents):
2052
"""Record the presence of a symbolic link.
2054
:param file_id: The file_id of the link to record.
2055
:param file_parents: The per-file parent revision ids.
2057
self._add_text_to_weave(file_id, [], file_parents.keys())
2059
def modified_file_text(self, file_id, file_parents,
2060
get_content_byte_lines, text_sha1=None,
2062
"""Record the text of file file_id
2064
:param file_id: The file_id of the file to record the text of.
2065
:param file_parents: The per-file parent revision ids.
2066
:param get_content_byte_lines: A callable which will return the byte
2068
:param text_sha1: Optional SHA1 of the file contents.
2069
:param text_size: Optional size of the file contents.
2071
mutter('storing text of file {%s} in revision {%s} into %r',
2072
file_id, self._new_revision_id, self.repository.weave_store)
2073
# special case to avoid diffing on renames or
2075
if (len(file_parents) == 1
2076
and text_sha1 == file_parents.values()[0].text_sha1
2077
and text_size == file_parents.values()[0].text_size):
2078
previous_ie = file_parents.values()[0]
2079
versionedfile = self.repository.weave_store.get_weave(file_id,
2080
self.repository.get_transaction())
2081
versionedfile.clone_text(self._new_revision_id,
2082
previous_ie.revision, file_parents.keys())
2083
return text_sha1, text_size
2085
new_lines = get_content_byte_lines()
2086
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2087
# should return the SHA1 and size
2088
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2089
return osutils.sha_strings(new_lines), \
2090
sum(map(len, new_lines))
2092
def modified_link(self, file_id, file_parents, link_target):
2093
"""Record the presence of a symbolic link.
2095
:param file_id: The file_id of the link to record.
2096
:param file_parents: The per-file parent revision ids.
2097
:param link_target: Target location of this link.
2099
self._add_text_to_weave(file_id, [], file_parents.keys())
2101
def _add_text_to_weave(self, file_id, new_lines, parents):
2102
versionedfile = self.repository.weave_store.get_weave_or_empty(
2103
file_id, self.repository.get_transaction())
2104
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2105
versionedfile.clear_cache()
1839
2108
# Copied from xml.sax.saxutils
1840
2109
def _unescape_xml(data):
1841
2110
"""Unescape &, <, and > in a string of data.