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
31 from cStringIO import StringIO
33 import svn.core, svn.wc, svn.delta
34 from svn.core import SubversionException, Pool
38 def apply_txdelta_handler(src_stream, target_stream):
39 assert hasattr(src_stream, 'read')
40 assert hasattr(target_stream, 'write')
41 ret = svn.delta.svn_txdelta_apply(
48 svn.delta.invoke_txdelta_window_handler(
49 ret[1], window, ret[2])
53 class SvnRevisionTree(RevisionTree):
54 def __init__(self, repository, revision_id, inventory=None):
55 self._repository = repository
56 self._revision_id = revision_id
57 (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
58 self._inventory = Inventory()
59 self._inventory.revision_id = revision_id
60 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
61 self.editor = TreeBuildEditor(self)
64 editor, baton = svn.delta.make_editor(self.editor)
66 root_repos = svn.ra.get_repos_root(repository.ra)
67 mutter('svn checkout -r %r %r' % (self.revnum, self.branch_path))
68 reporter, reporter_baton = svn.ra.do_switch(repository.ra, self.revnum, "", True, os.path.join(root_repos, self.branch_path), editor, baton)
70 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
72 svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
74 def get_file_lines(self, file_id):
75 return osutils.split_lines(self.file_data[file_id])
78 class TreeBuildEditor(svn.delta.Editor):
79 def __init__(self, tree):
81 self.repository = tree._repository
86 def set_target_revision(self, revnum):
89 def open_root(self, revnum, baton):
92 def relpath(self, path):
93 bp, rp = self.tree._repository.scheme.unprefix(path)
94 if bp == self.tree.branch_path:
98 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
99 file_id, revision_id = self.tree.id_map[path]
100 ie = self.tree._inventory.add_path(path, 'directory', file_id)
102 ie.revision = revision_id
105 def change_dir_prop(self, id, name, value, pool):
106 from repository import (SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
107 SVN_PROP_BZR_REVPROP_PREFIX)
109 if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
110 self.dir_revnum[id] = int(value)
111 elif name == svn.core.SVN_PROP_IGNORE:
112 self.dir_ignores[id] = value
113 elif name == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
115 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
117 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
118 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
119 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
120 svn.core.SVN_PROP_ENTRY_UUID,
121 svn.core.SVN_PROP_EXECUTABLE):
123 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
126 mutter('unsupported dir property %r' % name)
128 def change_file_prop(self, id, name, value, pool):
129 if name == svn.core.SVN_PROP_EXECUTABLE:
130 self.is_executable = (value != None)
131 elif name == svn.core.SVN_PROP_SPECIAL:
132 self.is_symlink = (value != None)
133 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
134 self.last_file_rev = int(value)
135 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
136 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
137 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
138 svn.core.SVN_PROP_ENTRY_UUID,
139 svn.core.SVN_PROP_MIME_TYPE):
141 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
144 mutter('unsupported file property %r' % name)
146 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
147 self.is_symlink = False
148 self.is_executable = False
151 def close_dir(self, id):
152 if id in self.tree._inventory and self.dir_ignores.has_key(id):
153 self.tree._inventory[id].ignores = self.dir_ignores[id]
155 def close_file(self, path, checksum):
156 file_id, revision_id = self.tree.id_map[path]
159 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
161 ie = self.tree._inventory.add_path(path, 'file', file_id)
162 ie.revision = revision_id
165 self.file_stream.seek(0)
166 file_data = self.file_stream.read()
170 actual_checksum = md5.new(file_data).hexdigest()
171 assert(checksum is None or checksum == actual_checksum,
172 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
175 ie.symlink_target = file_data[len("link "):]
180 ie.text_sha1 = osutils.sha_string(file_data)
181 ie.text_size = len(file_data)
182 self.tree.file_data[file_id] = file_data
183 ie.executable = self.is_executable
185 self.file_stream = None
187 def close_edit(self):
190 def abort_edit(self):
193 def apply_textdelta(self, file_id, base_checksum):
194 self.file_stream = StringIO()
195 return apply_txdelta_handler(StringIO(""), self.file_stream)
198 class SvnInventoryFile(InventoryFile):
199 """Inventory entry that can either be a plain file or a
200 symbolic link. Avoids fetching data until necessary. """
201 def __init__(self, file_id, name, parent_id, repository, path, revnum,
203 self.repository = repository
205 self.has_props = has_props
207 InventoryFile.__init__(self, file_id, name, parent_id)
210 text = self.repository._get_file(self.path, self.revnum).read()
211 return osutils.sha_string(text)
213 def _get_executable(self):
214 if not self.has_props:
217 value = self.repository._get_file_prop(self.path, self.revnum,
218 svn.core.SVN_PROP_EXECUTABLE)
219 if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
223 def _is_special(self):
224 if not self.has_props:
227 value = self.repository._get_file_prop(self.path, self.revnum,
228 svn.core.SVN_PROP_SPECIAL)
229 if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
233 def _get_symlink_target(self):
234 if not self._is_special():
236 data = self.repository._get_file(self.path, self.revnum).read()
237 if not data.startswith("link "):
238 raise BzrError("Improperly formatted symlink file")
239 return data[len("link "):]
242 if self._is_special():
246 # FIXME: we need a set function here because of InventoryEntry.__init__
247 def _phony_set(self, data):
250 text_sha1 = property(_get_sha1, _phony_set)
251 executable = property(_get_executable, _phony_set)
252 symlink_target = property(_get_symlink_target, _phony_set)
253 kind = property(_get_kind, _phony_set)
256 class SvnBasisTree(SvnRevisionTree):
257 """Optimized version of SvnRevisionTree."""
258 def __init__(self, workingtree, revid):
259 super(SvnBasisTree, self).__init__(workingtree.branch.repository,
261 self.workingtree = workingtree
263 def get_file_lines(self, file_id):
264 path = self.id2path(file_id)
265 base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
266 return osutils.split_lines(open(base_copy).read())