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
32 class SvnRevisionTree(RevisionTree):
33 """A tree that existed in a historical Subversion revision."""
34 def __init__(self, repository, revision_id):
35 self._repository = repository
36 self._revision_id = revision_id
37 (self.branch_path, self.revnum, mapping) = repository.lookup_revision_id(revision_id)
38 self._inventory = Inventory()
39 self.id_map = repository.get_fileid_map(self.revnum, self.branch_path,
41 editor = TreeBuildEditor(self)
43 root_repos = repository.transport.get_svn_repos_root()
44 reporter = repository.transport.do_switch(
46 urlutils.join(root_repos, self.branch_path), editor)
47 reporter.set_path("", 0, True, None)
48 reporter.finish_report()
50 def get_file_lines(self, file_id):
51 return osutils.split_lines(self.file_data[file_id])
54 class TreeBuildEditor:
55 """Builds a tree given Subversion tree transform calls."""
56 def __init__(self, tree):
58 self.repository = tree._repository
63 def set_target_revision(self, revnum):
66 def open_root(self, revnum):
67 file_id, revision_id = self.tree.id_map[""]
68 ie = self.tree._inventory.add_path("", 'directory', file_id)
69 ie.revision = revision_id
70 self.tree._inventory.revision_id = revision_id
79 class DirectoryTreeEditor:
80 def __init__(self, tree, file_id):
82 self.file_id = file_id
83 self.dir_ignores = None
85 def add_directory(self, path, copyfrom_path=None, copyfrom_revnum=-1):
86 path = path.decode("utf-8")
87 file_id, revision_id = self.tree.id_map[path]
88 ie = self.tree._inventory.add_path(path, 'directory', file_id)
89 ie.revision = revision_id
90 return DirectoryTreeEditor(self.editor, file_id)
92 def change_prop(self, name, value):
93 from mapping import (SVN_PROP_BZR_ANCESTRY,
94 SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO,
95 SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID,
96 SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_MERGE)
98 if name == constants.PROP_ENTRY_COMMITTED_REV:
99 self.dir_revnum = int(value)
100 elif name == constants.PROP_IGNORE:
101 self.dir_ignores = value
102 elif name.startswith(SVN_PROP_BZR_ANCESTRY):
103 if self.file_id != self.tree._inventory.root.file_id:
104 mutter('%r set on non-root dir!' % name)
106 elif name in (SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_BRANCHING_SCHEME):
107 if self.file_id != self.tree._inventory.root.file_id:
108 mutter('%r set on non-root dir!' % name)
110 elif name in (SVN_PROP_ENTRY_COMMITTED_DATE,
111 SVN_PROP_ENTRY_LAST_AUTHOR,
112 SVN_PROP_ENTRY_LOCK_TOKEN,
114 SVN_PROP_EXECUTABLE):
116 elif name.startswith(constants.PROP_WC_PREFIX):
118 elif (name == SVN_PROP_BZR_REVISION_INFO or
119 name.startswith(SVN_PROP_BZR_REVISION_ID)):
121 elif name == SVN_PROP_BZR_MERGE:
123 elif (name.startswith(constants.PROP_PREFIX) or
124 name.startswith(SVN_PROP_BZR_PREFIX)):
125 mutter('unsupported dir property %r' % name)
127 def add_file(self, path, copyfrom_path=None, copyfrom_revnum=-1):
128 path = path.decode("utf-8")
129 return FileTreeEditor(self.tree, path)
132 if (self.dir_ignores is not None and
133 self.file_id in self.tree._inventory):
134 self.tree._inventory[self.file_id].ignores = self.dir_ignores
137 class FileTreeEditor:
138 def __init__(self, tree, path):
141 self.is_executable = False
142 self.is_symlink = False
143 self.last_file_rev = None
145 def change_prop(self, name, value):
146 from mapping import SVN_PROP_BZR_PREFIX
148 if name == constants.PROP_EXECUTABLE:
149 self.is_executable = (value != None)
150 elif name == constants.PROP_SPECIAL:
151 self.is_symlink = (value != None)
152 elif name == constants.PROP_ENTRY_COMMITTED_REV:
153 self.last_file_rev = int(value)
154 elif name in (constants.PROP_ENTRY_COMMITTED_DATE,
155 constants.PROP_ENTRY_LAST_AUTHOR,
156 constants.PROP_ENTRY_LOCK_TOKEN,
157 constants.PROP_ENTRY_UUID,
158 constants.PROP_MIME_TYPE):
160 elif name.startswith(constants.PROP_WC_PREFIX):
162 elif (name.startswith(constants.PROP_PREFIX) or
163 name.startswith(SVN_PROP_BZR_PREFIX)):
164 mutter('unsupported file property %r' % name)
166 def close(self, checksum=None):
167 file_id, revision_id = self.tree.id_map[self.path]
169 ie = self.tree._inventory.add_path(path, 'symlink', file_id)
171 ie = self.tree._inventory.add_path(path, 'file', file_id)
172 ie.revision = revision_id
175 self.file_stream.seek(0)
176 file_data = self.file_stream.read()
180 actual_checksum = md5.new(file_data).hexdigest()
181 assert(checksum is None or checksum == actual_checksum,
182 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
185 ie.symlink_target = file_data[len("link "):]
189 ie.executable = False
191 ie.text_sha1 = osutils.sha_string(file_data)
192 ie.text_size = len(file_data)
193 self.tree.file_data[file_id] = file_data
194 ie.executable = self.is_executable
196 self.file_stream = None
198 def apply_textdelta(self, file_id, base_checksum):
199 self.file_stream = StringIO()
200 return apply_txdelta_handler(StringIO(""), self.file_stream)
203 class SvnBasisTree(RevisionTree):
204 """Optimized version of SvnRevisionTree."""
205 def __init__(self, workingtree):
206 self.workingtree = workingtree
207 self._revision_id = workingtree.branch.generate_revision_id(
208 workingtree.base_revnum)
209 self.id_map = workingtree.branch.repository.get_fileid_map(
210 workingtree.base_revnum,
211 workingtree.branch.get_branch_path(workingtree.base_revnum),
212 workingtree.branch.mapping)
213 self._inventory = Inventory(root_id=None)
214 self._repository = workingtree.branch.repository
216 def add_file_to_inv(relpath, id, revid, adm):
217 (delta_props, props) = adm.get_prop_diffs(self.workingtree.abspath(relpath))
218 if props.has_key(constants.PROP_SPECIAL):
219 ie = self._inventory.add_path(relpath, 'symlink', id)
220 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
224 ie.executable = False
226 ie = self._inventory.add_path(relpath, 'file', id)
227 data = osutils.fingerprint_file(open(self._abspath(relpath)))
228 ie.text_sha1 = data['sha1']
229 ie.text_size = data['size']
230 ie.executable = props.has_key(constants.PROP_EXECUTABLE)
235 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
236 if entry.schedule in (wc.SCHEDULE_NORMAL,
238 wc.SCHEDULE_REPLACE):
239 return self.id_map[workingtree.branch.unprefix(relpath)]
242 def add_dir_to_inv(relpath, adm, parent_id):
243 entries = adm.entries_read(False)
245 (id, revid) = find_ids(entry)
249 # First handle directory itself
250 ie = self._inventory.add_path(relpath, 'directory', id)
253 self._inventory.revision_id = revid
259 subrelpath = os.path.join(relpath, name)
261 entry = entries[name]
264 if entry.kind == core.NODE_DIR:
265 subwc = wc.WorkingCopy(adm,
266 self.workingtree.abspath(subrelpath),
269 add_dir_to_inv(subrelpath, subwc, id)
273 (subid, subrevid) = find_ids(entry)
274 if subid is not None:
275 add_file_to_inv(subrelpath, subid, subrevid, adm)
277 adm = workingtree._get_wc()
279 add_dir_to_inv("", adm, None)
283 def _abspath(self, relpath):
284 return wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
286 def get_file_lines(self, file_id):
287 base_copy = self._abspath(self.id2path(file_id))
288 return osutils.split_lines(open(base_copy).read())