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
16 """Access to stored Subversion basis trees."""
18 from bzrlib.inventory import Inventory
20 from bzrlib import osutils, urlutils
21 from bzrlib.trace import mutter
22 from bzrlib.revisiontree import RevisionTree
26 from cStringIO import StringIO
29 import svn.core, svn.wc, svn.delta
30 from svn.core import Pool
32 # Deal with Subversion 1.5 and the patched Subversion 1.4 (which are
33 # slightly different).
35 if hasattr(svn.delta, 'tx_invoke_window_handler'):
36 def apply_txdelta_handler(src_stream, target_stream, pool):
37 assert hasattr(src_stream, 'read')
38 assert hasattr(target_stream, 'write')
39 window_handler, baton = svn.delta.tx_apply(src_stream, target_stream,
43 window_handler(window, baton)
47 def apply_txdelta_handler(src_stream, target_stream, pool):
48 assert hasattr(src_stream, 'read')
49 assert hasattr(target_stream, 'write')
50 ret = svn.delta.svn_txdelta_apply(src_stream, target_stream, None, pool)
53 svn.delta.invoke_txdelta_window_handler(
54 ret[1], window, ret[2])
58 class SvnRevisionTree(RevisionTree):
59 """A tree that existed in a historical Subversion revision."""
60 def __init__(self, repository, revision_id):
61 self._repository = repository
62 self._revision_id = revision_id
64 (self.branch_path, self.revnum, scheme) = repository.lookup_revision_id(revision_id)
65 self._inventory = Inventory()
66 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path,
68 editor = TreeBuildEditor(self, pool)
70 root_repos = repository.transport.get_svn_repos_root()
71 reporter = repository.transport.do_switch(
73 urlutils.join(root_repos, self.branch_path), editor, pool)
74 reporter.set_path("", 0, True, None, pool)
75 reporter.finish_report(pool)
78 def get_file_lines(self, file_id):
79 return osutils.split_lines(self.file_data[file_id])
82 class TreeBuildEditor(svn.delta.Editor):
83 """Builds a tree given Subversion tree transform calls."""
84 def __init__(self, tree, pool):
86 self.repository = tree._repository
92 def set_target_revision(self, revnum):
95 def open_root(self, revnum, baton):
96 file_id, revision_id = self.tree.id_map[""]
97 ie = self.tree._inventory.add_path("", 'directory', file_id)
98 ie.revision = revision_id
99 self.tree._inventory.revision_id = revision_id
102 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
103 path = path.decode("utf-8")
104 file_id, revision_id = self.tree.id_map[path]
105 ie = self.tree._inventory.add_path(path, 'directory', file_id)
106 ie.revision = revision_id
109 def change_dir_prop(self, id, name, value, pool):
110 from repository import (SVN_PROP_BZR_ANCESTRY,
111 SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO,
112 SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID,
113 SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_MERGE)
115 if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
116 self.dir_revnum[id] = int(value)
117 elif name == svn.core.SVN_PROP_IGNORE:
118 self.dir_ignores[id] = value
119 elif name.startswith(SVN_PROP_BZR_ANCESTRY):
120 if id != self.tree._inventory.root.file_id:
121 mutter('%r set on non-root dir!' % name)
123 elif name in (SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_BRANCHING_SCHEME):
124 if id != self.tree._inventory.root.file_id:
125 mutter('%r set on non-root dir!' % name)
127 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
128 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
129 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
130 svn.core.SVN_PROP_ENTRY_UUID,
131 svn.core.SVN_PROP_EXECUTABLE):
133 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
135 elif (name == SVN_PROP_BZR_REVISION_INFO or
136 name.startswith(SVN_PROP_BZR_REVISION_ID)):
138 elif name == SVN_PROP_BZR_MERGE:
140 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
141 name.startswith(SVN_PROP_BZR_PREFIX)):
142 mutter('unsupported dir property %r' % name)
144 def change_file_prop(self, id, name, value, pool):
145 from repository import SVN_PROP_BZR_PREFIX
147 if name == svn.core.SVN_PROP_EXECUTABLE:
148 self.is_executable = (value != None)
149 elif name == svn.core.SVN_PROP_SPECIAL:
150 self.is_symlink = (value != None)
151 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
152 self.last_file_rev = int(value)
153 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
154 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
155 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
156 svn.core.SVN_PROP_ENTRY_UUID,
157 svn.core.SVN_PROP_MIME_TYPE):
159 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
161 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
162 name.startswith(SVN_PROP_BZR_PREFIX)):
163 mutter('unsupported file property %r' % name)
165 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
166 path = path.decode("utf-8")
167 self.is_symlink = False
168 self.is_executable = False
171 def close_dir(self, id):
172 if id in self.tree._inventory and self.dir_ignores.has_key(id):
173 self.tree._inventory[id].ignores = self.dir_ignores[id]
175 def close_file(self, path, checksum):
176 file_id, revision_id = self.tree.id_map[path]
178 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
180 ie = self.tree._inventory.add_path(path, 'file', file_id)
181 ie.revision = revision_id
184 self.file_stream.seek(0)
185 file_data = self.file_stream.read()
189 actual_checksum = md5.new(file_data).hexdigest()
190 assert(checksum is None or checksum == actual_checksum,
191 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
194 ie.symlink_target = file_data[len("link "):]
198 ie.executable = False
200 ie.text_sha1 = osutils.sha_string(file_data)
201 ie.text_size = len(file_data)
202 self.tree.file_data[file_id] = file_data
203 ie.executable = self.is_executable
205 self.file_stream = None
207 def close_edit(self):
210 def abort_edit(self):
213 def apply_textdelta(self, file_id, base_checksum):
214 self.file_stream = StringIO()
215 return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
218 class SvnBasisTree(RevisionTree):
219 """Optimized version of SvnRevisionTree."""
220 def __init__(self, workingtree):
221 self.workingtree = workingtree
222 self._revision_id = workingtree.branch.generate_revision_id(
223 workingtree.base_revnum)
224 self.id_map = workingtree.branch.repository.get_fileid_map(
225 workingtree.base_revnum,
226 workingtree.branch.get_branch_path(workingtree.base_revnum),
227 workingtree.branch.scheme)
228 self._inventory = Inventory(root_id=None)
229 self._repository = workingtree.branch.repository
231 def add_file_to_inv(relpath, id, revid, wc):
232 props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath), wc)
233 if isinstance(props, list): # Subversion 1.5
235 if props.has_key(svn.core.SVN_PROP_SPECIAL):
236 ie = self._inventory.add_path(relpath, 'symlink', id)
237 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
241 ie.executable = False
243 ie = self._inventory.add_path(relpath, 'file', id)
244 data = osutils.fingerprint_file(open(self._abspath(relpath)))
245 ie.text_sha1 = data['sha1']
246 ie.text_size = data['size']
247 ie.executable = props.has_key(svn.core.SVN_PROP_EXECUTABLE)
252 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
253 if entry.schedule in (svn.wc.schedule_normal,
254 svn.wc.schedule_delete,
255 svn.wc.schedule_replace):
256 return self.id_map[workingtree.branch.unprefix(relpath)]
259 def add_dir_to_inv(relpath, wc, parent_id):
260 entries = svn.wc.entries_read(wc, False)
262 (id, revid) = find_ids(entry)
266 # First handle directory itself
267 ie = self._inventory.add_path(relpath, 'directory', id)
270 self._inventory.revision_id = revid
276 subrelpath = os.path.join(relpath, name)
278 entry = entries[name]
281 if entry.kind == svn.core.svn_node_dir:
282 subwc = svn.wc.adm_open3(wc,
283 self.workingtree.abspath(subrelpath),
286 add_dir_to_inv(subrelpath, subwc, id)
288 svn.wc.adm_close(subwc)
290 (subid, subrevid) = find_ids(entry)
291 if subid is not None:
292 add_file_to_inv(subrelpath, subid, subrevid, wc)
294 wc = workingtree._get_wc()
296 add_dir_to_inv("", wc, None)
300 def _abspath(self, relpath):
301 return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
303 def get_file_lines(self, file_id):
304 base_copy = self._abspath(self.id2path(file_id))
305 return osutils.split_lines(open(base_copy).read())