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 import bzrlib.osutils as osutils
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 def apply_txdelta_handler(src_stream, target_stream, pool):
33 assert hasattr(src_stream, 'read')
34 assert hasattr(target_stream, 'write')
35 ret = svn.delta.svn_txdelta_apply(
42 svn.delta.invoke_txdelta_window_handler(
43 ret[1], window, ret[2])
47 class SvnRevisionTree(RevisionTree):
48 """A tree that existed in a historical Subversion revision."""
49 def __init__(self, repository, revision_id):
50 self._repository = repository
51 self._revision_id = revision_id
53 (self.branch_path, self.revnum) = repository.lookup_revision_id(revision_id)
54 self._inventory = Inventory()
55 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
56 self.editor = TreeBuildEditor(self, pool)
58 editor, baton = svn.delta.make_editor(self.editor, pool)
59 root_repos = repository.transport.get_repos_root()
60 reporter = repository.transport.do_switch(
61 self.revnum, "", True,
62 os.path.join(root_repos, self.branch_path), editor, baton, pool)
63 reporter.set_path("", 0, True, None, pool)
64 reporter.finish_report(pool)
67 def get_file_lines(self, file_id):
68 return osutils.split_lines(self.file_data[file_id])
71 class TreeBuildEditor(svn.delta.Editor):
72 """Builds a tree given Subversion tree transform calls."""
73 def __init__(self, tree, pool):
75 self.repository = tree._repository
81 def set_target_revision(self, revnum):
84 def open_root(self, revnum, baton):
85 file_id, revision_id = self.tree.id_map[""]
86 ie = self.tree._inventory.add_path("", 'directory', file_id)
87 ie.revision = revision_id
88 self.tree._inventory.revision_id = revision_id
91 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
92 path = path.decode("utf-8")
93 file_id, revision_id = self.tree.id_map[path]
94 ie = self.tree._inventory.add_path(path, 'directory', file_id)
95 ie.revision = revision_id
98 def change_dir_prop(self, id, name, value, pool):
99 from repository import (SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
100 SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVPROP_PREFIX,
101 SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID)
103 if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
104 self.dir_revnum[id] = int(value)
105 elif name == svn.core.SVN_PROP_IGNORE:
106 self.dir_ignores[id] = value
107 elif name == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
108 if id != self.tree._inventory.root.file_id:
109 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
111 elif name == SVN_PROP_BZR_FILEIDS:
112 if id != self.tree._inventory.root.file_id:
113 mutter('%r set on non-root dir!' % SVN_PROP_BZR_FILEIDS)
115 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
116 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
117 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
118 svn.core.SVN_PROP_ENTRY_UUID,
119 svn.core.SVN_PROP_EXECUTABLE):
121 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
123 elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
125 elif name == SVN_PROP_BZR_REVISION_ID:
127 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
128 name.startswith(SVN_PROP_BZR_PREFIX)):
129 mutter('unsupported dir property %r' % name)
131 def change_file_prop(self, id, name, value, pool):
132 from repository import SVN_PROP_BZR_PREFIX
134 if name == svn.core.SVN_PROP_EXECUTABLE:
135 self.is_executable = (value != None)
136 elif name == svn.core.SVN_PROP_SPECIAL:
137 self.is_symlink = (value != None)
138 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
139 self.last_file_rev = int(value)
140 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
141 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
142 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
143 svn.core.SVN_PROP_ENTRY_UUID,
144 svn.core.SVN_PROP_MIME_TYPE):
146 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
148 elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
149 name.startswith(SVN_PROP_BZR_PREFIX)):
150 mutter('unsupported file property %r' % name)
152 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
153 path = path.decode("utf-8")
154 self.is_symlink = False
155 self.is_executable = False
158 def close_dir(self, id):
159 if id in self.tree._inventory and self.dir_ignores.has_key(id):
160 self.tree._inventory[id].ignores = self.dir_ignores[id]
162 def close_file(self, path, checksum):
163 file_id, revision_id = self.tree.id_map[path]
165 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
167 ie = self.tree._inventory.add_path(path, 'file', file_id)
168 ie.revision = revision_id
171 self.file_stream.seek(0)
172 file_data = self.file_stream.read()
176 actual_checksum = md5.new(file_data).hexdigest()
177 assert(checksum is None or checksum == actual_checksum,
178 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
181 ie.symlink_target = file_data[len("link "):]
185 ie.executable = False
187 ie.text_sha1 = osutils.sha_string(file_data)
188 ie.text_size = len(file_data)
189 self.tree.file_data[file_id] = file_data
190 ie.executable = self.is_executable
192 self.file_stream = None
194 def close_edit(self):
197 def abort_edit(self):
200 def apply_textdelta(self, file_id, base_checksum):
201 self.file_stream = StringIO()
202 return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
205 class SvnBasisTree(RevisionTree):
206 """Optimized version of SvnRevisionTree."""
207 def __init__(self, workingtree):
208 self.workingtree = workingtree
209 self._revision_id = workingtree.branch.generate_revision_id(workingtree.base_revnum)
210 self.id_map = workingtree.branch.repository.get_fileid_map(
211 workingtree.base_revnum, workingtree.branch.branch_path)
212 self._inventory = Inventory(root_id=None)
213 self._repository = workingtree.branch.repository
215 def add_file_to_inv(relpath, id, revid, wc):
216 props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath), wc)
217 if props.has_key(svn.core.SVN_PROP_SPECIAL):
218 ie = self._inventory.add_path(relpath, 'symlink', id)
219 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
223 ie.executable = False
225 ie = self._inventory.add_path(relpath, 'file', id)
226 data = osutils.fingerprint_file(open(self._abspath(relpath)))
227 ie.text_sha1 = data['sha1']
228 ie.text_size = data['size']
229 ie.executable = props.has_key(svn.core.SVN_PROP_EXECUTABLE)
234 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
235 if entry.schedule in (svn.wc.schedule_normal,
236 svn.wc.schedule_delete,
237 svn.wc.schedule_replace):
238 return self.id_map[workingtree.branch.repository.scheme.unprefix(relpath)[1]]
241 def add_dir_to_inv(relpath, wc, parent_id):
242 entries = svn.wc.entries_read(wc, False)
244 (id, revid) = find_ids(entry)
248 # First handle directory itself
249 ie = self._inventory.add_path(relpath, 'directory', id)
252 self._inventory.revision_id = revid
258 subrelpath = os.path.join(relpath, name)
260 entry = entries[name]
263 if entry.kind == svn.core.svn_node_dir:
264 subwc = svn.wc.adm_open3(wc,
265 self.workingtree.abspath(subrelpath),
268 add_dir_to_inv(subrelpath, subwc, id)
270 svn.wc.adm_close(subwc)
272 (subid, subrevid) = find_ids(entry)
273 if subid is not None:
274 add_file_to_inv(subrelpath, subid, subrevid, wc)
276 wc = workingtree._get_wc()
278 add_dir_to_inv("", wc, None)
282 def _abspath(self, relpath):
283 return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
285 def get_file_lines(self, file_id):
286 base_copy = self._abspath(self.id2path(file_id))
287 return osutils.split_lines(open(base_copy).read())