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
18 from bzrlib.decorators import needs_write_lock
19 from bzrlib.inventory import Inventory, ROOT_ID
20 import bzrlib.osutils as osutils
21 from bzrlib.progress import ProgressBar
22 from bzrlib.revision import Revision
23 from bzrlib.repository import InterRepository
24 from bzrlib.trace import mutter
27 from cStringIO import StringIO
31 from svn.core import SubversionException, Pool
32 import svn.core, svn.ra
34 from repository import (SvnRepository, SVN_PROP_BZR_MERGE, SVN_PROP_SVK_MERGE,
35 SVN_PROP_BZR_REVPROP_PREFIX, SvnRepositoryFormat)
36 from tree import apply_txdelta_handler
39 def md5_strings(strings):
41 map(s.update, strings)
44 class RevisionBuildEditor(svn.delta.Editor):
45 def __init__(self, source, target, branch_path, revnum, prev_inventory, revid, svn_revprops, id_map, parent_branch, parent_id_map):
46 self.branch_path = branch_path
47 self.inventory = copy(prev_inventory)
51 self.parent_branch = parent_branch
52 self.parent_id_map = parent_id_map
55 self.transact = target.get_transaction()
56 self.weave_store = target.weave_store
60 self._parent_ids = None
62 self._svn_revprops = svn_revprops
64 def _get_revision(self, revid):
65 if self._parent_ids is None:
68 parent_ids = self.source.revision_parents(revid, self._parent_ids)
70 # Commit SVN revision properties to a Revision object
71 rev = Revision(revision_id=revid, parent_ids=parent_ids)
73 rev.timestamp = 1.0 * svn.core.secs_from_timestr(
74 self._svn_revprops[2], None) #date
77 rev.committer = self._svn_revprops[0] # author
78 if rev.committer is None:
80 rev.message = self._svn_revprops[1] # message
82 rev.properties = self._revprops
85 def open_root(self, base_revnum, baton):
86 if self.inventory.revision_id is None:
87 self.dir_baserev[ROOT_ID] = []
89 self.dir_baserev[ROOT_ID] = [self.inventory.revision_id]
90 self.inventory.revision_id = self.revid
93 def relpath(self, path):
94 return path.strip("/")
96 def delete_entry(self, path, revnum, parent_baton, pool):
97 del self.inventory[self.inventory.path2id(path)]
99 def close_directory(self, id):
103 self.inventory[id].revision = revid
105 file_weave = self.weave_store.get_weave_or_empty(id, self.transact)
106 if not file_weave.has_version(revid):
107 file_weave.add_lines(revid, self.dir_baserev[id], [])
109 def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
110 file_id, revision_id = self.id_map[path]
112 if copyfrom_path is not None:
113 base_file_id, base_revid = self.source.path_to_file_id(copyfrom_revnum, os.path.join(self.parent_branch, copyfrom_path))
114 if base_file_id == file_id:
115 self.dir_baserev[file_id] = [base_revid]
116 ie = self.inventory[file_id]
117 ie.revision = revision_id
120 self.dir_baserev[file_id] = []
121 ie = self.inventory.add_path(path, 'directory', file_id)
123 ie.revision = revision_id
127 def open_directory(self, path, parent_baton, base_revnum, pool):
128 return self.add_directory(path, parent_baton, path, base_revnum, pool)
130 def change_dir_prop(self, id, name, value, pool):
131 if name == SVN_PROP_BZR_MERGE:
133 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_MERGE)
136 self._parent_ids = value.splitlines()[-1]
137 elif name == SVN_PROP_SVK_MERGE:
138 if self._parent_ids is None:
139 # Only set parents using svk:merge if no
142 elif name.startswith(SVN_PROP_BZR_REVPROP_PREFIX):
143 self._revprops[name[len(SVN_PROP_BZR_REVPROP_PREFIX):]] = value
144 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
145 svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
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_EXECUTABLE):
151 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
154 mutter('unsupported file property %r' % name)
156 def change_file_prop(self, id, name, value, pool):
157 if name == svn.core.SVN_PROP_EXECUTABLE:
158 # Strange, you'd expect executable to match svn.core.SVN_PROP_EXECUTABLE_VALUE, but that's not how SVN behaves.
159 self.is_executable = (value != None)
160 elif (name == svn.core.SVN_PROP_SPECIAL):
161 self.is_symlink = (value != None)
162 elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
163 self.last_file_rev = int(value)
164 elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
165 svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
166 svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
167 svn.core.SVN_PROP_ENTRY_UUID,
168 svn.core.SVN_PROP_MIME_TYPE):
170 elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
173 mutter('unsupported file property %r' % name)
175 def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
176 self.is_symlink = False
177 self.is_executable = None
179 self.file_parents = []
180 self.file_stream = None
183 def open_file(self, path, parent_id, base_revnum, pool):
184 base_file_id, base_revid = self.source.path_to_file_id(base_revnum, os.path.join(self.parent_branch, path))
185 file_id, revid = self.id_map[path]
186 self.is_executable = None
187 self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
188 file_weave = self.weave_store.get_weave_or_empty(base_file_id, self.transact)
189 self.file_data = file_weave.get_text(base_revid)
190 self.file_stream = None
191 if file_id == base_file_id:
192 self.file_parents = [base_revid]
195 del self.inventory[base_file_id]
196 self.file_parents = []
199 def close_file(self, path, checksum):
200 if self.file_stream is not None:
201 self.file_stream.seek(0)
202 lines = osutils.split_lines(self.file_stream.read())
204 # Data didn't change or file is new
205 lines = osutils.split_lines(self.file_data)
207 actual_checksum = md5_strings(lines)
208 assert checksum is None or checksum == actual_checksum
210 file_id, revision_id = self.id_map[path]
211 file_weave = self.weave_store.get_weave_or_empty(file_id, self.transact)
212 if not file_weave.has_version(revision_id):
213 file_weave.add_lines(revision_id, self.file_parents, lines)
215 if file_id in self.inventory:
216 ie = self.inventory[file_id]
217 elif self.is_symlink:
218 ie = self.inventory.add_path(path, 'symlink', file_id)
220 ie = self.inventory.add_path(path, 'file', file_id)
221 ie.revision = revision_id
224 ie.symlink_target = lines[0][len("link "):]
229 ie.text_sha1 = osutils.sha_strings(lines)
230 ie.text_size = sum(map(len, lines))
231 if self.is_executable is not None:
232 ie.executable = self.is_executable
234 self.file_stream = None
236 def close_edit(self):
237 rev = self._get_revision(self.revid)
238 self.inventory.revision_id = self.revid
239 rev.inventory_sha1 = osutils.sha_string(
240 bzrlib.xml5.serializer_v5.write_inventory_to_string(
242 self.target.add_revision(self.revid, rev, self.inventory)
244 def abort_edit(self):
247 def apply_textdelta(self, file_id, base_checksum):
248 actual_checksum = md5.new(self.file_data).hexdigest(),
249 assert (base_checksum is None or base_checksum == actual_checksum,
250 "base checksum mismatch: %r != %r" % (base_checksum, actual_checksum))
251 self.file_stream = StringIO()
252 return apply_txdelta_handler(StringIO(self.file_data), self.file_stream)
255 class InterSvnRepository(InterRepository):
256 """Svn to any repository actions."""
258 _matching_repo_format = SvnRepositoryFormat
259 """The format to test with."""
262 def copy_content(self, revision_id=None, basis=None, pb=ProgressBar()):
263 """See InterRepository.copy_content."""
264 # Dictionary with paths as keys, revnums as values
266 # Loop over all the revnums until revision_id
267 # (or youngest_revnum) and call self.target.add_revision()
268 # or self.target.add_inventory() each time
269 if revision_id is None:
271 until_revnum = self.source._latest_revnum
273 (path, until_revnum) = self.source.parse_revision_id(revision_id)
275 repos_root = self.source.transport.get_repos_root()
280 for (branch, changes, revnum) in \
281 self.source._log.follow_history(path, until_revnum):
282 revid = self.source.generate_revision_id(revnum, branch)
284 if prev_revid is not None:
285 parents[prev_revid] = revid
289 if not self.target.has_revision(revid):
290 needed.append((branch, revnum, revid, changes))
292 parents[prev_revid] = None
297 transport = self.source.transport
298 for (branch, revnum, revid, changes) in needed:
300 pb.update('copying revision', num+1, len(needed)+1)
303 parent_revid = parents[revid]
305 if parent_revid is not None:
306 (parent_branch, parent_revnum) = self.source.parse_revision_id(parent_revid)
311 if parent_revid is None:
312 parent_id_map = {"": (ROOT_ID, None)}
313 id_map = self.source.get_fileid_map(revnum, branch)
314 parent_inv = Inventory(ROOT_ID)
315 elif prev_revid != parent_revid:
316 parent_id_map = self.source.get_fileid_map(parent_revnum, parent_branch)
317 id_map = self.source.get_fileid_map(revnum, branch)
318 parent_inv = self.target.get_inventory(parent_revid)
320 parent_id_map = copy(id_map)
321 self.source.transform_fileid_map(self.source.uuid,
324 parent_inv = prev_inv
327 editor = RevisionBuildEditor(self.source, self.target, branch,
328 revnum, parent_inv, revid,
329 self.source._log.get_revision_info(revnum),
330 id_map, parent_branch, parent_id_map)
332 edit, edit_baton = svn.delta.make_editor(editor)
334 if parent_branch is None:
335 transport.reparent(repos_root)
337 transport.reparent("%s/%s" % (repos_root, parent_branch))
339 if parent_branch != branch:
340 mutter('svn switch %r:%r -> %r:%r' %
341 (parent_branch, parent_revnum, branch, revnum))
342 reporter, reporter_baton = transport.do_switch(
344 "%s/%s" % (repos_root, branch),
345 edit, edit_baton, pool)
347 mutter('svn update -r %r:%r %r' %
348 (parent_revnum, revnum, branch))
349 reporter, reporter_baton = transport.do_update(
351 edit, edit_baton, pool)
353 # Report status of existing paths
354 svn.ra.reporter2_invoke_set_path(reporter, reporter_baton,
355 "", parent_revnum, False, None)
358 svn.ra.reporter2_invoke_finish_report(reporter, reporter_baton)
361 prev_inv = editor.inventory
367 self.source.transport.reparent(repos_root)
370 def fetch(self, revision_id=None, pb=ProgressBar()):
371 """Fetch revisions. """
372 self.copy_content(revision_id=revision_id, pb=pb)
375 def is_compatible(source, target):
376 """Be compatible with SvnRepository."""
377 # FIXME: Also check target uses VersionedFile
378 mutter('test %r' % source)
379 return isinstance(source, SvnRepository)