1 # Copyright (C) 2006-2008 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 3 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
16 """Committing and pushing to Subversion repositories."""
18 from bzrlib import debug, osutils, urlutils
19 from bzrlib.branch import Branch
20 from bzrlib.errors import (BzrError, InvalidRevisionId, DivergedBranches,
21 UnrelatedBranches, AppendRevisionsOnlyViolation)
22 from bzrlib.inventory import Inventory
23 from bzrlib.repository import RootCommitBuilder, InterRepository
24 from bzrlib.revision import NULL_REVISION
25 from bzrlib.trace import mutter, warning
27 from cStringIO import StringIO
29 from bzrlib.plugins.svn import core, properties
30 from bzrlib.plugins.svn.core import SubversionException, time_to_cstring
31 from bzrlib.plugins.svn.errors import ChangesRootLHSHistory, MissingPrefix, RevpropChangeFailed, ERR_FS_TXN_OUT_OF_DATE, ERR_REPOS_DISABLED_FEATURE
32 from bzrlib.plugins.svn.svk import (generate_svk_feature, serialize_svk_features,
33 parse_svk_features, SVN_PROP_SVK_MERGE)
34 from bzrlib.plugins.svn.logwalker import lazy_dict
35 from bzrlib.plugins.svn.mapping import parse_revision_id
36 from bzrlib.plugins.svn.ra import txdelta_send_stream
37 from bzrlib.plugins.svn.repository import SvnRepositoryFormat, SvnRepository
42 def _revision_id_to_svk_feature(revid):
43 """Create a SVK feature identifier from a revision id.
45 :param revid: Revision id to convert.
46 :return: Matching SVK feature identifier.
48 assert isinstance(revid, str)
49 (uuid, branch, revnum, _) = parse_revision_id(revid)
50 # TODO: What about renamed revisions? Should use
51 # repository.lookup_revision_id here.
52 return generate_svk_feature(uuid, branch, revnum)
55 def _check_dirs_exist(transport, bp_parts, base_rev):
56 """Make sure that the specified directories exist.
58 :param transport: SvnRaTransport to use.
59 :param bp_parts: List of directory names in the format returned by
61 :param base_rev: Base revision to check.
62 :return: List of the directories that exists in base_rev.
64 for i in range(len(bp_parts), 0, -1):
65 current = bp_parts[:i]
66 path = "/".join(current).strip("/")
67 if transport.check_path(path, base_rev) == core.NODE_DIR:
72 def set_svn_revprops(transport, revnum, revprops):
73 """Attempt to change the revision properties on the
76 :param transport: SvnRaTransport connected to target repository
77 :param revnum: Revision number of revision to change metadata of.
78 :param revprops: Dictionary with revision properties to set.
80 for (name, value) in revprops.items():
82 transport.change_rev_prop(revnum, name, value)
83 except SubversionException, (_, ERR_REPOS_DISABLED_FEATURE):
84 raise RevpropChangeFailed(name)
87 class SvnCommitBuilder(RootCommitBuilder):
88 """Commit Builder implementation wrapped around svn_delta_editor. """
90 def __init__(self, repository, branch, parents, config, timestamp,
91 timezone, committer, revprops, revision_id, old_inv=None):
92 """Instantiate a new SvnCommitBuilder.
94 :param repository: SvnRepository to commit to.
95 :param branch: SvnBranch to commit to.
96 :param parents: List of parent revision ids.
97 :param config: Branch configuration to use.
98 :param timestamp: Optional timestamp recorded for commit.
99 :param timezone: Optional timezone for timestamp.
100 :param committer: Optional committer to set for commit.
101 :param revprops: Revision properties to set.
102 :param revision_id: Revision id for the new revision.
103 :param old_inv: Optional revision on top of which
104 the commit is happening
106 super(SvnCommitBuilder, self).__init__(repository, parents,
107 config, timestamp, timezone, committer, revprops, revision_id)
110 # Gather information about revision on top of which the commit is
113 self.base_revid = None
115 self.base_revid = parents[0]
117 self.base_revno = self.branch.revision_id_to_revno(self.base_revid)
118 if self.base_revid is None:
119 self.base_revnum = -1
120 self.base_path = None
121 self.base_mapping = repository.get_mapping()
123 (self.base_path, self.base_revnum, self.base_mapping) = \
124 repository.lookup_revision_id(self.base_revid)
127 if self.base_revid is None:
128 self.old_inv = Inventory(root_id=None)
130 self.old_inv = self.repository.get_inventory(self.base_revid)
132 self.old_inv = old_inv
133 # Not all repositories appear to set Inventory.revision_id,
134 # so allow None as well.
135 assert self.old_inv.revision_id in (None, self.base_revid)
137 # Determine revisions merged in this one
138 merges = filter(lambda x: x != self.base_revid, parents)
140 self.modified_files = {}
141 self.modified_dirs = set()
142 if self.base_revid is None:
143 base_branch_props = {}
145 base_branch_props = lazy_dict({}, self.repository.branchprop_list.get_properties, self.base_path, self.base_revnum)
146 (self._svn_revprops, self._svnprops) = self.base_mapping.export_revision(self.branch.get_branch_path(), timestamp, timezone, committer, revprops, revision_id, self.base_revno+1, merges, base_branch_props)
149 old_svk_features = parse_svk_features(base_branch_props.get(SVN_PROP_SVK_MERGE, ""))
150 svk_features = set(old_svk_features)
155 svk_features.add(_revision_id_to_svk_feature(merge))
156 except InvalidRevisionId:
159 if old_svk_features != svk_features:
160 self._svnprops[SVN_PROP_SVK_MERGE] = serialize_svk_features(svk_features)
162 def mutter(self, text, *args):
163 if 'commit' in debug.debug_flags:
166 def _generate_revision_if_needed(self):
167 """See CommitBuilder._generate_revision_if_needed()."""
169 def finish_inventory(self):
170 """See CommitBuilder.finish_inventory()."""
172 def modified_file_text(self, file_id, file_parents,
173 get_content_byte_lines, text_sha1=None,
175 """See CommitBuilder.modified_file_text()."""
176 new_lines = get_content_byte_lines()
177 self.modified_files[file_id] = "".join(new_lines)
178 return osutils.sha_strings(new_lines), sum(map(len, new_lines))
180 def modified_link(self, file_id, file_parents, link_target):
181 """See CommitBuilder.modified_link()."""
182 self.modified_files[file_id] = "link %s" % link_target
184 def modified_directory(self, file_id, file_parents):
185 """See CommitBuilder.modified_directory()."""
186 self.modified_dirs.add(file_id)
188 def _file_process(self, file_id, contents, file_editor):
189 """Pass the changes to a file to the Subversion commit editor.
191 :param file_id: Id of the file to modify.
192 :param contents: Contents of the file.
193 :param file_editor: Subversion FileEditor object.
195 assert file_editor is not None
196 (txdelta, txbaton) = file_editor.apply_textdelta(None)
197 digest = txdelta_send_stream(StringIO(contents), txdelta, txbaton)
198 if 'validate' in debug.debug_flags:
199 from fetch import md5_strings
200 assert digest == md5_strings(contents)
202 def _dir_process(self, path, file_id, dir_editor):
203 """Pass the changes to a directory to the commit editor.
205 :param path: Path (from repository root) to the directory.
206 :param file_id: File id of the directory
207 :param dir_editor: Subversion DirEditor object.
209 assert dir_editor is not None
210 # Loop over entries of file_id in self.old_inv
211 # remove if they no longer exist with the same name
213 if file_id in self.old_inv:
214 for child_name in self.old_inv[file_id].children:
215 child_ie = self.old_inv.get_child(file_id, child_name)
218 # ... path no longer exists
219 not child_ie.file_id in self.new_inventory or
221 child_ie.parent_id != self.new_inventory[child_ie.file_id].parent_id or
223 self.new_inventory[child_ie.file_id].name != child_name):
224 self.mutter('removing %r(%r)', (child_name, child_ie.file_id))
225 dir_editor.delete_entry(
226 urlutils.join(self.branch.get_branch_path(), path, child_name),
229 # Loop over file children of file_id in self.new_inventory
230 for child_name in self.new_inventory[file_id].children:
231 child_ie = self.new_inventory.get_child(file_id, child_name)
232 assert child_ie is not None
234 if not (child_ie.kind in ('file', 'symlink')):
237 new_child_path = self.new_inventory.id2path(child_ie.file_id).encode("utf-8")
238 full_new_child_path = urlutils.join(self.branch.get_branch_path(),
240 # add them if they didn't exist in old_inv
241 if not child_ie.file_id in self.old_inv:
242 self.mutter('adding %s %r', child_ie.kind, new_child_path)
243 child_editor = dir_editor.add_file(
244 full_new_child_path, None, -1)
247 # copy if they existed at different location
248 elif (self.old_inv.id2path(child_ie.file_id) != new_child_path or
249 self.old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
250 self.mutter('copy %s %r -> %r', child_ie.kind,
251 self.old_inv.id2path(child_ie.file_id),
253 child_editor = dir_editor.add_file(
255 urlutils.join(self.repository.transport.svn_url, self.base_path, self.old_inv.id2path(child_ie.file_id)),
258 # open if they existed at the same location
259 elif child_ie.revision is None:
260 self.mutter('open %s %r', child_ie.kind, new_child_path)
262 child_editor = dir_editor.open_file(
263 full_new_child_path, self.base_revnum)
266 # Old copy of the file was retained. No need to send changes
267 assert child_ie.file_id not in self.modified_files
270 if child_ie.file_id in self.old_inv:
271 old_executable = self.old_inv[child_ie.file_id].executable
272 old_special = (self.old_inv[child_ie.file_id].kind == 'symlink')
275 old_executable = False
277 if child_editor is not None:
278 if old_executable != child_ie.executable:
279 if child_ie.executable:
280 value = properties.PROP_EXECUTABLE_VALUE
283 child_editor.change_prop(
284 properties.PROP_EXECUTABLE, value)
286 if old_special != (child_ie.kind == 'symlink'):
287 if child_ie.kind == 'symlink':
288 value = properties.PROP_SPECIAL_VALUE
292 child_editor.change_prop(
293 properties.PROP_SPECIAL, value)
296 if child_ie.file_id in self.modified_files:
297 self._file_process(child_ie.file_id,
298 self.modified_files[child_ie.file_id], child_editor)
300 if child_editor is not None:
301 child_editor.close(None)
303 # Loop over subdirectories of file_id in self.new_inventory
304 for child_name in self.new_inventory[file_id].children:
305 child_ie = self.new_inventory.get_child(file_id, child_name)
306 if child_ie.kind != 'directory':
309 new_child_path = self.new_inventory.id2path(child_ie.file_id)
310 # add them if they didn't exist in old_inv
311 if not child_ie.file_id in self.old_inv:
312 self.mutter('adding dir %r', child_ie.name)
313 child_editor = dir_editor.add_directory(
314 urlutils.join(self.branch.get_branch_path(),
315 new_child_path), None, -1)
317 # copy if they existed at different location
318 elif self.old_inv.id2path(child_ie.file_id) != new_child_path:
319 old_child_path = self.old_inv.id2path(child_ie.file_id)
320 self.mutter('copy dir %r -> %r', old_child_path, new_child_path)
321 child_editor = dir_editor.add_directory(
322 urlutils.join(self.branch.get_branch_path(), new_child_path),
323 urlutils.join(self.repository.transport.svn_url, self.base_path, old_child_path), self.base_revnum)
325 # open if they existed at the same location and
326 # the directory was touched
327 elif self.new_inventory[child_ie.file_id].revision is None:
328 self.mutter('open dir %r', new_child_path)
330 child_editor = dir_editor.open_directory(
331 urlutils.join(self.branch.get_branch_path(), new_child_path),
334 assert child_ie.file_id not in self.modified_dirs
337 # Handle this directory
338 if child_ie.file_id in self.modified_dirs:
339 self._dir_process(new_child_path, child_ie.file_id, child_editor)
343 def open_branch_editors(self, root, elements, existing_elements,
344 base_path, base_rev, replace_existing):
345 """Open a specified directory given an editor for the repository root.
347 :param root: Editor for the repository root
348 :param elements: List of directory names to open
349 :param existing_elements: List of directory names that exist
350 :param base_path: Path to base top-level branch on
351 :param base_rev: Revision of path to base top-level branch on
352 :param replace_existing: Whether the current branch should be replaced
356 self.mutter('opening branch %r (base %r:%r)', elements, base_path,
359 # Open paths leading up to branch
360 for i in range(0, len(elements)-1):
361 # Does directory already exist?
362 ret.append(ret[-1].open_directory(
363 "/".join(existing_elements[0:i+1]), -1))
365 if (len(existing_elements) != len(elements) and
366 len(existing_elements)+1 != len(elements)):
367 raise MissingPrefix("/".join(elements))
369 # Branch already exists and stayed at the same location, open:
370 # TODO: What if the branch didn't change but the new revision
371 # was based on an older revision of the branch?
372 # This needs to also check that base_rev was the latest version of
374 if (len(existing_elements) == len(elements) and
375 not replace_existing):
376 ret.append(ret[-1].open_directory(
377 "/".join(elements), base_rev))
378 else: # Branch has to be created
379 # Already exists, old copy needs to be removed
380 name = "/".join(elements)
383 raise ChangesRootLHSHistory()
384 self.mutter("removing branch dir %r", name)
385 ret[-1].delete_entry(name, -1)
386 if base_path is not None:
387 base_url = urlutils.join(self.repository.transport.svn_url, base_path)
390 self.mutter("adding branch dir %r", name)
391 ret.append(ret[-1].add_directory(
392 name, base_url, base_rev))
396 def commit(self, message):
397 """Finish the commit.
400 def done(revision_data, pool):
401 """Callback that is called by the Subversion commit editor
402 once the commit finishes.
404 :param revision_data: Revision metadata
406 self.revision_metadata = revision_data
408 bp_parts = self.branch.get_branch_path().split("/")
409 repository_latest_revnum = self.repository.get_latest_revnum()
410 lock = self.repository.transport.lock_write(".")
411 set_revprops = self._config.get_set_revprops()
412 remaining_revprops = self._svn_revprops # Keep track of the revprops that haven't been set yet
415 def _dir_process_file_id(old_inv, new_inv, path, file_id):
417 for child_name in new_inv[file_id].children:
418 child_ie = new_inv.get_child(file_id, child_name)
419 new_child_path = new_inv.id2path(child_ie.file_id)
420 assert child_ie is not None
422 if (not child_ie.file_id in old_inv or
423 old_inv.id2path(child_ie.file_id) != new_child_path or
424 old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
425 ret.append((child_ie.file_id, new_child_path))
427 if (child_ie.kind == 'directory' and
428 child_ie.file_id in self.modified_dirs):
429 ret += _dir_process_file_id(old_inv, new_inv, new_child_path, child_ie.file_id)
434 if (self.old_inv.root is None or
435 self.new_inventory.root.file_id != self.old_inv.root.file_id):
436 fileids[""] = self.new_inventory.root.file_id
438 for id, path in _dir_process_file_id(self.old_inv, self.new_inventory, "", self.new_inventory.root.file_id):
441 self.base_mapping.export_fileid_map(fileids, self._svn_revprops, self._svnprops)
442 if self._config.get_log_strip_trailing_newline():
443 self.base_mapping.export_message(message, self._svn_revprops, self._svnprops)
444 message = message.rstrip("\n")
445 self._svn_revprops[properties.PROP_REVISION_LOG] = message.encode("utf-8")
448 existing_bp_parts = _check_dirs_exist(self.repository.transport,
450 self.revision_metadata = None
451 for prop in self._svn_revprops:
452 if not properties.is_valid_property_name(prop):
453 warning("Setting property %r with invalid characters in name", prop)
455 self.editor = self.repository.transport.get_commit_editor(
456 self._svn_revprops, done, None, False)
457 self._svn_revprops = {}
458 except NotImplementedError:
461 # Try without bzr: revprops
462 self.editor = self.repository.transport.get_commit_editor({
463 properties.PROP_REVISION_LOG: self._svn_revprops[properties.PROP_REVISION_LOG]},
465 del self._svn_revprops[properties.PROP_REVISION_LOG]
467 root = self.editor.open_root(self.base_revnum)
469 replace_existing = False
470 # See whether the base of the commit matches the lhs parent
471 # if not, we need to replace the existing directory
472 if len(bp_parts) == len(existing_bp_parts):
473 if self.base_path.strip("/") != "/".join(bp_parts).strip("/"):
474 replace_existing = True
475 elif self.base_revnum < self.repository._log.find_latest_change(self.branch.get_branch_path(), repository_latest_revnum):
476 replace_existing = True
478 if replace_existing and self.branch._get_append_revisions_only():
479 raise AppendRevisionsOnlyViolation(self.branch.base)
481 # TODO: Accept create_prefix argument (#118787)
482 branch_editors = self.open_branch_editors(root, bp_parts,
483 existing_bp_parts, self.base_path, self.base_revnum,
486 self._dir_process("", self.new_inventory.root.file_id,
489 # Set all the revprops
490 for prop, value in self._svnprops.items():
491 if not properties.is_valid_property_name(prop):
492 warning("Setting property %r with invalid characters in name", prop)
493 if value is not None:
494 value = value.encode('utf-8')
495 branch_editors[-1].change_prop(prop, value)
496 self.mutter("Setting root file property %r -> %r", prop, value)
498 for dir_editor in reversed(branch_editors):
505 assert self.revision_metadata is not None
507 self.repository._clear_cached_state()
509 revid = self.branch.generate_revision_id(self.revision_metadata.revision)
511 assert self._new_revision_id is None or self._new_revision_id == revid
513 self.mutter('commit %d finished. author: %r, date: %r, revid: %r',
514 self.revision_metadata.revision, self.revision_metadata.author,
515 self.revision_metadata.date, revid)
517 override_svn_revprops = self._config.get_override_svn_revprops()
518 if override_svn_revprops is not None:
520 if properties.PROP_REVISION_AUTHOR in override_svn_revprops:
521 new_revprops[properties.PROP_REVISION_AUTHOR] = self._committer.encode("utf-8")
522 if properties.PROP_REVISION_DATE in override_svn_revprops:
523 new_revprops[properties.PROP_REVISION_DATE] = time_to_cstring(1000000*self._timestamp)
524 set_svn_revprops(self.repository.transport, self.revision_metadata.revision, new_revprops)
527 set_svn_revprops(self.repository.transport, self.revision_metadata.revision,
529 except RevpropChangeFailed:
530 pass # Ignore for now
534 def record_entry_contents(self, ie, parent_invs, path, tree,
536 """Record the content of ie from tree into the commit if needed.
538 Side effect: sets ie.revision when unchanged
540 :param ie: An inventory entry present in the commit.
541 :param parent_invs: The inventories of the parent revisions of the
543 :param path: The path the entry is at in the tree.
544 :param tree: The tree which contains this entry and should be used to
546 :param content_summary: Summary data from the tree about the paths
547 content - stat, length, exec, sha/link target. This is only
548 accessed when the entry has a revision of None - that is when
549 it is a candidate to commit.
551 self.new_inventory.add(ie)
554 def replay_delta(builder, old_tree, new_tree):
555 """Replays a delta to a commit builder.
557 :param builder: The commit builder.
558 :param old_tree: Original tree on top of which the delta should be applied
559 :param new_tree: New tree that should be committed
561 for path, ie in new_tree.inventory.iter_entries():
562 builder.record_entry_contents(ie.copy(), [old_tree.inventory],
563 path, new_tree, None)
564 builder.finish_inventory()
565 delta = new_tree.changes_from(old_tree)
567 ie = builder.new_inventory[id]
570 while builder.new_inventory[id].parent_id is not None:
571 if builder.new_inventory[id].revision is None:
573 builder.new_inventory[id].revision = None
574 if builder.new_inventory[id].kind == 'directory':
575 builder.modified_directory(id, [])
576 id = builder.new_inventory[id].parent_id
578 assert ie.kind in ('symlink', 'file', 'directory')
579 if ie.kind == 'symlink':
580 builder.modified_link(ie.file_id, [], ie.symlink_target)
581 elif ie.kind == 'file':
583 return new_tree.get_file_text(ie.file_id)
584 builder.modified_file_text(ie.file_id, [], get_text)
586 for (_, id, _) in delta.added:
589 for (_, id, _, _, _) in delta.modified:
592 for (oldpath, _, id, _, _, _) in delta.renamed:
594 old_parent_id = old_tree.inventory.path2id(urlutils.dirname(oldpath))
595 if old_parent_id in builder.new_inventory:
596 touch_id(old_parent_id)
598 for (path, _, _) in delta.removed:
599 old_parent_id = old_tree.inventory.path2id(urlutils.dirname(path))
600 if old_parent_id in builder.new_inventory:
601 touch_id(old_parent_id)
604 def push_new(target_repository, target_branch_path, source,
606 """Push a revision into Subversion, creating a new branch.
608 This will do a new commit in the target branch.
610 :param target_branch_path: Path to create new branch at
611 :param source: Branch to pull the revision from
612 :param revision_id: Revision id of the revision to push
614 assert isinstance(source, Branch)
615 if stop_revision is None:
616 stop_revision = source.last_revision()
617 history = source.revision_history()
618 revhistory = list(history)
619 start_revid = NULL_REVISION
620 while len(revhistory) > 0:
621 revid = revhistory.pop()
622 # We've found the revision to push if there is a revision
623 # which LHS parent is present or if this is the first revision.
624 if (len(revhistory) == 0 or
625 target_repository.has_revision(revhistory[-1])):
629 # Get commit builder but specify that target_branch_path should
630 # be created and copied from (copy_path, copy_revnum)
631 class ImaginaryBranch(object):
632 """Simple branch that pretends to be empty but already exist."""
633 def __init__(self, repository):
634 self.repository = repository
635 self._revision_history = None
637 def get_config(self):
638 """See Branch.get_config()."""
639 return self.repository.get_config()
641 def revision_id_to_revno(self, revid):
644 return history.index(revid)
646 def last_revision_info(self):
647 """See Branch.last_revision_info()."""
648 last_revid = self.last_revision()
649 if last_revid is None:
650 return (0, NULL_REVISION)
651 return (history.index(last_revid), last_revid)
653 def last_revision(self):
654 """See Branch.last_revision()."""
655 parents = source.repository.get_parent_map(start_revid)[start_revid]
660 def get_branch_path(self, revnum=None):
661 """See SvnBranch.get_branch_path()."""
662 return target_branch_path
664 def generate_revision_id(self, revnum):
665 """See SvnBranch.generate_revision_id()."""
666 return self.repository.generate_revision_id(
667 revnum, self.get_branch_path(revnum),
668 self.repository.get_mapping())
670 push(ImaginaryBranch(target_repository), source, start_revid)
673 def push_revision_tree(target, config, source_repo, base_revid, revision_id,
675 old_tree = source_repo.revision_tree(revision_id)
676 base_tree = source_repo.revision_tree(base_revid)
678 builder = SvnCommitBuilder(target.repository, target, rev.parent_ids,
679 config, rev.timestamp,
680 rev.timezone, rev.committer, rev.properties,
681 revision_id, base_tree.inventory)
683 replay_delta(builder, base_tree, old_tree)
685 builder.commit(rev.message)
686 except SubversionException, (_, num):
687 if num == ERR_FS_TXN_OUT_OF_DATE:
688 raise DivergedBranches(source, target)
690 except ChangesRootLHSHistory:
691 raise BzrError("Unable to push revision %r because it would change the ordering of existing revisions on the Subversion repository root. Use rebase and try again or push to a non-root path" % revision_id)
693 if source_repo.has_signature_for_revision_id(revision_id):
694 pass # FIXME: Copy revision signature for rev
697 def push(target, source, revision_id):
698 """Push a revision into Subversion.
700 This will do a new commit in the target branch.
702 :param target: Branch to push to
703 :param source: Branch to pull the revision from
704 :param revision_id: Revision id of the revision to push
706 assert isinstance(source, Branch)
707 rev = source.repository.get_revision(revision_id)
708 mutter('pushing %r (%r)', revision_id, rev.parent_ids)
710 # revision on top of which to commit
711 if rev.parent_ids == []:
714 base_revid = rev.parent_ids[0]
718 push_revision_tree(target, target.get_config(), source.repository, base_revid, revision_id, rev)
722 if 'validate' in debug.debug_flags:
723 crev = target.repository.get_revision(revision_id)
724 ctree = target.repository.revision_tree(revision_id)
725 treedelta = ctree.changes_from(old_tree)
726 assert not treedelta.has_changed(), "treedelta: %r" % treedelta
727 assert crev.committer == rev.committer
728 assert crev.timezone == rev.timezone
729 assert crev.timestamp == rev.timestamp
730 assert crev.message == rev.message
731 assert crev.properties == rev.properties
734 class InterToSvnRepository(InterRepository):
735 """Any to Subversion repository actions."""
737 _matching_repo_format = SvnRepositoryFormat()
740 def _get_repo_format_to_test():
741 """See InterRepository._get_repo_format_to_test()."""
744 def copy_content(self, revision_id=None, pb=None):
745 """See InterRepository.copy_content."""
746 self.source.lock_read()
748 assert revision_id is not None, "fetching all revisions not supported"
749 # Go back over the LHS parent until we reach a revid we know
751 while not self.target.has_revision(revision_id):
752 todo.append(revision_id)
754 revision_id = self.source.get_parent_map(revision_id)[revision_id][0]
758 if revision_id == NULL_REVISION:
759 raise UnrelatedBranches()
763 mutter("pushing %r into svn", todo)
765 for revision_id in todo:
767 pb.update("pushing revisions", todo.index(revision_id), len(todo))
768 rev = self.source.get_revision(revision_id)
770 mutter('pushing %r', revision_id)
772 parent_revid = rev.parent_ids[0]
774 (bp, _, _) = self.target.lookup_revision_id(parent_revid)
775 if target_branch is None:
776 target_branch = Branch.open(urlutils.join(self.target.base, bp))
777 if target_branch.get_branch_path() != bp:
778 target_branch.set_branch_path(bp)
780 push_revision_tree(target_branch, target_branch.get_config(), self.source, parent_revid, revision_id, rev)
785 def fetch(self, revision_id=None, pb=None, find_ghosts=False):
786 """Fetch revisions. """
787 self.copy_content(revision_id=revision_id, pb=pb)
790 def is_compatible(source, target):
791 """Be compatible with SvnRepository."""
792 return isinstance(target, SvnRepository)