Rewrite svn2bzr as part of bzr-svn. It is now a bzr command called 'import-svn'.
[jelmer/subvertpy.git] / tree.py
1 # Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
2
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.
7
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.
12
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
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                               ROOT_ID)
22 from bzrlib.lockable_files import TransportLock, LockableFiles
23 from bzrlib.lockdir import LockDir
24 import bzrlib.osutils as osutils
25 from bzrlib.progress import DummyProgress
26 from bzrlib.trace import mutter
27 from bzrlib.tree import RevisionTree, EmptyTree
28
29 import os
30 from cStringIO import StringIO
31
32 import svn.core, svn.wc, svn.delta
33 from svn.core import SubversionException, Pool
34
35 _global_pool = Pool()
36
37 def apply_txdelta_handler(src_stream, target_stream):
38     ret = svn.delta.svn_txdelta_apply(
39             src_stream, 
40             target_stream,
41             _global_pool)
42
43     print ret
44     def wrapper(window):
45         ret[1](window)
46
47     return wrapper
48
49 class SvnRevisionTree(RevisionTree):
50      def __init__(self, repository, revision_id, inventory=None):
51         self._repository = repository
52         self._revision_id = revision_id
53         (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
54         self._inventory = Inventory()
55         self.editor = TreeBuildEditor(self)
56         self.file_data = {}
57
58         editor, baton = svn.delta.make_editor(self.editor)
59
60         mutter('do update: %r, %r' % (self.revnum, self.branch_path))
61         reporter, reporter_baton = svn.ra.do_update(repository.ra, self.revnum, self.branch_path, True, editor, baton)
62
63         svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
64
65         svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
66
67      def get_file_lines(self, file_id):
68         return self.file_data[file_id].splitlines(True)
69
70
71 class TreeBuildEditor(svn.delta.Editor):
72     def __init__(self, tree):
73         self.tree = tree
74         self.repository = tree._repository
75         self.last_revnum = {}
76         self.dir_revnum = {}
77         self.dir_ignores = {}
78
79     def set_target_revision(self, revnum):
80         self.revnum = revnum
81
82     def open_root(self, revnum, baton):
83         return ROOT_ID
84
85     def relpath(self, path):
86         bp, rp = self.tree._repository.scheme.unprefix(path)
87         if bp == self.tree.branch_path:
88             return rp
89         return None
90
91     def get_file_id(self, path, revnum):
92         return self.tree._repository.path_to_file_id(revnum, path)
93
94     def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
95         relpath = self.relpath(path)
96         if relpath is None:
97             return ROOT_ID
98         file_id, revision_id = self.get_file_id(path, self.revnum)
99         ie = self.tree._inventory.add_path(relpath, 'directory', file_id)
100         if ie is None:
101             self.tree._inventory.revision_id = revision_id
102             return ROOT_ID
103
104         ie.revision = revision_id
105         return file_id
106
107     def change_dir_prop(self, id, name, value, pool):
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 in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
113                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
114                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
115                       svn.core.SVN_PROP_ENTRY_UUID,
116                       svn.core.SVN_PROP_EXECUTABLE):
117             pass
118         else:
119             mutter('unsupported file property %r' % name)
120
121     def change_file_prop(self, id, name, value, pool):
122         if (name == svn.core.SVN_PROP_EXECUTABLE and 
123             value == svn.core.SVN_PROP_EXECUTABLE_VALUE):
124             self.is_executable = True
125         elif (name == svn.core.SVN_PROP_SPECIAL and 
126             value == svn.core.SVN_PROP_SPECIAL_VALUE):
127             self.is_symlink = True
128         elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
129             self.last_file_rev = int(value)
130         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
131                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
132                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
133                       svn.core.SVN_PROP_ENTRY_UUID,
134                       svn.core.SVN_PROP_MIME_TYPE):
135             pass
136         else:
137             mutter('unsupported file property %r' % name)
138
139     def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
140         self.is_symlink = False
141         self.is_executable = False
142         return path
143
144     def close_dir(self, id):
145         if id in self.tree._inventory and self.dir_ignores.has_key(id):
146             self.tree._inventory[id].ignores = self.dir_ignores[id]
147
148     def close_file(self, path, checksum):
149         relpath = self.relpath(path)
150         if relpath is None:
151             return 
152
153         file_id, revision_id = self.get_file_id(path, self.revnum)
154
155         ie = self.tree._inventory.add_path(relpath, 'file', file_id)
156         ie.revision = revision_id
157
158         if self.file_stream:
159             self.file_stream.seek(0)
160             file_data = self.file_stream.read()
161         else:
162             file_data = ""
163
164         if self.is_symlink:
165             ie.kind = 'symlink'
166             ie.symlink_target = file_data[len("link "):]
167         else:
168             ie.text_sha1 = osutils.sha_string(file_data)
169             ie.text_size = len(file_data)
170             self.tree.file_data[file_id] = file_data
171             ie.executable = self.is_executable
172
173         self.file_stream = None
174
175     def close_edit(self):
176         pass
177
178     def abort_edit(self):
179         pass
180
181     def apply_textdelta(self, file_id, base_checksum):
182         self.file_stream = StringIO()
183         return apply_txdelta_handler(StringIO(""), self.file_stream)
184
185
186 class SvnInventoryFile(InventoryFile):
187     """Inventory entry that can either be a plain file or a 
188     symbolic link. Avoids fetching data until necessary. """
189     def __init__(self, file_id, name, parent_id, repository, path, revnum, 
190                  has_props):
191         self.repository = repository
192         self.path = path
193         self.has_props = has_props
194         self.revnum = revnum
195         InventoryFile.__init__(self, file_id, name, parent_id)
196
197     def _get_sha1(self):
198         text = self.repository._get_file(self.path, self.revnum).read()
199         return osutils.sha_string(text)
200
201     def _get_executable(self):
202         if not self.has_props:
203             return False
204
205         value = self.repository._get_file_prop(self.path, self.revnum, 
206                     svn.core.SVN_PROP_EXECUTABLE)
207         if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
208             return True
209         return False 
210
211     def _is_special(self):
212         if not self.has_props:
213             return False
214
215         value = self.repository._get_file_prop(self.path, self.revnum, 
216                     svn.core.SVN_PROP_SPECIAL)
217         if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
218             return True
219         return False 
220
221     def _get_symlink_target(self):
222         if not self._is_special():
223             return None
224         data = self.repository._get_file(self.path, self.revnum).read()
225         if not data.startswith("link "):
226             raise BzrError("Improperly formatted symlink file")
227         return data[len("link "):]
228
229     def _get_kind(self):
230         if self._is_special():
231             return 'symlink'
232         return 'file'
233
234     # FIXME: we need a set function here because of InventoryEntry.__init__
235     def _phony_set(self, data):
236         pass
237    
238     text_sha1 = property(_get_sha1, _phony_set)
239     executable = property(_get_executable, _phony_set)
240     symlink_target = property(_get_symlink_target, _phony_set)
241     kind = property(_get_kind, _phony_set)
242
243
244 class SlowSvnRevisionTree(RevisionTree):
245     """Original implementation of SvnRevisionTree.
246     
247     More roundtrip intensive than SvnRevisionTree, but more 
248     efficient on bandwidth usage if the full tree isn't used.
249     """
250     def __init__(self, repository, revision_id, inventory=None):
251         self._repository = repository
252         self._revision_id = revision_id
253         if inventory:
254             self._inventory = inventory
255         else:
256             self._inventory = repository.get_inventory(revision_id)
257         (self._branch_path, self._revnum) = repository.parse_revision_id(revision_id)
258
259         self.fetch_inventory()
260
261     def fetch_inventory(self):
262         mutter('getting inventory %r for branch %r' % (self._revnum, self._branch_path))
263
264         def read_directory(inv, id, path, revnum):
265             (props, dirents) = self._cache_get_dir(path, revnum)
266
267             recurse = {}
268
269             for child_name in dirents:
270                 dirent = dirents[child_name]
271
272                 child_path = os.path.join(path, child_name)
273
274                 (child_id, revid) = self.path_to_file_id(dirent.created_rev, 
275                     child_path)
276                 if dirent.kind == svn.core.svn_node_dir:
277                     inventry = InventoryDirectory(child_id, child_name, id)
278                     recurse[child_path] = dirent.created_rev
279                 elif dirent.kind == svn.core.svn_node_file:
280                     inventry = SvnInventoryFile(child_id, child_name, id, self, 
281                         child_path, dirent.created_rev, dirent.has_props)
282
283                 else:
284                     raise BzrError("Unknown entry kind for '%s': %s" % 
285                         (child_path, dirent.kind))
286
287                 inventry.revision = revid
288                 inv.add(inventry)
289
290             for child_path in recurse:
291                 (child_id, _) = self.path_to_file_id(recurse[child_path], 
292                     child_path)
293                 read_directory(inv, child_id, child_path, recurse[child_path])
294     
295         inv = Inventory(revision_id=self._revision_id, root_id=ROOT_ID)
296         inv[ROOT_ID].revision = self._revision_id
297
298         assert path != None
299         read_directory(inv, ROOT_ID, self._branch_path, self._revnum)
300
301         return inv
302
303     def get_file_lines(self, file_id):
304         path = "%s/%s" % (self._branch_path, self.id2path(file_id))
305         stream = self._repository._get_file(path, self._revnum)
306         return stream.readlines()
307
308
309 class SvnBasisTree(SvnRevisionTree):
310     """Optimized version of SvnRevisionTree."""
311     def __init__(self, workingtree, revid):
312         super(SvnBasisTree, self).__init__(workingtree.branch.repository,
313                                            revid)
314         self.workingtree = workingtree
315
316     def get_file_lines(self, file_id):
317         path = self.id2path(file_id)
318         base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
319         return open(base_copy).readlines()
320