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)
21 from bzrlib.lockable_files import TransportLock, LockableFiles
22 from bzrlib.lockdir import LockDir
23 import bzrlib.osutils as osutils
24 from bzrlib.progress import DummyProgress
25 from bzrlib.revision import NULL_REVISION
26 from bzrlib.trace import mutter
27 from bzrlib.revisiontree import RevisionTree
31 from cStringIO import StringIO
34 import svn.core, svn.wc, svn.delta
35 from svn.core import SubversionException, Pool
37 def apply_txdelta_handler(src_stream, target_stream, pool):
38 assert hasattr(src_stream, 'read')
39 assert hasattr(target_stream, 'write')
40 ret = svn.delta.svn_txdelta_apply(
47 svn.delta.invoke_txdelta_window_handler(
48 ret[1], window, ret[2])
52 class SvnRevisionTree(RevisionTree):
53 def __init__(self, repository, revision_id, inventory=None):
54 self._repository = repository
55 self._revision_id = revision_id
56 (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
57 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
58 self._inventory = Inventory()
60 (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
61 self._inventory = Inventory()
62 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
63 self.editor = TreeBuildEditor(self, pool)
65 editor, baton = svn.delta.make_editor(self.editor, pool)
66 root_repos = repository.transport.get_repos_root()
67 reporter = repository.transport.do_switch(
68 self.revnum, "", True,
69 os.path.join(root_repos, self.branch_path), editor, baton, pool)
70 reporter.set_path("", 0, True, None, pool)
71 reporter.finish_report(pool)
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, pool):
81 self.repository = tree._repository
87 def set_target_revision(self, revnum):
90 def open_root(self, revnum, baton):
91 file_id, revision_id = self.tree.id_map[""]
92 ie = self.tree._inventory.add_path("", 'directory', file_id)
93 ie.revision = revision_id
94 self.tree._inventory.revision_id = revision_id
97 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
98 path = path.decode("utf-8")
99 file_id, revision_id = self.tree.id_map[path]
100 ie = self.tree._inventory.add_path(path, 'directory', file_id)
101 ie.revision = revision_id
104 def change_dir_prop(self, id, name, value, pool):
105 from repository import (SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
106 SVN_PROP_BZR_REVPROP_PREFIX, SVN_PROP_BZR_FILEIDS)
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 == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
113 if id != self.tree.id_map[""][0]:
114 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
116 elif name == SVN_PROP_BZR_FILEIDS:
117 if id != self.tree.id_map[""][0]:
118 mutter('%r set on non-root dir!' % SVN_PROP_BZR_FILEIDS)
120 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
121 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
122 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
123 svn.core.SVN_PROP_ENTRY_UUID,
124 svn.core.SVN_PROP_EXECUTABLE):
126 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
128 elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
131 mutter('unsupported dir property %r' % name)
133 def change_file_prop(self, id, name, value, pool):
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):
149 mutter('unsupported file property %r' % name)
151 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
152 path = path.decode("utf-8")
153 self.is_symlink = False
154 self.is_executable = False
157 def close_dir(self, id):
158 if id in self.tree._inventory and self.dir_ignores.has_key(id):
159 self.tree._inventory[id].ignores = self.dir_ignores[id]
161 def close_file(self, path, checksum):
162 file_id, revision_id = self.tree.id_map[path]
164 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
166 ie = self.tree._inventory.add_path(path, 'file', file_id)
167 ie.revision = revision_id
170 self.file_stream.seek(0)
171 file_data = self.file_stream.read()
175 actual_checksum = md5.new(file_data).hexdigest()
176 assert(checksum is None or checksum == actual_checksum,
177 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
180 ie.symlink_target = file_data[len("link "):]
184 ie.executable = False
186 ie.text_sha1 = osutils.sha_string(file_data)
187 ie.text_size = len(file_data)
188 self.tree.file_data[file_id] = file_data
189 ie.executable = self.is_executable
191 self.file_stream = None
193 def close_edit(self):
196 def abort_edit(self):
199 def apply_textdelta(self, file_id, base_checksum):
200 self.file_stream = StringIO()
201 return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
204 class SvnBasisTree(RevisionTree):
205 """Optimized version of SvnRevisionTree."""
206 def __init__(self, workingtree):
207 self.workingtree = workingtree
208 self._revision_id = workingtree.branch.generate_revision_id(workingtree.base_revnum)
209 self.id_map = workingtree.branch.repository.get_fileid_map(
210 workingtree.base_revnum, workingtree.branch.branch_path)
211 self._inventory = Inventory()
212 self._repository = workingtree.branch.repository
214 def add_file_to_inv(relpath, id, revid, wc):
215 props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath), wc)
216 if props.has_key(svn.core.SVN_PROP_SPECIAL):
217 ie = self._inventory.add_path(relpath, 'symlink', id)
218 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
222 ie.executable = False
224 ie = self._inventory.add_path(relpath, 'file', id)
225 data = osutils.fingerprint_file(open(self._abspath(relpath)))
226 ie.text_sha1 = data['sha1']
227 ie.text_size = data['size']
228 ie.executable = props.has_key(svn.core.SVN_PROP_EXECUTABLE)
233 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
234 if entry.schedule in (svn.wc.schedule_normal,
235 svn.wc.schedule_delete,
236 svn.wc.schedule_replace):
237 return self.id_map[workingtree.branch.repository.scheme.unprefix(relpath)[1]]
240 def add_dir_to_inv(relpath, wc, parent_id):
241 entries = svn.wc.entries_read(wc, False)
243 (id, revid) = find_ids(entry)
247 # First handle directory itself
248 ie = self._inventory.add_path(relpath, 'directory', id)
255 subrelpath = os.path.join(relpath, name)
257 entry = entries[name]
260 if entry.kind == svn.core.svn_node_dir:
261 subwc = svn.wc.adm_open3(wc,
262 self.workingtree.abspath(subrelpath),
265 add_dir_to_inv(subrelpath, subwc, id)
267 svn.wc.adm_close(subwc)
269 (subid, subrevid) = find_ids(entry)
270 if subid is not None:
271 add_file_to_inv(subrelpath, subid, subrevid, wc)
273 wc = workingtree._get_wc()
275 add_dir_to_inv("", wc, None)
279 def _abspath(self, relpath):
280 return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
282 def get_file_lines(self, file_id):
283 base_copy = self._abspath(self.id2path(file_id))
284 return osutils.split_lines(open(base_copy).read())