Reduce amount of mutters.
[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 import md5
31 from cStringIO import StringIO
32
33 import svn.core, svn.wc, svn.delta
34 from svn.core import SubversionException, Pool
35
36 _global_pool = Pool()
37
38 def apply_txdelta_handler(src_stream, target_stream):
39     assert hasattr(src_stream, 'read')
40     assert hasattr(target_stream, 'write')
41     ret = svn.delta.svn_txdelta_apply(
42             src_stream, 
43             target_stream,
44             None,
45             _global_pool)
46
47     def wrapper(window):
48         svn.delta.invoke_txdelta_window_handler(
49             ret[1], window, ret[2])
50
51     return wrapper
52
53 class SvnRevisionTree(RevisionTree):
54      def __init__(self, repository, revision_id, inventory=None):
55         self._repository = repository
56         self._revision_id = revision_id
57         (self.branch_path, self.revnum) = repository.parse_revision_id(revision_id)
58         self._inventory = Inventory()
59         self._inventory.revision_id = revision_id
60         self.id_map = repository.get_fileid_map(self.revnum, self.branch_path)
61         self.editor = TreeBuildEditor(self)
62         self.file_data = {}
63
64         editor, baton = svn.delta.make_editor(self.editor)
65
66         root_repos = svn.ra.get_repos_root(repository.ra)
67         mutter('svn checkout -r %r %r' % (self.revnum, self.branch_path))
68         reporter, reporter_baton = svn.ra.do_switch(repository.ra, self.revnum, "", True, os.path.join(root_repos, self.branch_path), editor, baton)
69
70         svn.ra.reporter2_invoke_set_path(reporter, reporter_baton, "", 0, True, None)
71
72         svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
73
74      def get_file_lines(self, file_id):
75         return osutils.split_lines(self.file_data[file_id])
76
77
78 class TreeBuildEditor(svn.delta.Editor):
79     def __init__(self, tree):
80         self.tree = tree
81         self.repository = tree._repository
82         self.last_revnum = {}
83         self.dir_revnum = {}
84         self.dir_ignores = {}
85
86     def set_target_revision(self, revnum):
87         self.revnum = revnum
88
89     def open_root(self, revnum, baton):
90         return ROOT_ID
91
92     def relpath(self, path):
93         bp, rp = self.tree._repository.scheme.unprefix(path)
94         if bp == self.tree.branch_path:
95             return rp
96         return None
97
98     def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
99         file_id, revision_id = self.tree.id_map[path]
100         ie = self.tree._inventory.add_path(path, 'directory', file_id)
101
102         ie.revision = revision_id
103         return file_id
104
105     def change_dir_prop(self, id, name, value, pool):
106         from repository import (SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE, 
107                         SVN_PROP_BZR_REVPROP_PREFIX)
108
109         if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
110             self.dir_revnum[id] = int(value)
111         elif name == svn.core.SVN_PROP_IGNORE:
112             self.dir_ignores[id] = value
113         elif name == SVN_PROP_BZR_MERGE or name == SVN_PROP_SVK_MERGE:
114             if id != ROOT_ID:
115                 mutter('%r set on non-root dir!' % SVN_PROP_BZR_MERGE)
116                 return
117         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
118                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
119                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
120                       svn.core.SVN_PROP_ENTRY_UUID,
121                       svn.core.SVN_PROP_EXECUTABLE):
122             pass
123         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
124             pass
125         else:
126             mutter('unsupported dir property %r' % name)
127
128     def change_file_prop(self, id, name, value, pool):
129         if name == svn.core.SVN_PROP_EXECUTABLE:
130             self.is_executable = (value != None)
131         elif name == svn.core.SVN_PROP_SPECIAL:
132             self.is_symlink = (value != None)
133         elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
134             self.last_file_rev = int(value)
135         elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
136                       svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
137                       svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
138                       svn.core.SVN_PROP_ENTRY_UUID,
139                       svn.core.SVN_PROP_MIME_TYPE):
140             pass
141         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
142             pass
143         else:
144             mutter('unsupported file property %r' % name)
145
146     def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
147         self.is_symlink = False
148         self.is_executable = False
149         return path
150
151     def close_dir(self, id):
152         if id in self.tree._inventory and self.dir_ignores.has_key(id):
153             self.tree._inventory[id].ignores = self.dir_ignores[id]
154
155     def close_file(self, path, checksum):
156         file_id, revision_id = self.tree.id_map[path]
157
158         if self.is_symlink:
159             ie = self.tree._inventory.add_path(path, 'symlink', file_id)
160         else:
161             ie = self.tree._inventory.add_path(path, 'file', file_id)
162         ie.revision = revision_id
163
164         if self.file_stream:
165             self.file_stream.seek(0)
166             file_data = self.file_stream.read()
167         else:
168             file_data = ""
169
170         actual_checksum = md5.new(file_data).hexdigest()
171         assert(checksum is None or checksum == actual_checksum,
172                 "checksum mismatch: %r != %r" % (checksum, actual_checksum))
173
174         if self.is_symlink:
175             ie.symlink_target = file_data[len("link "):]
176             ie.text_sha1 = None
177             ie.text_size = None
178             ie.text_id = None
179         else:
180             ie.text_sha1 = osutils.sha_string(file_data)
181             ie.text_size = len(file_data)
182             self.tree.file_data[file_id] = file_data
183             ie.executable = self.is_executable
184
185         self.file_stream = None
186
187     def close_edit(self):
188         pass
189
190     def abort_edit(self):
191         pass
192
193     def apply_textdelta(self, file_id, base_checksum):
194         self.file_stream = StringIO()
195         return apply_txdelta_handler(StringIO(""), self.file_stream)
196
197
198 class SvnInventoryFile(InventoryFile):
199     """Inventory entry that can either be a plain file or a 
200     symbolic link. Avoids fetching data until necessary. """
201     def __init__(self, file_id, name, parent_id, repository, path, revnum, 
202                  has_props):
203         self.repository = repository
204         self.path = path
205         self.has_props = has_props
206         self.revnum = revnum
207         InventoryFile.__init__(self, file_id, name, parent_id)
208
209     def _get_sha1(self):
210         text = self.repository._get_file(self.path, self.revnum).read()
211         return osutils.sha_string(text)
212
213     def _get_executable(self):
214         if not self.has_props:
215             return False
216
217         value = self.repository._get_file_prop(self.path, self.revnum, 
218                     svn.core.SVN_PROP_EXECUTABLE)
219         if value and value == svn.core.SVN_PROP_EXECUTABLE_VALUE:
220             return True
221         return False 
222
223     def _is_special(self):
224         if not self.has_props:
225             return False
226
227         value = self.repository._get_file_prop(self.path, self.revnum, 
228                     svn.core.SVN_PROP_SPECIAL)
229         if value and value == svn.core.SVN_PROP_SPECIAL_VALUE:
230             return True
231         return False 
232
233     def _get_symlink_target(self):
234         if not self._is_special():
235             return None
236         data = self.repository._get_file(self.path, self.revnum).read()
237         if not data.startswith("link "):
238             raise BzrError("Improperly formatted symlink file")
239         return data[len("link "):]
240
241     def _get_kind(self):
242         if self._is_special():
243             return 'symlink'
244         return 'file'
245
246     # FIXME: we need a set function here because of InventoryEntry.__init__
247     def _phony_set(self, data):
248         pass
249    
250     text_sha1 = property(_get_sha1, _phony_set)
251     executable = property(_get_executable, _phony_set)
252     symlink_target = property(_get_symlink_target, _phony_set)
253     kind = property(_get_kind, _phony_set)
254
255
256 class SvnBasisTree(SvnRevisionTree):
257     """Optimized version of SvnRevisionTree."""
258     def __init__(self, workingtree, revid):
259         super(SvnBasisTree, self).__init__(workingtree.branch.repository,
260                                            revid)
261         self.workingtree = workingtree
262
263     def get_file_lines(self, file_id):
264         path = self.id2path(file_id)
265         base_copy = svn.wc.get_pristine_copy_path(self.workingtree.abspath(path))
266         return osutils.split_lines(open(base_copy).read())
267