Merge dev tree.
[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 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
28
29 import os
30 import md5
31 from cStringIO import StringIO
32 import urllib
33
34 import svn.core, svn.wc, svn.delta
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         (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()
59         pool = Pool()
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)
64         self.file_data = {}
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)
72         pool.destroy()
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, pool):
80         self.tree = tree
81         self.repository = tree._repository
82         self.last_revnum = {}
83         self.dir_revnum = {}
84         self.dir_ignores = {}
85         self.pool = pool
86
87     def set_target_revision(self, revnum):
88         self.revnum = revnum
89
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
95         return file_id
96
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
102         return file_id
103
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)
107
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)
115                 return
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)
119                 return
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):
125             pass
126         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
127             pass
128         elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
129             pass
130         else:
131             mutter('unsupported dir property %r' % name)
132
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):
145             pass
146         elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
147             pass
148         else:
149             mutter('unsupported file property %r' % name)
150
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
155         return path
156
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]
160
161     def close_file(self, path, checksum):
162         file_id, revision_id = self.tree.id_map[path]
163         if self.is_symlink:
164             ie = self.tree._inventory.add_path(path, 'symlink', file_id)
165         else:
166             ie = self.tree._inventory.add_path(path, 'file', file_id)
167         ie.revision = revision_id
168
169         if self.file_stream:
170             self.file_stream.seek(0)
171             file_data = self.file_stream.read()
172         else:
173             file_data = ""
174
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))
178
179         if self.is_symlink:
180             ie.symlink_target = file_data[len("link "):]
181             ie.text_sha1 = None
182             ie.text_size = None
183             ie.text_id = None
184             ie.executable = False
185         else:
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
190
191         self.file_stream = None
192
193     def close_edit(self):
194         pass
195
196     def abort_edit(self):
197         pass
198
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)
202
203
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
213
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 "):]
219                 ie.text_sha1 = None
220                 ie.text_size = None
221                 ie.text_id = None
222                 ie.executable = False
223             else:
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)
229             ie.revision = revid
230             return ie
231
232         def find_ids(entry):
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]]
238             return (None, None)
239
240         def add_dir_to_inv(relpath, wc, parent_id):
241             entries = svn.wc.entries_read(wc, False)
242             entry = entries[""]
243             (id, revid) = find_ids(entry)
244             if id == None:
245                 return
246
247             # First handle directory itself
248             ie = self._inventory.add_path(relpath, 'directory', id)
249             ie.revision = revid
250
251             for name in entries:
252                 if name == "":
253                     continue
254
255                 subrelpath = os.path.join(relpath, name)
256
257                 entry = entries[name]
258                 assert entry
259                 
260                 if entry.kind == svn.core.svn_node_dir:
261                     subwc = svn.wc.adm_open3(wc, 
262                             self.workingtree.abspath(subrelpath), 
263                                              False, 0, None)
264                     try:
265                         add_dir_to_inv(subrelpath, subwc, id)
266                     finally:
267                         svn.wc.adm_close(subwc)
268                 else:
269                     (subid, subrevid) = find_ids(entry)
270                     if subid is not None:
271                         add_file_to_inv(subrelpath, subid, subrevid, wc)
272
273         wc = workingtree._get_wc() 
274         try:
275             add_dir_to_inv("", wc, None)
276         finally:
277             svn.wc.adm_close(wc)
278
279     def _abspath(self, relpath):
280         return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
281
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())
285