Try to reduce memory usage further.
[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.revision import NULL_REVISION
27 from bzrlib.trace import mutter
28 from bzrlib.revisiontree import RevisionTree
29
30 import os
31 import md5
32 from cStringIO import StringIO
33
34 import svn.core, svn.wc, svn.delta, svn.ra
35 from svn.core import SubversionException, Pool
36
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(
41             src_stream, 
42             target_stream,
43             None,
44             pool)
45
46     def wrapper(window):
47         svn.delta.invoke_txdelta_window_handler(
48             ret[1], window, ret[2])
49
50     return wrapper
51
52 class SvnRevisionTree(RevisionTree):
53      def __init__(self, repository, revision_id, inventory=None):
54         self._repository = repository
55         self._revision_id = revision_id
56         pool = Pool()
57         if revision_id == NULL_REVISION:
58             self._inventory = Inventory(ROOT_ID)
59             self._inventory.revision_id = NULL_REVISION
60         else:
61             (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
62             self._inventory = Inventory(ROOT_ID)
63             self._inventory.revision_id = revision_id
64             self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
65             self.editor = TreeBuildEditor(self, pool)
66             self.file_data = {}
67
68             editor, baton = svn.delta.make_editor(self.editor, pool)
69
70             root_repos = repository.transport.get_repos_root()
71             reporter, reporter_baton = repository.transport.do_switch(
72                     self.revnum, "", True, 
73                     os.path.join(root_repos, self.branch_path), editor, baton, pool)
74
75             svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None, pool)
76
77             svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton, pool)
78
79             pool.destroy()
80
81      def get_file_lines(self, file_id):
82         return osutils.split_lines(self.file_data[file_id])
83
84
85 class TreeBuildEditor(svn.delta.Editor):
86     def __init__(self, tree, pool):
87         self.tree = tree
88         self.repository = tree._repository
89         self.last_revnum = {}
90         self.dir_revnum = {}
91         self.dir_ignores = {}
92         self.pool = pool
93
94     def set_target_revision(self, revnum):
95         self.revnum = revnum
96
97     def open_root(self, revnum, baton):
98         return ROOT_ID
99
100     def relpath(self, path):
101         bp, rp = self.tree._repository.scheme.unprefix(path)
102         if bp == self.tree.branch_path:
103             return rp
104         return None
105
106     def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
107         file_id, revision_id = self.tree.id_map[path]
108         ie = self.tree._inventory.add_path(path, 'directory', file_id)
109
110         ie.revision = revision_id
111         return file_id
112
113     def change_dir_prop(self, id, name, value, pool):
114         from repository import (SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE, 
115                         SVN_PROP_BZR_REVPROP_PREFIX)
116
117         if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
118             self.dir_revnum[id] = int(value)
119         elif name == svn.core.SVN_PROP_IGNORE:
120             self.dir_ignores[id] = value
121         elif name == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
122             if id != ROOT_ID:
123                 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
124                 return
125         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
126                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
127                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
128                       svn.core.SVN_PROP_ENTRY_UUID,
129                       svn.core.SVN_PROP_EXECUTABLE):
130             pass
131         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
132             pass
133         elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
134             pass
135         else:
136             mutter('unsupported dir property %r' % name)
137
138     def change_file_prop(self, id, name, value, pool):
139         if name == svn.core.SVN_PROP_EXECUTABLE:
140             self.is_executable = (value != None)
141         elif name == svn.core.SVN_PROP_SPECIAL:
142             self.is_symlink = (value != None)
143         elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
144             self.last_file_rev = int(value)
145         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
146                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
147                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
148                       svn.core.SVN_PROP_ENTRY_UUID,
149                       svn.core.SVN_PROP_MIME_TYPE):
150             pass
151         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
152             pass
153         else:
154             mutter('unsupported file property %r' % name)
155
156     def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
157         self.is_symlink = False
158         self.is_executable = False
159         return path
160
161     def close_dir(self, id):
162         if id in self.tree._inventory and self.dir_ignores.has_key(id):
163             self.tree._inventory[id].ignores = self.dir_ignores[id]
164
165     def close_file(self, path, checksum):
166         file_id, revision_id = self.tree.id_map[path]
167
168         if self.is_symlink:
169             ie = self.tree._inventory.add_path(path, 'symlink', file_id)
170         else:
171             ie = self.tree._inventory.add_path(path, 'file', file_id)
172         ie.revision = revision_id
173
174         if self.file_stream:
175             self.file_stream.seek(0)
176             file_data = self.file_stream.read()
177         else:
178             file_data = ""
179
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))
183
184         if self.is_symlink:
185             ie.symlink_target = file_data[len("link "):]
186             ie.text_sha1 = None
187             ie.text_size = None
188             ie.text_id = None
189         else:
190             ie.text_sha1 = osutils.sha_string(file_data)
191             ie.text_size = len(file_data)
192             self.tree.file_data[file_id] = file_data
193             ie.executable = self.is_executable
194
195         self.file_stream = None
196
197     def close_edit(self):
198         pass
199
200     def abort_edit(self):
201         pass
202
203     def apply_textdelta(self, file_id, base_checksum):
204         self.file_stream = StringIO()
205         return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
206
207
208 class SvnBasisTree(SvnRevisionTree):
209     """Optimized version of SvnRevisionTree."""
210     def __init__(self, workingtree, revid):
211         super(SvnBasisTree, self).__init__(workingtree.branch.repository,
212                                            revid)
213         self.workingtree = workingtree
214
215     def get_file_lines(self, file_id):
216         path = self.id2path(file_id)
217         base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
218         return osutils.split_lines(open(base_copy).read())
219