1 # Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 from binascii import hexlify
18 from bzrlib.bzrdir import BzrDirFormat
19 from bzrlib.errors import NotBranchError, NoSuchFile
20 from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile,
22 from bzrlib.lockable_files import TransportLock, LockableFiles
23 from bzrlib.lockdir import LockDir
24 import bzrlib.osutils as osutils
25 from bzrlib.progress import DummyProgress
26 from bzrlib.trace import mutter
27 from bzrlib.tree import RevisionTree, EmptyTree
30 from cStringIO import StringIO
32 import svn.core, svn.wc, svn.delta
33 from svn.core import SubversionException, Pool
37 def apply_txdelta_handler(src_stream, target_stream):
38 ret = svn.delta.svn_txdelta_apply(
49 class SvnRevisionTree(RevisionTree):
50 def __init__(self, repository, revision_id, inventory=None):
51 self._repository = repository
52 self._revision_id = revision_id
53 (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
54 self._inventory = Inventory()
55 self.editor = TreeBuildEditor(self)
58 editor, baton = svn.delta.make_editor(self.editor)
60 mutter('do update: %r, %r' % (self.revnum, self.branch_path))
61 reporter, reporter_baton = svn.ra.do_update(repository.ra, self.revnum, self.branch_path, True, editor, baton)
63 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
65 svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
67 def get_file_lines(self, file_id):
68 return self.file_data[file_id].splitlines(True)
71 class TreeBuildEditor(svn.delta.Editor):
72 def __init__(self, tree):
74 self.repository = tree._repository
79 def set_target_revision(self, revnum):
82 def open_root(self, revnum, baton):
85 def relpath(self, path):
86 bp, rp = self.tree._repository.scheme.unprefix(path)
87 if bp == self.tree.branch_path:
91 def get_file_id(self, path, revnum):
92 return self.tree._repository.path_to_file_id(revnum, path)
94 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
95 relpath = self.relpath(path)
98 file_id, revision_id = self.get_file_id(path, self.revnum)
99 ie = self.tree._inventory.add_path(relpath, 'directory', file_id)
101 self.tree._inventory.revision_id = revision_id
104 ie.revision = revision_id
107 def change_dir_prop(self, id, name, value, pool):
108 if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
109 self.dir_revnum[id] = int(value)
110 elif name == svn.core.SVN_PROP_IGNORE:
111 self.dir_ignores[id] = value
112 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
113 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
114 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
115 svn.core.SVN_PROP_ENTRY_UUID,
116 svn.core.SVN_PROP_EXECUTABLE):
119 mutter('unsupported file property %r' % name)
121 def change_file_prop(self, id, name, value, pool):
122 if (name == svn.core.SVN_PROP_EXECUTABLE and
123 value == svn.core.SVN_PROP_EXECUTABLE_VALUE):
124 self.is_executable = True
125 elif (name == svn.core.SVN_PROP_SPECIAL and
126 value == svn.core.SVN_PROP_SPECIAL_VALUE):
127 self.is_symlink = True
128 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
129 self.last_file_rev = int(value)
130 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
131 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
132 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
133 svn.core.SVN_PROP_ENTRY_UUID,
134 svn.core.SVN_PROP_MIME_TYPE):
137 mutter('unsupported file property %r' % name)
139 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
140 self.is_symlink = False
141 self.is_executable = False
144 def close_dir(self, id):
145 if id in self.tree._inventory and self.dir_ignores.has_key(id):
146 self.tree._inventory[id].ignores = self.dir_ignores[id]
148 def close_file(self, path, checksum):
149 relpath = self.relpath(path)
153 file_id, revision_id = self.get_file_id(path, self.revnum)
155 ie = self.tree._inventory.add_path(relpath, 'file', file_id)
156 ie.revision = revision_id
159 self.file_stream.seek(0)
160 file_data = self.file_stream.read()
166 ie.symlink_target = file_data[len("link "):]
168 ie.text_sha1 = osutils.sha_string(file_data)
169 ie.text_size = len(file_data)
170 self.tree.file_data[file_id] = file_data
171 ie.executable = self.is_executable
173 self.file_stream = None
175 def close_edit(self):
178 def abort_edit(self):
181 def apply_textdelta(self, file_id, base_checksum):
182 self.file_stream = StringIO()
183 return apply_txdelta_handler(StringIO(""), self.file_stream)
186 class SvnInventoryFile(InventoryFile):
187 """Inventory entry that can either be a plain file or a
188 symbolic link. Avoids fetching data until necessary. """
189 def __init__(self, file_id, name, parent_id, repository, path, revnum,
191 self.repository = repository
193 self.has_props = has_props
195 InventoryFile.__init__(self, file_id, name, parent_id)
198 text = self.repository._get_file(self.path, self.revnum).read()
199 return osutils.sha_string(text)
201 def _get_executable(self):
202 if not self.has_props:
205 value = self.repository._get_file_prop(self.path, self.revnum,
206 svn.core.SVN_PROP_EXECUTABLE)
207 if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
211 def _is_special(self):
212 if not self.has_props:
215 value = self.repository._get_file_prop(self.path, self.revnum,
216 svn.core.SVN_PROP_SPECIAL)
217 if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
221 def _get_symlink_target(self):
222 if not self._is_special():
224 data = self.repository._get_file(self.path, self.revnum).read()
225 if not data.startswith("link "):
226 raise BzrError("Improperly formatted symlink file")
227 return data[len("link "):]
230 if self._is_special():
234 # FIXME: we need a set function here because of InventoryEntry.__init__
235 def _phony_set(self, data):
238 text_sha1 = property(_get_sha1, _phony_set)
239 executable = property(_get_executable, _phony_set)
240 symlink_target = property(_get_symlink_target, _phony_set)
241 kind = property(_get_kind, _phony_set)
244 class SlowSvnRevisionTree(RevisionTree):
245 """Original implementation of SvnRevisionTree.
247 More roundtrip intensive than SvnRevisionTree, but more
248 efficient on bandwidth usage if the full tree isn't used.
250 def __init__(self, repository, revision_id, inventory=None):
251 self._repository = repository
252 self._revision_id = revision_id
254 self._inventory = inventory
256 self._inventory = repository.get_inventory(revision_id)
257 (self._branch_path, self._revnum) = repository.parse_revision_id(revision_id)
259 self.fetch_inventory()
261 def fetch_inventory(self):
262 mutter('getting inventory %r for branch %r' % (self._revnum, self._branch_path))
264 def read_directory(inv, id, path, revnum):
265 (props, dirents) = self._cache_get_dir(path, revnum)
269 for child_name in dirents:
270 dirent = dirents[child_name]
272 child_path = os.path.join(path, child_name)
274 (child_id, revid) = self.path_to_file_id(dirent.created_rev,
276 if dirent.kind == svn.core.svn_node_dir:
277 inventry = InventoryDirectory(child_id, child_name, id)
278 recurse[child_path] = dirent.created_rev
279 elif dirent.kind == svn.core.svn_node_file:
280 inventry = SvnInventoryFile(child_id, child_name, id, self,
281 child_path, dirent.created_rev, dirent.has_props)
284 raise BzrError("Unknown entry kind for '%s': %s" %
285 (child_path, dirent.kind))
287 inventry.revision = revid
290 for child_path in recurse:
291 (child_id, _) = self.path_to_file_id(recurse[child_path],
293 read_directory(inv, child_id, child_path, recurse[child_path])
295 inv = Inventory(revision_id=self._revision_id, root_id=ROOT_ID)
296 inv[ROOT_ID].revision = self._revision_id
299 read_directory(inv, ROOT_ID, self._branch_path, self._revnum)
303 def get_file_lines(self, file_id):
304 path = "%s/%s" % (self._branch_path, self.id2path(file_id))
305 stream = self._repository._get_file(path, self._revnum)
306 return stream.readlines()
309 class SvnBasisTree(SvnRevisionTree):
310 """Optimized version of SvnRevisionTree."""
311 def __init__(self, workingtree, revid):
312 super(SvnBasisTree, self).__init__(workingtree.branch.repository,
314 self.workingtree = workingtree
316 def get_file_lines(self, file_id):
317 path = self.id2path(file_id)
318 base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
319 return open(base_copy).readlines()