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 self.editor = TreeBuildEditor(self, pool)
70 editor, baton = svn.delta.make_editor(self.editor, pool)
71 root_repos = repository.transport.get_repos_root()
72 reporter = repository.transport.do_switch(
74 urlutils.join(root_repos, self.branch_path), editor, baton, pool)
75 reporter.set_path("", 0, True, None, pool)
76 reporter.finish_report(pool)
79 def get_file_lines(self, file_id):
80 return osutils.split_lines(self.file_data[file_id])
83 class TreeBuildEditor(svn.delta.Editor):
84 """Builds a tree given Subversion tree transform calls."""
85 def __init__(self, tree, pool):
87 self.repository = tree._repository
93 def set_target_revision(self, revnum):
96 def open_root(self, revnum, baton):
97 file_id, revision_id = self.tree.id_map[""]
98 ie = self.tree._inventory.add_path("", 'directory', file_id)
99 ie.revision = revision_id
100 self.tree._inventory.revision_id = revision_id
103 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
104 path = path.decode("utf-8")
105 file_id, revision_id = self.tree.id_map[path]
106 ie = self.tree._inventory.add_path(path, 'directory', file_id)
107 ie.revision = revision_id
110 def change_dir_prop(self, id, name, value, pool):
111 from repository import (SVN_PROP_BZR_ANCESTRY,
112 SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO,
113 SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID,
114 SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_MERGE)
116 if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
117 self.dir_revnum[id] = int(value)
118 elif name == svn.core.SVN_PROP_IGNORE:
119 self.dir_ignores[id] = value
120 elif name.startswith(SVN_PROP_BZR_ANCESTRY):
121 if id != self.tree._inventory.root.file_id:
122 mutter('%r set on non-root dir!' % name)
124 elif name in (SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_BRANCHING_SCHEME):
125 if id != self.tree._inventory.root.file_id:
126 mutter('%r set on non-root dir!' % name)
128 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
129 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
130 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
131 svn.core.SVN_PROP_ENTRY_UUID,
132 svn.core.SVN_PROP_EXECUTABLE):
134 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
136 elif (name == SVN_PROP_BZR_REVISION_INFO or
137 name.startswith(SVN_PROP_BZR_REVISION_ID)):
139 elif name == SVN_PROP_BZR_MERGE:
141 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
142 name.startswith(SVN_PROP_BZR_PREFIX)):
143 mutter('unsupported dir property %r' % name)
145 def change_file_prop(self, id, name, value, pool):
146 from repository import SVN_PROP_BZR_PREFIX
148 if name == svn.core.SVN_PROP_EXECUTABLE:
149 self.is_executable = (value != None)
150 elif name == svn.core.SVN_PROP_SPECIAL:
151 self.is_symlink = (value != None)
152 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
153 self.last_file_rev = int(value)
154 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
155 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
156 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
157 svn.core.SVN_PROP_ENTRY_UUID,
158 svn.core.SVN_PROP_MIME_TYPE):
160 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
162 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
163 name.startswith(SVN_PROP_BZR_PREFIX)):
164 mutter('unsupported file property %r' % name)
166 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
167 path = path.decode("utf-8")
168 self.is_symlink = False
169 self.is_executable = False
172 def close_dir(self, id):
173 if id in self.tree._inventory and self.dir_ignores.has_key(id):
174 self.tree._inventory[id].ignores = self.dir_ignores[id]
176 def close_file(self, path, checksum):
177 file_id, revision_id = self.tree.id_map[path]
179 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
181 ie = self.tree._inventory.add_path(path, 'file', file_id)
182 ie.revision = revision_id
185 self.file_stream.seek(0)
186 file_data = self.file_stream.read()
190 actual_checksum = md5.new(file_data).hexdigest()
191 assert(checksum is None or checksum == actual_checksum,
192 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
195 ie.symlink_target = file_data[len("link "):]
199 ie.executable = False
201 ie.text_sha1 = osutils.sha_string(file_data)
202 ie.text_size = len(file_data)
203 self.tree.file_data[file_id] = file_data
204 ie.executable = self.is_executable
206 self.file_stream = None
208 def close_edit(self):
211 def abort_edit(self):
214 def apply_textdelta(self, file_id, base_checksum):
215 self.file_stream = StringIO()
216 return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
219 class SvnBasisTree(RevisionTree):
220 """Optimized version of SvnRevisionTree."""
221 def __init__(self, workingtree):
222 self.workingtree = workingtree
223 self._revision_id = workingtree.branch.generate_revision_id(
224 workingtree.base_revnum)
225 self.id_map = workingtree.branch.repository.get_fileid_map(
226 workingtree.base_revnum,
227 workingtree.branch.get_branch_path(workingtree.base_revnum),
228 workingtree.branch.scheme)
229 self._inventory = Inventory(root_id=None)
230 self._repository = workingtree.branch.repository
232 def add_file_to_inv(relpath, id, revid, wc):
233 props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath), wc)
234 if isinstance(props, list): # Subversion 1.5
236 if props.has_key(svn.core.SVN_PROP_SPECIAL):
237 ie = self._inventory.add_path(relpath, 'symlink', id)
238 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
242 ie.executable = False
244 ie = self._inventory.add_path(relpath, 'file', id)
245 data = osutils.fingerprint_file(open(self._abspath(relpath)))
246 ie.text_sha1 = data['sha1']
247 ie.text_size = data['size']
248 ie.executable = props.has_key(svn.core.SVN_PROP_EXECUTABLE)
253 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
254 if entry.schedule in (svn.wc.schedule_normal,
255 svn.wc.schedule_delete,
256 svn.wc.schedule_replace):
257 return self.id_map[workingtree.branch.scheme.unprefix(relpath)[1]]
260 def add_dir_to_inv(relpath, wc, parent_id):
261 entries = svn.wc.entries_read(wc, False)
263 (id, revid) = find_ids(entry)
267 # First handle directory itself
268 ie = self._inventory.add_path(relpath, 'directory', id)
271 self._inventory.revision_id = revid
277 subrelpath = os.path.join(relpath, name)
279 entry = entries[name]
282 if entry.kind == svn.core.svn_node_dir:
283 subwc = svn.wc.adm_open3(wc,
284 self.workingtree.abspath(subrelpath),
287 add_dir_to_inv(subrelpath, subwc, id)
289 svn.wc.adm_close(subwc)
291 (subid, subrevid) = find_ids(entry)
292 if subid is not None:
293 add_file_to_inv(subrelpath, subid, subrevid, wc)
295 wc = workingtree._get_wc()
297 add_dir_to_inv("", wc, None)
301 def _abspath(self, relpath):
302 return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
304 def get_file_lines(self, file_id):
305 base_copy = self._abspath(self.id2path(file_id))
306 return osutils.split_lines(open(base_copy).read())