# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""Access to stored Subversion basis trees."""
-from binascii import hexlify
-from bzrlib.bzrdir import BzrDirFormat
-from bzrlib.errors import NotBranchError, NoSuchFile
-from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile,
- ROOT_ID)
-from bzrlib.lockable_files import TransportLock, LockableFiles
-from bzrlib.lockdir import LockDir
-import bzrlib.osutils as osutils
-from bzrlib.progress import DummyProgress
+from bzrlib.inventory import Inventory
+
+from bzrlib import osutils, urlutils
from bzrlib.trace import mutter
-from bzrlib.tree import RevisionTree, EmptyTree
+from bzrlib.revisiontree import RevisionTree
import os
+import md5
from cStringIO import StringIO
+import urllib
import svn.core, svn.wc, svn.delta
-from svn.core import SubversionException, Pool
+from svn.core import Pool
+
+# Deal with Subversion 1.5 and the patched Subversion 1.4 (which are
+# slightly different).
-_global_pool = Pool()
+if hasattr(svn.delta, 'tx_invoke_window_handler'):
+ def apply_txdelta_handler(src_stream, target_stream, pool):
+ assert hasattr(src_stream, 'read')
+ assert hasattr(target_stream, 'write')
+ window_handler, baton = svn.delta.tx_apply(src_stream, target_stream,
+ None, pool)
-def apply_txdelta_handler(src_stream, target_stream):
- ret = svn.delta.svn_txdelta_apply(
- src_stream,
- target_stream,
- _global_pool)
+ def wrapper(window):
+ window_handler(window, baton)
- print ret
- def wrapper(window):
- ret[1](window)
+ return wrapper
+else:
+ def apply_txdelta_handler(src_stream, target_stream, pool):
+ assert hasattr(src_stream, 'read')
+ assert hasattr(target_stream, 'write')
+ ret = svn.delta.svn_txdelta_apply(src_stream, target_stream, None, pool)
- return wrapper
+ def wrapper(window):
+ svn.delta.invoke_txdelta_window_handler(
+ ret[1], window, ret[2])
+
+ return wrapper
class SvnRevisionTree(RevisionTree):
- def __init__(self, repository, revision_id, inventory=None):
+ """A tree that existed in a historical Subversion revision."""
+ def __init__(self, repository, revision_id):
self._repository = repository
self._revision_id = revision_id
- (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
+ pool = Pool()
+ (self.branch_path, self.revnum, scheme) = repository.lookup_revision_id(revision_id)
self._inventory = Inventory()
- self.editor = TreeBuildEditor(self)
+ self.id_map = repository.get_fileid_map(self.revnum, self.branch_path,
+ scheme)
+ self.editor = TreeBuildEditor(self, pool)
self.file_data = {}
+ editor, baton = svn.delta.make_editor(self.editor, pool)
+ root_repos = repository.transport.get_repos_root()
+ reporter = repository.transport.do_switch(
+ self.revnum, True,
+ urlutils.join(root_repos, self.branch_path), editor, baton, pool)
+ reporter.set_path("", 0, True, None, pool)
+ reporter.finish_report(pool)
+ pool.destroy()
- editor, baton = svn.delta.make_editor(self.editor)
-
- mutter('do update: %r, %r' % (self.revnum, self.branch_path))
- reporter, reporter_baton = svn.ra.do_update(repository.ra, self.revnum, self.branch_path, True, editor, baton)
-
- svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
-
- svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
-
- def get_file_lines(self, file_id):
- return self.file_data[file_id].splitlines(True)
+ def get_file_lines(self, file_id):
+ return osutils.split_lines(self.file_data[file_id])
class TreeBuildEditor(svn.delta.Editor):
- def __init__(self, tree):
+ """Builds a tree given Subversion tree transform calls."""
+ def __init__(self, tree, pool):
self.tree = tree
self.repository = tree._repository
self.last_revnum = {}
self.dir_revnum = {}
self.dir_ignores = {}
+ self.pool = pool
def set_target_revision(self, revnum):
self.revnum = revnum
def open_root(self, revnum, baton):
- return ROOT_ID
-
- def relpath(self, path):
- bp, rp = self.tree._repository.scheme.unprefix(path)
- if bp == self.tree.branch_path:
- return rp
- return None
-
- def get_file_id(self, path, revnum):
- return self.tree._repository.path_to_file_id(revnum, path)
+ file_id, revision_id = self.tree.id_map[""]
+ ie = self.tree._inventory.add_path("", 'directory', file_id)
+ ie.revision = revision_id
+ self.tree._inventory.revision_id = revision_id
+ return file_id
def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
- relpath = self.relpath(path)
- if relpath is None:
- return ROOT_ID
- file_id, revision_id = self.get_file_id(path, self.revnum)
- ie = self.tree._inventory.add_path(relpath, 'directory', file_id)
- if ie is None:
- self.tree._inventory.revision_id = revision_id
- return ROOT_ID
-
+ path = path.decode("utf-8")
+ file_id, revision_id = self.tree.id_map[path]
+ ie = self.tree._inventory.add_path(path, 'directory', file_id)
ie.revision = revision_id
return file_id
def change_dir_prop(self, id, name, value, pool):
+ from repository import (SVN_PROP_BZR_ANCESTRY,
+ SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO,
+ SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID,
+ SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_MERGE)
+
if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
self.dir_revnum[id] = int(value)
elif name == svn.core.SVN_PROP_IGNORE:
self.dir_ignores[id] = value
+ elif name.startswith(SVN_PROP_BZR_ANCESTRY):
+ if id != self.tree._inventory.root.file_id:
+ mutter('%r set on non-root dir!' % name)
+ return
+ elif name in (SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_BRANCHING_SCHEME):
+ if id != self.tree._inventory.root.file_id:
+ mutter('%r set on non-root dir!' % name)
+ return
elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
svn.core.SVN_PROP_ENTRY_UUID,
svn.core.SVN_PROP_EXECUTABLE):
pass
- else:
- mutter('unsupported file property %r' % name)
+ elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
+ pass
+ elif (name == SVN_PROP_BZR_REVISION_INFO or
+ name.startswith(SVN_PROP_BZR_REVISION_ID)):
+ pass
+ elif name == SVN_PROP_BZR_MERGE:
+ pass
+ elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
+ name.startswith(SVN_PROP_BZR_PREFIX)):
+ mutter('unsupported dir property %r' % name)
def change_file_prop(self, id, name, value, pool):
- if (name == svn.core.SVN_PROP_EXECUTABLE and
- value == svn.core.SVN_PROP_EXECUTABLE_VALUE):
- self.is_executable = True
- elif (name == svn.core.SVN_PROP_SPECIAL and
- value == svn.core.SVN_PROP_SPECIAL_VALUE):
- self.is_symlink = True
+ from repository import SVN_PROP_BZR_PREFIX
+
+ if name == svn.core.SVN_PROP_EXECUTABLE:
+ self.is_executable = (value != None)
+ elif name == svn.core.SVN_PROP_SPECIAL:
+ self.is_symlink = (value != None)
elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
self.last_file_rev = int(value)
elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
svn.core.SVN_PROP_ENTRY_UUID,
svn.core.SVN_PROP_MIME_TYPE):
pass
- else:
+ elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
+ pass
+ elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
+ name.startswith(SVN_PROP_BZR_PREFIX)):
mutter('unsupported file property %r' % name)
def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
+ path = path.decode("utf-8")
self.is_symlink = False
self.is_executable = False
return path
self.tree._inventory[id].ignores = self.dir_ignores[id]
def close_file(self, path, checksum):
- relpath = self.relpath(path)
- if relpath is None:
- return
-
- file_id, revision_id = self.get_file_id(path, self.revnum)
-
- ie = self.tree._inventory.add_path(relpath, 'file', file_id)
+ file_id, revision_id = self.tree.id_map[path]
+ if self.is_symlink:
+ ie = self.tree._inventory.add_path(path, 'symlink', file_id)
+ else:
+ ie = self.tree._inventory.add_path(path, 'file', file_id)
ie.revision = revision_id
if self.file_stream:
else:
file_data = ""
+ actual_checksum = md5.new(file_data).hexdigest()
+ assert(checksum is None or checksum == actual_checksum,
+ "checksum mismatch: %r != %r" % (checksum, actual_checksum))
+
if self.is_symlink:
- ie.kind = 'symlink'
ie.symlink_target = file_data[len("link "):]
+ ie.text_sha1 = None
+ ie.text_size = None
+ ie.text_id = None
+ ie.executable = False
else:
ie.text_sha1 = osutils.sha_string(file_data)
ie.text_size = len(file_data)
def apply_textdelta(self, file_id, base_checksum):
self.file_stream = StringIO()
- return apply_txdelta_handler(StringIO(""), self.file_stream)
-
-
-class SvnInventoryFile(InventoryFile):
- """Inventory entry that can either be a plain file or a
- symbolic link. Avoids fetching data until necessary. """
- def __init__(self, file_id, name, parent_id, repository, path, revnum,
- has_props):
- self.repository = repository
- self.path = path
- self.has_props = has_props
- self.revnum = revnum
- InventoryFile.__init__(self, file_id, name, parent_id)
-
- def _get_sha1(self):
- text = self.repository._get_file(self.path, self.revnum).read()
- return osutils.sha_string(text)
-
- def _get_executable(self):
- if not self.has_props:
- return False
-
- value = self.repository._get_file_prop(self.path, self.revnum,
- svn.core.SVN_PROP_EXECUTABLE)
- if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
- return True
- return False
-
- def _is_special(self):
- if not self.has_props:
- return False
-
- value = self.repository._get_file_prop(self.path, self.revnum,
- svn.core.SVN_PROP_SPECIAL)
- if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
- return True
- return False
-
- def _get_symlink_target(self):
- if not self._is_special():
- return None
- data = self.repository._get_file(self.path, self.revnum).read()
- if not data.startswith("link "):
- raise BzrError("Improperly formatted symlink file")
- return data[len("link "):]
-
- def _get_kind(self):
- if self._is_special():
- return 'symlink'
- return 'file'
-
- # FIXME: we need a set function here because of InventoryEntry.__init__
- def _phony_set(self, data):
- pass
-
- text_sha1 = property(_get_sha1, _phony_set)
- executable = property(_get_executable, _phony_set)
- symlink_target = property(_get_symlink_target, _phony_set)
- kind = property(_get_kind, _phony_set)
-
-
-class SlowSvnRevisionTree(RevisionTree):
- """Original implementation of SvnRevisionTree.
-
- More roundtrip intensive than SvnRevisionTree, but more
- efficient on bandwidth usage if the full tree isn't used.
- """
- def __init__(self, repository, revision_id, inventory=None):
- self._repository = repository
- self._revision_id = revision_id
- if inventory:
- self._inventory = inventory
- else:
- self._inventory = repository.get_inventory(revision_id)
- (self._branch_path, self._revnum) = repository.parse_revision_id(revision_id)
-
- self.fetch_inventory()
-
- def fetch_inventory(self):
- mutter('getting inventory %r for branch %r' % (self._revnum, self._branch_path))
-
- def read_directory(inv, id, path, revnum):
- (props, dirents) = self._cache_get_dir(path, revnum)
-
- recurse = {}
+ return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
- for child_name in dirents:
- dirent = dirents[child_name]
-
- child_path = os.path.join(path, child_name)
-
- (child_id, revid) = self.path_to_file_id(dirent.created_rev,
- child_path)
- if dirent.kind == svn.core.svn_node_dir:
- inventry = InventoryDirectory(child_id, child_name, id)
- recurse[child_path] = dirent.created_rev
- elif dirent.kind == svn.core.svn_node_file:
- inventry = SvnInventoryFile(child_id, child_name, id, self,
- child_path, dirent.created_rev, dirent.has_props)
+class SvnBasisTree(RevisionTree):
+ """Optimized version of SvnRevisionTree."""
+ def __init__(self, workingtree):
+ self.workingtree = workingtree
+ self._revision_id = workingtree.branch.generate_revision_id(
+ workingtree.base_revnum)
+ self.id_map = workingtree.branch.repository.get_fileid_map(
+ workingtree.base_revnum,
+ workingtree.branch.get_branch_path(workingtree.base_revnum),
+ workingtree.branch.scheme)
+ self._inventory = Inventory(root_id=None)
+ self._repository = workingtree.branch.repository
+
+ def add_file_to_inv(relpath, id, revid, wc):
+ props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath), wc)
+ if isinstance(props, list): # Subversion 1.5
+ props = props[1]
+ if props.has_key(svn.core.SVN_PROP_SPECIAL):
+ ie = self._inventory.add_path(relpath, 'symlink', id)
+ ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
+ ie.text_sha1 = None
+ ie.text_size = None
+ ie.text_id = None
+ ie.executable = False
+ else:
+ ie = self._inventory.add_path(relpath, 'file', id)
+ data = osutils.fingerprint_file(open(self._abspath(relpath)))
+ ie.text_sha1 = data['sha1']
+ ie.text_size = data['size']
+ ie.executable = props.has_key(svn.core.SVN_PROP_EXECUTABLE)
+ ie.revision = revid
+ return ie
+
+ def find_ids(entry):
+ relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
+ if entry.schedule in (svn.wc.schedule_normal,
+ svn.wc.schedule_delete,
+ svn.wc.schedule_replace):
+ return self.id_map[workingtree.branch.scheme.unprefix(relpath)[1]]
+ return (None, None)
+
+ def add_dir_to_inv(relpath, wc, parent_id):
+ entries = svn.wc.entries_read(wc, False)
+ entry = entries[""]
+ (id, revid) = find_ids(entry)
+ if id == None:
+ return
+
+ # First handle directory itself
+ ie = self._inventory.add_path(relpath, 'directory', id)
+ ie.revision = revid
+ if relpath == "":
+ self._inventory.revision_id = revid
+
+ for name in entries:
+ if name == "":
+ continue
+
+ subrelpath = os.path.join(relpath, name)
+
+ entry = entries[name]
+ assert entry
+
+ if entry.kind == svn.core.svn_node_dir:
+ subwc = svn.wc.adm_open3(wc,
+ self.workingtree.abspath(subrelpath),
+ False, 0, None)
+ try:
+ add_dir_to_inv(subrelpath, subwc, id)
+ finally:
+ svn.wc.adm_close(subwc)
else:
- raise BzrError("Unknown entry kind for '%s': %s" %
- (child_path, dirent.kind))
-
- inventry.revision = revid
- inv.add(inventry)
-
- for child_path in recurse:
- (child_id, _) = self.path_to_file_id(recurse[child_path],
- child_path)
- read_directory(inv, child_id, child_path, recurse[child_path])
-
- inv = Inventory(revision_id=self._revision_id, root_id=ROOT_ID)
- inv[ROOT_ID].revision = self._revision_id
+ (subid, subrevid) = find_ids(entry)
+ if subid is not None:
+ add_file_to_inv(subrelpath, subid, subrevid, wc)
- assert path != None
- read_directory(inv, ROOT_ID, self._branch_path, self._revnum)
+ wc = workingtree._get_wc()
+ try:
+ add_dir_to_inv("", wc, None)
+ finally:
+ svn.wc.adm_close(wc)
- return inv
-
- def get_file_lines(self, file_id):
- path = "%s/%s" % (self._branch_path, self.id2path(file_id))
- stream = self._repository._get_file(path, self._revnum)
- return stream.readlines()
-
-
-class SvnBasisTree(SvnRevisionTree):
- """Optimized version of SvnRevisionTree."""
- def __init__(self, workingtree, revid):
- super(SvnBasisTree, self).__init__(workingtree.branch.repository,
- revid)
- self.workingtree = workingtree
+ def _abspath(self, relpath):
+ return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
def get_file_lines(self, file_id):
- path = self.id2path(file_id)
- base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
- return open(base_copy).readlines()
+ base_copy = self._abspath(self.id2path(file_id))
+ return osutils.split_lines(open(base_copy).read())