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):
740
def install_revision(repository, rev, revision_tree):
741
"""Install all revision data into a repository."""
744
for p_id in rev.parent_ids:
745
if repository.has_revision(p_id):
746
present_parents.append(p_id)
747
parent_trees[p_id] = repository.revision_tree(p_id)
749
parent_trees[p_id] = EmptyTree()
751
inv = revision_tree.inventory
753
# Add the texts that are not already present
754
for path, ie in inv.iter_entries():
755
w = repository.weave_store.get_weave_or_empty(ie.file_id,
756
repository.get_transaction())
757
if ie.revision not in w:
759
# FIXME: TODO: The following loop *may* be overlapping/duplicate
760
# with InventoryEntry.find_previous_heads(). if it is, then there
761
# is a latent bug here where the parents may have ancestors of each
763
for revision, tree in parent_trees.iteritems():
764
if ie.file_id not in tree:
766
parent_id = tree.inventory[ie.file_id].revision
767
if parent_id in text_parents:
769
text_parents.append(parent_id)
771
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
772
repository.get_transaction())
773
lines = revision_tree.get_file(ie.file_id).readlines()
774
vfile.add_lines(rev.revision_id, text_parents, lines)
776
# install the inventory
777
repository.add_inventory(rev.revision_id, inv, present_parents)
778
except errors.RevisionAlreadyPresent:
780
repository.add_revision(rev.revision_id, rev, inv)
619
783
class MetaDirRepository(Repository):
620
784
"""Repositories in the new meta-dir layout."""
1797
1976
self.pb.update(message, self.count, self.total)
1800
# Copied from xml.sax.saxutils
1979
class CommitBuilder(object):
1980
"""Provides an interface to build up a commit.
1982
This allows describing a tree to be committed without needing to
1983
know the internals of the format of the repository.
1985
def __init__(self, repository, parents, config, timestamp=None,
1986
timezone=None, committer=None, revprops=None,
1988
"""Initiate a CommitBuilder.
1990
:param repository: Repository to commit to.
1991
:param parents: Revision ids of the parents of the new revision.
1992
:param config: Configuration to use.
1993
:param timestamp: Optional timestamp recorded for commit.
1994
:param timezone: Optional timezone for timestamp.
1995
:param committer: Optional committer to set for commit.
1996
:param revprops: Optional dictionary of revision properties.
1997
:param revision_id: Optional revision id.
1999
self._config = config
2001
if committer is None:
2002
self._committer = self._config.username()
2004
assert isinstance(committer, basestring), type(committer)
2005
self._committer = committer
2007
self.new_inventory = Inventory()
2008
self._new_revision_id = revision_id
2009
self.parents = parents
2010
self.repository = repository
2013
if revprops is not None:
2014
self._revprops.update(revprops)
2016
if timestamp is None:
2017
timestamp = time.time()
2018
# Restrict resolution to 1ms
2019
self._timestamp = round(timestamp, 3)
2021
if timezone is None:
2022
self._timezone = local_time_offset()
2024
self._timezone = int(timezone)
2026
self._generate_revision_if_needed()
2028
def commit(self, message):
2029
"""Make the actual commit.
2031
:return: The revision id of the recorded revision.
2033
rev = Revision(timestamp=self._timestamp,
2034
timezone=self._timezone,
2035
committer=self._committer,
2037
inventory_sha1=self.inv_sha1,
2038
revision_id=self._new_revision_id,
2039
properties=self._revprops)
2040
rev.parent_ids = self.parents
2041
self.repository.add_revision(self._new_revision_id, rev,
2042
self.new_inventory, self._config)
2043
return self._new_revision_id
2045
def finish_inventory(self):
2046
"""Tell the builder that the inventory is finished."""
2047
self.new_inventory.revision_id = self._new_revision_id
2048
self.inv_sha1 = self.repository.add_inventory(
2049
self._new_revision_id,
2054
def _gen_revision_id(self):
2055
"""Return new revision-id."""
2056
s = '%s-%s-' % (self._config.user_email(),
2057
compact_date(self._timestamp))
2058
s += hexlify(rand_bytes(8))
2061
def _generate_revision_if_needed(self):
2062
"""Create a revision id if None was supplied.
2064
If the repository can not support user-specified revision ids
2065
they should override this function and raise UnsupportedOperation
2066
if _new_revision_id is not None.
2068
:raises: UnsupportedOperation
2070
if self._new_revision_id is None:
2071
self._new_revision_id = self._gen_revision_id()
2073
def record_entry_contents(self, ie, parent_invs, path, tree):
2074
"""Record the content of ie from tree into the commit if needed.
2076
:param ie: An inventory entry present in the commit.
2077
:param parent_invs: The inventories of the parent revisions of the
2079
:param path: The path the entry is at in the tree.
2080
:param tree: The tree which contains this entry and should be used to
2083
self.new_inventory.add(ie)
2085
# ie.revision is always None if the InventoryEntry is considered
2086
# for committing. ie.snapshot will record the correct revision
2087
# which may be the sole parent if it is untouched.
2088
if ie.revision is not None:
2090
previous_entries = ie.find_previous_heads(
2092
self.repository.weave_store,
2093
self.repository.get_transaction())
2094
# we are creating a new revision for ie in the history store
2096
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2098
def modified_directory(self, file_id, file_parents):
2099
"""Record the presence of a symbolic link.
2101
:param file_id: The file_id of the link to record.
2102
:param file_parents: The per-file parent revision ids.
2104
self._add_text_to_weave(file_id, [], file_parents.keys())
2106
def modified_file_text(self, file_id, file_parents,
2107
get_content_byte_lines, text_sha1=None,
2109
"""Record the text of file file_id
2111
:param file_id: The file_id of the file to record the text of.
2112
:param file_parents: The per-file parent revision ids.
2113
:param get_content_byte_lines: A callable which will return the byte
2115
:param text_sha1: Optional SHA1 of the file contents.
2116
:param text_size: Optional size of the file contents.
2118
mutter('storing text of file {%s} in revision {%s} into %r',
2119
file_id, self._new_revision_id, self.repository.weave_store)
2120
# special case to avoid diffing on renames or
2122
if (len(file_parents) == 1
2123
and text_sha1 == file_parents.values()[0].text_sha1
2124
and text_size == file_parents.values()[0].text_size):
2125
previous_ie = file_parents.values()[0]
2126
versionedfile = self.repository.weave_store.get_weave(file_id,
2127
self.repository.get_transaction())
2128
versionedfile.clone_text(self._new_revision_id,
2129
previous_ie.revision, file_parents.keys())
2130
return text_sha1, text_size
2132
new_lines = get_content_byte_lines()
2133
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2134
# should return the SHA1 and size
2135
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2136
return osutils.sha_strings(new_lines), \
2137
sum(map(len, new_lines))
2139
def modified_link(self, file_id, file_parents, link_target):
2140
"""Record the presence of a symbolic link.
2142
:param file_id: The file_id of the link to record.
2143
:param file_parents: The per-file parent revision ids.
2144
:param link_target: Target location of this link.
2146
self._add_text_to_weave(file_id, [], file_parents.keys())
2148
def _add_text_to_weave(self, file_id, new_lines, parents):
2149
versionedfile = self.repository.weave_store.get_weave_or_empty(
2150
file_id, self.repository.get_transaction())
2151
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2152
versionedfile.clear_cache()
2164
def _unescaper(match, _map=_unescape_map):
2165
return _map[match.group(1)]
1801
2171
def _unescape_xml(data):
1802
"""Unescape &, <, and > in a string of data.
1804
data = data.replace("<", "<")
1805
data = data.replace(">", ">")
1806
# must do ampersand last
1807
return data.replace("&", "&")
2172
"""Unescape predefined XML entities in a string of data."""
2174
if _unescape_re is None:
2175
_unescape_re = re.compile('\&([^;]*);')
2176
return _unescape_re.sub(_unescaper, data)