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
31 from bzrlib.plugins.svn.delta import send_stream
32 from bzrlib.plugins.svn.errors import ChangesRootLHSHistory, MissingPrefix, RevpropChangeFailed, ERR_FS_TXN_OUT_OF_DATE, ERR_REPOS_DISABLED_FEATURE
33 from bzrlib.plugins.svn.svk import (generate_svk_feature, serialize_svk_features,
34 parse_svk_features, SVN_PROP_SVK_MERGE)
35 from bzrlib.plugins.svn.logwalker import lazy_dict
36 from bzrlib.plugins.svn.mapping import parse_revision_id
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,
93 """Instantiate a new SvnCommitBuilder.
95 :param repository: SvnRepository to commit to.
96 :param branch: SvnBranch to commit to.
97 :param parents: List of parent revision ids.
98 :param config: Branch configuration to use.
99 :param timestamp: Optional timestamp recorded for commit.
100 :param timezone: Optional timezone for timestamp.
101 :param committer: Optional committer to set for commit.
102 :param revprops: Revision properties to set.
103 :param revision_id: Revision id for the new revision.
104 :param old_inv: Optional revision on top of which
105 the commit is happening
106 :param push_metadata: Whether or not to push all bazaar metadata
107 (in svn file properties, etc).
109 super(SvnCommitBuilder, self).__init__(repository, parents,
110 config, timestamp, timezone, committer, revprops, revision_id)
112 self.push_metadata = push_metadata
114 # Gather information about revision on top of which the commit is
117 self.base_revid = None
119 self.base_revid = parents[0]
121 self.base_revno = self.branch.revision_id_to_revno(self.base_revid)
122 if self.base_revid is None:
123 self.base_revnum = -1
124 self.base_path = None
125 self.base_mapping = repository.get_mapping()
127 (self.base_path, self.base_revnum, self.base_mapping) = \
128 repository.lookup_revision_id(self.base_revid)
131 if self.base_revid is None:
132 self.old_inv = Inventory(root_id=None)
134 self.old_inv = self.repository.get_inventory(self.base_revid)
136 self.old_inv = old_inv
137 # Not all repositories appear to set Inventory.revision_id,
138 # so allow None as well.
139 assert self.old_inv.revision_id in (None, self.base_revid)
141 # Determine revisions merged in this one
142 merges = filter(lambda x: x != self.base_revid, parents)
144 self.modified_files = {}
145 self.modified_dirs = set()
146 if self.base_revid is None:
147 base_branch_props = {}
149 base_branch_props = lazy_dict({}, self.repository.branchprop_list.get_properties, self.base_path, self.base_revnum)
150 (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)
153 old_svk_features = parse_svk_features(base_branch_props.get(SVN_PROP_SVK_MERGE, ""))
154 svk_features = set(old_svk_features)
159 svk_features.add(_revision_id_to_svk_feature(merge))
160 except InvalidRevisionId:
163 if old_svk_features != svk_features:
164 self._svnprops[SVN_PROP_SVK_MERGE] = serialize_svk_features(svk_features)
166 def mutter(self, text, *args):
167 if 'commit' in debug.debug_flags:
170 def _generate_revision_if_needed(self):
171 """See CommitBuilder._generate_revision_if_needed()."""
173 def finish_inventory(self):
174 """See CommitBuilder.finish_inventory()."""
176 def modified_file_text(self, file_id, file_parents,
177 get_content_byte_lines, text_sha1=None,
179 """See CommitBuilder.modified_file_text()."""
180 new_lines = get_content_byte_lines()
181 self.modified_files[file_id] = "".join(new_lines)
182 return osutils.sha_strings(new_lines), sum(map(len, new_lines))
184 def modified_link(self, file_id, file_parents, link_target):
185 """See CommitBuilder.modified_link()."""
186 self.modified_files[file_id] = "link %s" % link_target
188 def modified_directory(self, file_id, file_parents):
189 """See CommitBuilder.modified_directory()."""
190 self.modified_dirs.add(file_id)
192 def _file_process(self, file_id, contents, file_editor):
193 """Pass the changes to a file to the Subversion commit editor.
195 :param file_id: Id of the file to modify.
196 :param contents: Contents of the file.
197 :param file_editor: Subversion FileEditor object.
199 assert file_editor is not None
200 txdelta = file_editor.apply_textdelta()
201 digest = send_stream(StringIO(contents), txdelta)
202 if 'validate' in debug.debug_flags:
203 from fetch import md5_strings
204 assert digest == md5_strings(contents)
206 def _dir_process(self, path, file_id, dir_editor):
207 """Pass the changes to a directory to the commit editor.
209 :param path: Path (from repository root) to the directory.
210 :param file_id: File id of the directory
211 :param dir_editor: Subversion DirEditor object.
213 assert dir_editor is not None
214 # Loop over entries of file_id in self.old_inv
215 # remove if they no longer exist with the same name
217 if file_id in self.old_inv:
218 for child_name in self.old_inv[file_id].children:
219 child_ie = self.old_inv.get_child(file_id, child_name)
222 # ... path no longer exists
223 not child_ie.file_id in self.new_inventory or
225 child_ie.parent_id != self.new_inventory[child_ie.file_id].parent_id or
227 self.new_inventory[child_ie.file_id].name != child_name):
228 self.mutter('removing %r(%r)', (child_name, child_ie.file_id))
229 dir_editor.delete_entry(
230 urlutils.join(self.branch.get_branch_path(), path, child_name),
233 # Loop over file children of file_id in self.new_inventory
234 for child_name in self.new_inventory[file_id].children:
235 child_ie = self.new_inventory.get_child(file_id, child_name)
236 assert child_ie is not None
238 if not (child_ie.kind in ('file', 'symlink')):
241 new_child_path = self.new_inventory.id2path(child_ie.file_id).encode("utf-8")
242 full_new_child_path = urlutils.join(self.branch.get_branch_path(),
244 # add them if they didn't exist in old_inv
245 if not child_ie.file_id in self.old_inv:
246 self.mutter('adding %s %r', child_ie.kind, new_child_path)
247 child_editor = dir_editor.add_file(full_new_child_path)
249 # copy if they existed at different location
250 elif (self.old_inv.id2path(child_ie.file_id) != new_child_path or
251 self.old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
252 self.mutter('copy %s %r -> %r', child_ie.kind,
253 self.old_inv.id2path(child_ie.file_id),
255 child_editor = dir_editor.add_file(
257 urlutils.join(self.repository.transport.svn_url, self.base_path, self.old_inv.id2path(child_ie.file_id)),
260 # open if they existed at the same location
261 elif child_ie.revision is None:
262 self.mutter('open %s %r', child_ie.kind, new_child_path)
264 child_editor = dir_editor.open_file(
265 full_new_child_path, self.base_revnum)
268 # Old copy of the file was retained. No need to send changes
269 assert child_ie.file_id not in self.modified_files
272 if child_ie.file_id in self.old_inv:
273 old_executable = self.old_inv[child_ie.file_id].executable
274 old_special = (self.old_inv[child_ie.file_id].kind == 'symlink')
277 old_executable = False
279 if child_editor is not None:
280 if old_executable != child_ie.executable:
281 if child_ie.executable:
282 value = properties.PROP_EXECUTABLE_VALUE
285 child_editor.change_prop(
286 properties.PROP_EXECUTABLE, value)
288 if old_special != (child_ie.kind == 'symlink'):
289 if child_ie.kind == 'symlink':
290 value = properties.PROP_SPECIAL_VALUE
294 child_editor.change_prop(
295 properties.PROP_SPECIAL, value)
298 if child_ie.file_id in self.modified_files:
299 self._file_process(child_ie.file_id,
300 self.modified_files[child_ie.file_id], child_editor)
302 if child_editor is not None:
305 # Loop over subdirectories of file_id in self.new_inventory
306 for child_name in self.new_inventory[file_id].children:
307 child_ie = self.new_inventory.get_child(file_id, child_name)
308 if child_ie.kind != 'directory':
311 new_child_path = self.new_inventory.id2path(child_ie.file_id)
312 # add them if they didn't exist in old_inv
313 if not child_ie.file_id in self.old_inv:
314 self.mutter('adding dir %r', child_ie.name)
315 child_editor = dir_editor.add_directory(
316 urlutils.join(self.branch.get_branch_path(),
319 # copy if they existed at different location
320 elif self.old_inv.id2path(child_ie.file_id) != new_child_path:
321 old_child_path = self.old_inv.id2path(child_ie.file_id)
322 self.mutter('copy dir %r -> %r', old_child_path, new_child_path)
323 child_editor = dir_editor.add_directory(
324 urlutils.join(self.branch.get_branch_path(), new_child_path),
325 urlutils.join(self.repository.transport.svn_url, self.base_path, old_child_path), self.base_revnum)
327 # open if they existed at the same location and
328 # the directory was touched
329 elif self.new_inventory[child_ie.file_id].revision is None:
330 self.mutter('open dir %r', new_child_path)
332 child_editor = dir_editor.open_directory(
333 urlutils.join(self.branch.get_branch_path(), new_child_path),
336 assert child_ie.file_id not in self.modified_dirs
339 # Handle this directory
340 if child_ie.file_id in self.modified_dirs:
341 self._dir_process(new_child_path, child_ie.file_id, child_editor)
345 def open_branch_editors(self, root, elements, existing_elements,
346 base_path, base_rev, replace_existing):
347 """Open a specified directory given an editor for the repository root.
349 :param root: Editor for the repository root
350 :param elements: List of directory names to open
351 :param existing_elements: List of directory names that exist
352 :param base_path: Path to base top-level branch on
353 :param base_rev: Revision of path to base top-level branch on
354 :param replace_existing: Whether the current branch should be replaced
358 self.mutter('opening branch %r (base %r:%r)', elements, base_path,
361 # Open paths leading up to branch
362 for i in range(0, len(elements)-1):
363 # Does directory already exist?
364 ret.append(ret[-1].open_directory(
365 "/".join(existing_elements[0:i+1]), -1))
367 if (len(existing_elements) != len(elements) and
368 len(existing_elements)+1 != len(elements)):
369 raise MissingPrefix("/".join(elements))
371 # Branch already exists and stayed at the same location, open:
372 # TODO: What if the branch didn't change but the new revision
373 # was based on an older revision of the branch?
374 # This needs to also check that base_rev was the latest version of
376 if (len(existing_elements) == len(elements) and
377 not replace_existing):
378 ret.append(ret[-1].open_directory(
379 "/".join(elements), base_rev))
380 else: # Branch has to be created
381 # Already exists, old copy needs to be removed
382 name = "/".join(elements)
385 raise ChangesRootLHSHistory()
386 self.mutter("removing branch dir %r", name)
387 ret[-1].delete_entry(name, -1)
388 if base_path is not None:
389 base_url = urlutils.join(self.repository.transport.svn_url, base_path)
392 self.mutter("adding branch dir %r", name)
393 ret.append(ret[-1].add_directory(
394 name, base_url, base_rev))
398 def commit(self, message):
399 """Finish the commit.
403 """Callback that is called by the Subversion commit editor
404 once the commit finishes.
406 :param revision_data: Revision metadata
408 self.revision_metadata = args
410 bp_parts = self.branch.get_branch_path().split("/")
411 repository_latest_revnum = self.repository.get_latest_revnum()
412 lock = self.repository.transport.lock_write(".")
413 set_revprops = self._config.get_set_revprops()
414 remaining_revprops = self._svn_revprops # Keep track of the revprops that haven't been set yet
417 def _dir_process_file_id(old_inv, new_inv, path, file_id):
419 for child_name in new_inv[file_id].children:
420 child_ie = new_inv.get_child(file_id, child_name)
421 new_child_path = new_inv.id2path(child_ie.file_id)
422 assert child_ie is not None
424 if (not child_ie.file_id in old_inv or
425 old_inv.id2path(child_ie.file_id) != new_child_path or
426 old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
427 ret.append((child_ie.file_id, new_child_path))
429 if (child_ie.kind == 'directory' and
430 child_ie.file_id in self.modified_dirs):
431 ret += _dir_process_file_id(old_inv, new_inv, new_child_path, child_ie.file_id)
436 if (self.old_inv.root is None or
437 self.new_inventory.root.file_id != self.old_inv.root.file_id):
438 fileids[""] = self.new_inventory.root.file_id
440 for id, path in _dir_process_file_id(self.old_inv, self.new_inventory, "", self.new_inventory.root.file_id):
443 self.base_mapping.export_fileid_map(fileids, self._svn_revprops, self._svnprops)
444 if self._config.get_log_strip_trailing_newline():
445 self.base_mapping.export_message(message, self._svn_revprops, self._svnprops)
446 message = message.rstrip("\n")
447 if not self.push_metadata:
448 self._svn_revprops = {}
449 self._svn_revprops[properties.PROP_REVISION_LOG] = message.encode("utf-8")
452 existing_bp_parts = _check_dirs_exist(self.repository.transport,
454 self.revision_metadata = None
455 for prop in self._svn_revprops:
456 if not properties.is_valid_property_name(prop):
457 warning("Setting property %r with invalid characters in name", prop)
458 if self.repository.transport.has_capability("commit-revprops"):
459 self.editor = self.repository.transport.get_commit_editor(
460 self._svn_revprops, done, None, False)
461 self._svn_revprops = {}
465 # Try without bzr: revprops
466 self.editor = self.repository.transport.get_commit_editor({
467 properties.PROP_REVISION_LOG: self._svn_revprops[properties.PROP_REVISION_LOG]},
469 del self._svn_revprops[properties.PROP_REVISION_LOG]
471 root = self.editor.open_root(self.base_revnum)
473 replace_existing = False
474 # See whether the base of the commit matches the lhs parent
475 # if not, we need to replace the existing directory
476 if len(bp_parts) == len(existing_bp_parts):
477 if self.base_path.strip("/") != "/".join(bp_parts).strip("/"):
478 replace_existing = True
479 elif self.base_revnum < self.repository._log.find_latest_change(self.branch.get_branch_path(), repository_latest_revnum):
480 replace_existing = True
482 if replace_existing and self.branch._get_append_revisions_only():
483 raise AppendRevisionsOnlyViolation(self.branch.base)
485 # TODO: Accept create_prefix argument (#118787)
486 branch_editors = self.open_branch_editors(root, bp_parts,
487 existing_bp_parts, self.base_path, self.base_revnum,
490 self._dir_process("", self.new_inventory.root.file_id,
493 # Set all the revprops
494 if self.push_metadata:
495 for prop, value in self._svnprops.items():
496 if not properties.is_valid_property_name(prop):
497 warning("Setting property %r with invalid characters in name", prop)
498 if value is not None:
499 value = value.encode('utf-8')
500 branch_editors[-1].change_prop(prop, value)
501 self.mutter("Setting root file property %r -> %r", prop, value)
503 for dir_editor in reversed(branch_editors):
510 assert self.revision_metadata is not None
512 self.repository._clear_cached_state()
514 (result_revision, result_date, result_author) = self.revision_metadata
516 revid = self.branch.generate_revision_id(result_revision)
518 assert self._new_revision_id is None or self._new_revision_id == revid
520 self.mutter('commit %d finished. author: %r, date: %r, revid: %r',
521 result_revision, result_author,
524 override_svn_revprops = self._config.get_override_svn_revprops()
525 if override_svn_revprops is not None:
527 if properties.PROP_REVISION_AUTHOR in override_svn_revprops:
528 new_revprops[properties.PROP_REVISION_AUTHOR] = self._committer.encode("utf-8")
529 if properties.PROP_REVISION_DATE in override_svn_revprops:
530 new_revprops[properties.PROP_REVISION_DATE] = properties.time_to_cstring(1000000*self._timestamp)
531 set_svn_revprops(self.repository.transport, result_revision, new_revprops)
534 set_svn_revprops(self.repository.transport, result_revision,
536 except RevpropChangeFailed:
537 pass # Ignore for now
541 def record_entry_contents(self, ie, parent_invs, path, tree,
543 """Record the content of ie from tree into the commit if needed.
545 Side effect: sets ie.revision when unchanged
547 :param ie: An inventory entry present in the commit.
548 :param parent_invs: The inventories of the parent revisions of the
550 :param path: The path the entry is at in the tree.
551 :param tree: The tree which contains this entry and should be used to
553 :param content_summary: Summary data from the tree about the paths
554 content - stat, length, exec, sha/link target. This is only
555 accessed when the entry has a revision of None - that is when
556 it is a candidate to commit.
558 self.new_inventory.add(ie)
561 def replay_delta(builder, old_tree, new_tree):
562 """Replays a delta to a commit builder.
564 :param builder: The commit builder.
565 :param old_tree: Original tree on top of which the delta should be applied
566 :param new_tree: New tree that should be committed
568 for path, ie in new_tree.inventory.iter_entries():
569 builder.record_entry_contents(ie.copy(), [old_tree.inventory],
570 path, new_tree, None)
571 builder.finish_inventory()
572 delta = new_tree.changes_from(old_tree)
574 ie = builder.new_inventory[id]
577 while builder.new_inventory[id].parent_id is not None:
578 if builder.new_inventory[id].revision is None:
580 builder.new_inventory[id].revision = None
581 if builder.new_inventory[id].kind == 'directory':
582 builder.modified_directory(id, [])
583 id = builder.new_inventory[id].parent_id
585 assert ie.kind in ('symlink', 'file', 'directory')
586 if ie.kind == 'symlink':
587 builder.modified_link(ie.file_id, [], ie.symlink_target)
588 elif ie.kind == 'file':
590 return new_tree.get_file_text(ie.file_id)
591 builder.modified_file_text(ie.file_id, [], get_text)
593 for (_, id, _) in delta.added:
596 for (_, id, _, _, _) in delta.modified:
599 for (oldpath, _, id, _, _, _) in delta.renamed:
601 old_parent_id = old_tree.inventory.path2id(urlutils.dirname(oldpath))
602 if old_parent_id in builder.new_inventory:
603 touch_id(old_parent_id)
605 for (path, _, _) in delta.removed:
606 old_parent_id = old_tree.inventory.path2id(urlutils.dirname(path))
607 if old_parent_id in builder.new_inventory:
608 touch_id(old_parent_id)
611 def push_new(target_repository, target_branch_path, source,
613 """Push a revision into Subversion, creating a new branch.
615 This will do a new commit in the target branch.
617 :param target_repository: Repository to push to
618 :param target_branch_path: Path to create new branch at
619 :param source: Branch to pull the revision from
621 assert isinstance(source, Branch)
622 history = source.revision_history()
623 revhistory = list(history)
624 start_revid = NULL_REVISION
625 while len(revhistory) > 0:
626 revid = revhistory.pop()
627 # We've found the revision to push if there is a revision
628 # which LHS parent is present or if this is the first revision.
629 if (len(revhistory) == 0 or
630 target_repository.has_revision(revhistory[-1])):
634 # Get commit builder but specify that target_branch_path should
635 # be created and copied from (copy_path, copy_revnum)
636 class ImaginaryBranch(object):
637 """Simple branch that pretends to be empty but already exist."""
638 def __init__(self, repository):
639 self.repository = repository
640 self._revision_history = None
642 def get_config(self):
643 """See Branch.get_config()."""
644 return self.repository.get_config()
646 def revision_id_to_revno(self, revid):
649 return history.index(revid)
651 def last_revision_info(self):
652 """See Branch.last_revision_info()."""
653 last_revid = self.last_revision()
654 if last_revid is None:
655 return (0, NULL_REVISION)
656 return (history.index(last_revid), last_revid)
658 def last_revision(self):
659 """See Branch.last_revision()."""
660 parents = source.repository.get_parent_map(start_revid)[start_revid]
665 def get_branch_path(self, revnum=None):
666 """See SvnBranch.get_branch_path()."""
667 return target_branch_path
669 def generate_revision_id(self, revnum):
670 """See SvnBranch.generate_revision_id()."""
671 return self.repository.generate_revision_id(
672 revnum, self.get_branch_path(revnum),
673 self.repository.get_mapping())
675 push(ImaginaryBranch(target_repository), source, start_revid, push_metadata=push_metadata)
678 def push_revision_tree(target, config, source_repo, base_revid, revision_id,
679 rev, push_metadata=True):
680 old_tree = source_repo.revision_tree(revision_id)
681 base_tree = source_repo.revision_tree(base_revid)
683 builder = SvnCommitBuilder(target.repository, target, rev.parent_ids,
684 config, rev.timestamp,
685 rev.timezone, rev.committer, rev.properties,
686 revision_id, base_tree.inventory,
687 push_metadata=push_metadata)
689 replay_delta(builder, base_tree, old_tree)
691 revid = builder.commit(rev.message)
692 except SubversionException, (_, num):
693 if num == ERR_FS_TXN_OUT_OF_DATE:
694 raise DivergedBranches(source, target)
696 except ChangesRootLHSHistory:
697 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)
699 if source_repo.has_signature_for_revision_id(revision_id):
700 pass # FIXME: Copy revision signature for rev
705 def push(target, source, revision_id, push_metadata=True):
706 """Push a revision into Subversion.
708 This will do a new commit in the target branch.
710 :param target: Branch to push to
711 :param source: Branch to pull the revision from
712 :param revision_id: Revision id of the revision to push
713 :return: revision id of revision that was pushed
715 assert isinstance(source, Branch)
716 rev = source.repository.get_revision(revision_id)
717 mutter('pushing %r (%r)', revision_id, rev.parent_ids)
719 # revision on top of which to commit
720 if rev.parent_ids == []:
723 base_revid = rev.parent_ids[0]
727 revid = push_revision_tree(target, target.get_config(), source.repository, base_revid,
728 revision_id, rev, push_metadata=push_metadata)
732 assert revid == revision_id or not push_metadata
734 if 'validate' in debug.debug_flags and push_metadata:
735 crev = target.repository.get_revision(revision_id)
736 ctree = target.repository.revision_tree(revision_id)
737 treedelta = ctree.changes_from(old_tree)
738 assert not treedelta.has_changed(), "treedelta: %r" % treedelta
739 assert crev.committer == rev.committer
740 assert crev.timezone == rev.timezone
741 assert crev.timestamp == rev.timestamp
742 assert crev.message == rev.message
743 assert crev.properties == rev.properties
748 class InterToSvnRepository(InterRepository):
749 """Any to Subversion repository actions."""
751 _matching_repo_format = SvnRepositoryFormat()
754 def _get_repo_format_to_test():
755 """See InterRepository._get_repo_format_to_test()."""
758 def copy_content(self, revision_id=None, pb=None):
759 """See InterRepository.copy_content."""
760 self.source.lock_read()
762 assert revision_id is not None, "fetching all revisions not supported"
763 # Go back over the LHS parent until we reach a revid we know
765 while not self.target.has_revision(revision_id):
766 todo.append(revision_id)
768 revision_id = self.source.get_parent_map(revision_id)[revision_id][0]
772 if revision_id == NULL_REVISION:
773 raise UnrelatedBranches()
777 mutter("pushing %r into svn", todo)
779 for revision_id in todo:
781 pb.update("pushing revisions", todo.index(revision_id), len(todo))
782 rev = self.source.get_revision(revision_id)
784 mutter('pushing %r', revision_id)
786 parent_revid = rev.parent_ids[0]
788 (bp, _, _) = self.target.lookup_revision_id(parent_revid)
789 if target_branch is None:
790 target_branch = Branch.open(urlutils.join(self.target.base, bp))
791 if target_branch.get_branch_path() != bp:
792 target_branch.set_branch_path(bp)
794 push_revision_tree(target_branch, target_branch.get_config(), self.source, parent_revid, revision_id, rev)
799 def fetch(self, revision_id=None, pb=None, find_ghosts=False):
800 """Fetch revisions. """
801 self.copy_content(revision_id=revision_id, pb=pb)
804 def is_compatible(source, target):
805 """Be compatible with SvnRepository."""
806 return isinstance(target, SvnRepository)