Create a new class, DumpstreamDelegate.
[cvs2svn.git] / cvs2svn_lib / svn_output_option.py
blobbf32934aca7fae050a183b3b5dc515caa3af8ce4
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2009 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """Classes for outputting the converted repository to SVN."""
20 import os
22 from cvs2svn_lib import config
23 from cvs2svn_lib.common import InternalError
24 from cvs2svn_lib.common import FatalError
25 from cvs2svn_lib.common import FatalException
26 from cvs2svn_lib.common import error_prefix
27 from cvs2svn_lib.common import format_date
28 from cvs2svn_lib.common import PathsNotDisjointException
29 from cvs2svn_lib.common import verify_paths_disjoint
30 from cvs2svn_lib.log import Log
31 from cvs2svn_lib.context import Ctx
32 from cvs2svn_lib.artifact_manager import artifact_manager
33 from cvs2svn_lib.process import CommandFailedException
34 from cvs2svn_lib.process import check_command_runs
35 from cvs2svn_lib.process import call_command
36 from cvs2svn_lib.cvs_path import CVSDirectory
37 from cvs2svn_lib.symbol import Trunk
38 from cvs2svn_lib.symbol import LineOfDevelopment
39 from cvs2svn_lib.cvs_item import CVSRevisionAdd
40 from cvs2svn_lib.cvs_item import CVSRevisionChange
41 from cvs2svn_lib.cvs_item import CVSRevisionDelete
42 from cvs2svn_lib.cvs_item import CVSRevisionNoop
43 from cvs2svn_lib.repository_mirror import RepositoryMirror
44 from cvs2svn_lib.repository_mirror import PathExistsError
45 from cvs2svn_lib.openings_closings import SymbolingsReader
46 from cvs2svn_lib.fill_source import get_source_set
47 from cvs2svn_lib.stdout_delegate import StdoutDelegate
48 from cvs2svn_lib.svn_dump import DumpstreamDelegate
49 from cvs2svn_lib.repository_delegate import RepositoryDelegate
50 from cvs2svn_lib.output_option import OutputOption
53 class SVNOutputOption(OutputOption):
54 """An OutputOption appropriate for output to Subversion."""
56 class ParentMissingError(Exception):
57 """The parent of a path is missing.
59 Exception raised if an attempt is made to add a path to the
60 repository mirror but the parent's path doesn't exist in the
61 youngest revision of the repository."""
63 pass
65 class ExpectedDirectoryError(Exception):
66 """A file was found where a directory was expected."""
68 pass
70 def __init__(self, author_transforms=None):
71 self._mirror = RepositoryMirror()
73 def to_utf8(s):
74 if isinstance(s, unicode):
75 return s.encode('utf8')
76 else:
77 return s
79 self.author_transforms = {}
80 if author_transforms is not None:
81 for (cvsauthor, name) in author_transforms.iteritems():
82 cvsauthor = to_utf8(cvsauthor)
83 name = to_utf8(name)
84 self.author_transforms[cvsauthor] = name
86 def register_artifacts(self, which_pass):
87 # These artifacts are needed for SymbolingsReader:
88 artifact_manager.register_temp_file_needed(
89 config.SYMBOL_OPENINGS_CLOSINGS_SORTED, which_pass
91 artifact_manager.register_temp_file_needed(
92 config.SYMBOL_OFFSETS_DB, which_pass
95 self._mirror.register_artifacts(which_pass)
96 Ctx().revision_reader.register_artifacts(which_pass)
98 def check_symbols(self, symbol_map):
99 """Check that the paths of all included LODs are set and disjoint."""
101 error_found = False
103 # Check that all included LODs have their base paths set, and
104 # collect the paths into a list:
105 paths = []
106 for lod in symbol_map.itervalues():
107 if isinstance(lod, LineOfDevelopment):
108 if lod.base_path is None:
109 Log().error('%s: No path was set for %r\n' % (error_prefix, lod,))
110 error_found = True
111 else:
112 paths.append(lod.base_path)
114 # Check that the SVN paths of all LODS are disjoint:
115 try:
116 verify_paths_disjoint(*paths)
117 except PathsNotDisjointException, e:
118 Log().error(str(e))
119 error_found = True
121 if error_found:
122 raise FatalException(
123 'Please fix the above errors and restart CollateSymbolsPass'
126 def setup(self, svn_rev_count):
127 self._symbolings_reader = SymbolingsReader()
128 self._mirror.open()
129 self._delegates = []
130 Ctx().revision_reader.start()
131 self.add_delegate(StdoutDelegate(svn_rev_count))
133 def _get_author(self, svn_commit):
134 author = svn_commit.get_author()
135 name = self.author_transforms.get(author, author)
136 return name
138 def _get_revprops(self, svn_commit):
139 """Return the Subversion revprops for this SVNCommit."""
141 return {
142 'svn:author' : self._get_author(svn_commit),
143 'svn:log' : svn_commit.get_log_msg(),
144 'svn:date' : format_date(svn_commit.date),
147 def start_commit(self, revnum, revprops):
148 """Start a new commit."""
150 self._mirror.start_commit(revnum)
151 self._invoke_delegates('start_commit', revnum, revprops)
153 def end_commit(self):
154 """Called at the end of each commit.
156 This method copies the newly created nodes to the on-disk nodes
157 db."""
159 self._mirror.end_commit()
160 self._invoke_delegates('end_commit')
162 def delete_lod(self, lod):
163 """Delete the main path for LOD from the tree.
165 The path must currently exist. Silently refuse to delete trunk
166 paths."""
168 if isinstance(lod, Trunk):
169 # Never delete a Trunk path.
170 return
172 self._mirror.get_current_lod_directory(lod).delete()
173 self._invoke_delegates('delete_lod', lod)
175 def delete_path(self, cvs_path, lod, should_prune=False):
176 """Delete CVS_PATH from LOD."""
178 if cvs_path.parent_directory is None:
179 self.delete_lod(lod)
180 return
182 parent_node = self._mirror.get_current_path(
183 cvs_path.parent_directory, lod
185 del parent_node[cvs_path]
186 self._invoke_delegates('delete_path', lod, cvs_path)
188 if should_prune:
189 while parent_node is not None and len(parent_node) == 0:
190 # A drawback of this code is that we issue a delete for each
191 # path and not just a single delete for the topmost directory
192 # pruned.
193 node = parent_node
194 cvs_path = node.cvs_path
195 if cvs_path.parent_directory is None:
196 parent_node = None
197 self.delete_lod(lod)
198 else:
199 parent_node = node.parent_mirror_dir
200 node.delete()
201 self._invoke_delegates('delete_path', lod, cvs_path)
203 def initialize_project(self, project):
204 """Create the basic structure for PROJECT."""
206 self._invoke_delegates('initialize_project', project)
208 # Don't invoke delegates.
209 self._mirror.add_lod(project.get_trunk())
210 if Ctx().include_empty_directories:
211 self._make_empty_subdirectories(
212 project.get_root_cvs_directory(), project.get_trunk()
215 def change_path(self, cvs_rev):
216 """Register a change in self._youngest for the CVS_REV's svn_path."""
218 # We do not have to update the nodes because our mirror is only
219 # concerned with the presence or absence of paths, and a file
220 # content change does not cause any path changes.
221 self._invoke_delegates('change_path', cvs_rev)
223 def _make_empty_subdirectories(self, cvs_directory, lod):
224 """Make any empty subdirectories of CVS_DIRECTORY in LOD."""
226 for empty_subdirectory_id in cvs_directory.empty_subdirectory_ids:
227 empty_subdirectory = Ctx()._cvs_path_db.get_path(empty_subdirectory_id)
228 # There is no need to record the empty subdirectories in the
229 # mirror, since they live and die with their parent directories.
230 self._invoke_delegates('mkdir', lod, empty_subdirectory)
231 self._make_empty_subdirectories(empty_subdirectory, lod)
233 def _mkdir_p(self, cvs_directory, lod):
234 """Make sure that CVS_DIRECTORY exists in LOD.
236 If not, create it, calling delegates. Return the node for
237 CVS_DIRECTORY."""
239 ancestry = cvs_directory.get_ancestry()
241 try:
242 node = self._mirror.get_current_lod_directory(lod)
243 except KeyError:
244 node = self._mirror.add_lod(lod)
245 self._invoke_delegates('initialize_lod', lod)
246 if ancestry and Ctx().include_empty_directories:
247 self._make_empty_subdirectories(ancestry[0], lod)
249 for sub_path in ancestry[1:]:
250 try:
251 node = node[sub_path]
252 except KeyError:
253 node = node.mkdir(sub_path)
254 self._invoke_delegates('mkdir', lod, sub_path)
255 if Ctx().include_empty_directories:
256 self._make_empty_subdirectories(sub_path, lod)
257 if node is None:
258 raise self.ExpectedDirectoryError(
259 'File found at \'%s\' where directory was expected.' % (sub_path,)
262 return node
264 def add_path(self, cvs_rev):
265 """Add the CVS_REV's svn_path to the repository mirror.
267 Create any missing intermediate paths."""
269 cvs_file = cvs_rev.cvs_file
270 parent_path = cvs_file.parent_directory
271 lod = cvs_rev.lod
272 parent_node = self._mkdir_p(parent_path, lod)
273 parent_node.add_file(cvs_file)
274 self._invoke_delegates('add_path', cvs_rev)
276 def copy_lod(self, src_lod, dest_lod, src_revnum):
277 """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD.
279 In the youngest revision of the repository, the destination LOD
280 *must not* already exist.
282 Return the new node at DEST_LOD. Note that this node is not
283 necessarily writable, though its parent node necessarily is."""
285 node = self._mirror.copy_lod(src_lod, dest_lod, src_revnum)
286 self._invoke_delegates('copy_lod', src_lod, dest_lod, src_revnum)
287 return node
289 def copy_path(
290 self, cvs_path, src_lod, dest_lod, src_revnum, create_parent=False
292 """Copy CVS_PATH from SRC_LOD at SRC_REVNUM to DST_LOD.
294 In the youngest revision of the repository, the destination's
295 parent *must* exist unless CREATE_PARENT is specified. But the
296 destination itself *must not* exist.
298 Return the new node at (CVS_PATH, DEST_LOD), as a
299 CurrentMirrorDirectory."""
301 if cvs_path.parent_directory is None:
302 return self.copy_lod(src_lod, dest_lod, src_revnum)
304 # Get the node of our source, or None if it is a file:
305 src_node = self._mirror.get_old_path(cvs_path, src_lod, src_revnum)
307 # Get the parent path of the destination:
308 if create_parent:
309 dest_parent_node = self._mkdir_p(cvs_path.parent_directory, dest_lod)
310 else:
311 try:
312 dest_parent_node = self._mirror.get_current_path(
313 cvs_path.parent_directory, dest_lod
315 except KeyError:
316 raise self.ParentMissingError(
317 'Attempt to add path \'%s\' to repository mirror, '
318 'but its parent directory doesn\'t exist in the mirror.'
319 % (dest_lod.get_path(cvs_path.cvs_path),)
322 if cvs_path in dest_parent_node:
323 raise PathExistsError(
324 'Attempt to add path \'%s\' to repository mirror '
325 'when it already exists in the mirror.'
326 % (dest_lod.get_path(cvs_path.cvs_path),)
329 dest_parent_node[cvs_path] = src_node
330 self._invoke_delegates(
331 'copy_path',
332 cvs_path, src_lod, dest_lod, src_revnum
335 return dest_parent_node[cvs_path]
337 def fill_symbol(self, svn_symbol_commit, fill_source):
338 """Perform all copies for the CVSSymbols in SVN_SYMBOL_COMMIT.
340 The symbolic name is guaranteed to exist in the Subversion
341 repository by the end of this call, even if there are no paths
342 under it."""
344 symbol = svn_symbol_commit.symbol
346 try:
347 dest_node = self._mirror.get_current_lod_directory(symbol)
348 except KeyError:
349 self._fill_directory(symbol, None, fill_source, None)
350 else:
351 self._fill_directory(symbol, dest_node, fill_source, None)
353 def _fill_directory(self, symbol, dest_node, fill_source, parent_source):
354 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
356 Use items from FILL_SOURCE, and recurse into the child items.
358 Fill SYMBOL starting at the path FILL_SOURCE.cvs_path. DEST_NODE
359 is the node of this destination path, or None if the destination
360 does not yet exist. All directories above this path have already
361 been filled. FILL_SOURCE is a FillSource instance describing the
362 items within a subtree of the repository that still need to be
363 copied to the destination.
365 PARENT_SOURCE is the SVNRevisionRange that was used to copy the
366 parent directory, if it was copied in this commit. We prefer to
367 copy from the same source as was used for the parent, since it
368 typically requires less touching-up. If PARENT_SOURCE is None,
369 then the parent directory was not copied in this commit, so no
370 revision is preferable to any other."""
372 copy_source = fill_source.compute_best_source(parent_source)
374 # Figure out if we shall copy to this destination and delete any
375 # destination path that is in the way.
376 if dest_node is None:
377 # The destination does not exist at all, so it definitely has to
378 # be copied:
379 dest_node = self.copy_path(
380 fill_source.cvs_path, copy_source.source_lod,
381 symbol, copy_source.opening_revnum
383 elif (parent_source is not None) and (
384 copy_source.source_lod != parent_source.source_lod
385 or copy_source.opening_revnum != parent_source.opening_revnum
387 # The parent path was copied from a different source than we
388 # need to use, so we have to delete the version that was copied
389 # with the parent then re-copy from the correct source:
390 self.delete_path(fill_source.cvs_path, symbol)
391 dest_node = self.copy_path(
392 fill_source.cvs_path, copy_source.source_lod,
393 symbol, copy_source.opening_revnum
395 else:
396 copy_source = parent_source
398 # The map {CVSPath : FillSource} of entries within this directory
399 # that need filling:
400 src_entries = fill_source.get_subsource_map()
402 if copy_source is not None:
403 self._prune_extra_entries(
404 fill_source.cvs_path, symbol, dest_node, src_entries
407 return self._cleanup_filled_directory(
408 symbol, dest_node, src_entries, copy_source
411 def _cleanup_filled_directory(
412 self, symbol, dest_node, src_entries, copy_source
414 """The directory at DEST_NODE has been filled and pruned; recurse.
416 Recurse into the SRC_ENTRIES, in alphabetical order. If DEST_NODE
417 was copied in this revision, COPY_SOURCE should indicate where it
418 was copied from; otherwise, COPY_SOURCE should be None."""
420 cvs_paths = src_entries.keys()
421 cvs_paths.sort()
422 for cvs_path in cvs_paths:
423 if isinstance(cvs_path, CVSDirectory):
424 # Path is a CVSDirectory:
425 try:
426 dest_subnode = dest_node[cvs_path]
427 except KeyError:
428 # Path doesn't exist yet; it has to be created:
429 dest_node = self._fill_directory(
430 symbol, None, src_entries[cvs_path], None
431 ).parent_mirror_dir
432 else:
433 # Path already exists, but might have to be cleaned up:
434 dest_node = self._fill_directory(
435 symbol, dest_subnode, src_entries[cvs_path], copy_source
436 ).parent_mirror_dir
437 else:
438 # Path is a CVSFile:
439 self._fill_file(
440 symbol, cvs_path in dest_node, src_entries[cvs_path], copy_source
442 # Reread dest_node since the call to _fill_file() might have
443 # made it writable:
444 dest_node = self._mirror.get_current_path(
445 dest_node.cvs_path, dest_node.lod
448 return dest_node
450 def _fill_file(self, symbol, dest_existed, fill_source, parent_source):
451 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
453 Use items from FILL_SOURCE.
455 Fill SYMBOL at path FILL_SOURCE.cvs_path. DEST_NODE is the node
456 of this destination path, or None if the destination does not yet
457 exist. All directories above this path have already been filled
458 as needed. FILL_SOURCE is a FillSource instance describing the
459 item that needs to be copied to the destination.
461 PARENT_SOURCE is the source from which the parent directory was
462 copied, or None if the parent directory was not copied during this
463 commit. We prefer to copy from PARENT_SOURCE, since it typically
464 requires less touching-up. If PARENT_SOURCE is None, then the
465 parent directory was not copied in this commit, so no revision is
466 preferable to any other."""
468 copy_source = fill_source.compute_best_source(parent_source)
470 # Figure out if we shall copy to this destination and delete any
471 # destination path that is in the way.
472 if not dest_existed:
473 # The destination does not exist at all, so it definitely has to
474 # be copied:
475 self.copy_path(
476 fill_source.cvs_path, copy_source.source_lod,
477 symbol, copy_source.opening_revnum
479 elif (parent_source is not None) and (
480 copy_source.source_lod != parent_source.source_lod
481 or copy_source.opening_revnum != parent_source.opening_revnum
483 # The parent path was copied from a different source than we
484 # need to use, so we have to delete the version that was copied
485 # with the parent and then re-copy from the correct source:
486 self.delete_path(fill_source.cvs_path, symbol)
487 self.copy_path(
488 fill_source.cvs_path, copy_source.source_lod,
489 symbol, copy_source.opening_revnum
492 def _prune_extra_entries(
493 self, dest_cvs_path, symbol, dest_node, src_entries
495 """Delete any entries in DEST_NODE that are not in SRC_ENTRIES."""
497 delete_list = [
498 cvs_path
499 for cvs_path in dest_node
500 if cvs_path not in src_entries
503 # Sort the delete list so that the output is in a consistent
504 # order:
505 delete_list.sort()
506 for cvs_path in delete_list:
507 del dest_node[cvs_path]
508 self._invoke_delegates('delete_path', symbol, cvs_path)
510 def add_delegate(self, delegate):
511 """Adds DELEGATE to self._delegates.
513 For every delegate you add, whenever a repository action method is
514 performed, delegate's corresponding repository action method is
515 called. Multiple delegates will be called in the order that they
516 are added. See SVNRepositoryDelegate for more information."""
518 self._delegates.append(delegate)
520 def _invoke_delegates(self, method, *args):
521 """Invoke a method on each delegate.
523 Iterate through each of our delegates, in the order that they were
524 added, and call the delegate's method named METHOD with the
525 arguments in ARGS."""
527 for delegate in self._delegates:
528 getattr(delegate, method)(*args)
530 def process_initial_project_commit(self, svn_commit):
531 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
533 for project in svn_commit.projects:
534 self.initialize_project(project)
536 self.end_commit()
538 def process_primary_commit(self, svn_commit):
539 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
541 # This actually commits CVSRevisions
542 if len(svn_commit.cvs_revs) > 1:
543 plural = "s"
544 else:
545 plural = ""
546 Log().verbose("Committing %d CVSRevision%s"
547 % (len(svn_commit.cvs_revs), plural))
548 for cvs_rev in svn_commit.cvs_revs:
549 if isinstance(cvs_rev, CVSRevisionNoop):
550 pass
552 elif isinstance(cvs_rev, CVSRevisionDelete):
553 self.delete_path(cvs_rev.cvs_file, cvs_rev.lod, Ctx().prune)
555 elif isinstance(cvs_rev, CVSRevisionAdd):
556 self.add_path(cvs_rev)
558 elif isinstance(cvs_rev, CVSRevisionChange):
559 self.change_path(cvs_rev)
561 self.end_commit()
563 def process_post_commit(self, svn_commit):
564 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
566 Log().verbose(
567 'Synchronizing default branch motivated by %d'
568 % (svn_commit.motivating_revnum,)
571 for cvs_rev in svn_commit.cvs_revs:
572 trunk = cvs_rev.cvs_file.project.get_trunk()
573 if isinstance(cvs_rev, CVSRevisionAdd):
574 # Copy from branch to trunk:
575 self.copy_path(
576 cvs_rev.cvs_file, cvs_rev.lod, trunk,
577 svn_commit.motivating_revnum, True
579 elif isinstance(cvs_rev, CVSRevisionChange):
580 # Delete old version of the path on trunk...
581 self.delete_path(cvs_rev.cvs_file, trunk)
582 # ...and copy the new version over from branch:
583 self.copy_path(
584 cvs_rev.cvs_file, cvs_rev.lod, trunk,
585 svn_commit.motivating_revnum, True
587 elif isinstance(cvs_rev, CVSRevisionDelete):
588 # Delete trunk path:
589 self.delete_path(cvs_rev.cvs_file, trunk)
590 elif isinstance(cvs_rev, CVSRevisionNoop):
591 # Do nothing
592 pass
593 else:
594 raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev,))
596 self.end_commit()
598 def process_branch_commit(self, svn_commit):
599 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
600 Log().verbose('Filling branch:', svn_commit.symbol.name)
602 # Get the set of sources for the symbolic name:
603 source_set = get_source_set(
604 svn_commit.symbol,
605 self._symbolings_reader.get_range_map(svn_commit),
608 self.fill_symbol(svn_commit, source_set)
610 self.end_commit()
612 def process_tag_commit(self, svn_commit):
613 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
614 Log().verbose('Filling tag:', svn_commit.symbol.name)
616 # Get the set of sources for the symbolic name:
617 source_set = get_source_set(
618 svn_commit.symbol,
619 self._symbolings_reader.get_range_map(svn_commit),
622 self.fill_symbol(svn_commit, source_set)
624 self.end_commit()
626 def cleanup(self):
627 self._invoke_delegates('finish')
628 self._mirror.close()
629 self._mirror = None
630 Ctx().revision_reader.finish()
631 self._symbolings_reader.close()
632 del self._symbolings_reader
635 class DumpfileOutputOption(SVNOutputOption):
636 """Output the result of the conversion into a dumpfile."""
638 def __init__(self, dumpfile_path, author_transforms=None):
639 SVNOutputOption.__init__(self, author_transforms)
640 self.dumpfile_path = dumpfile_path
642 def check(self):
643 pass
645 def setup(self, svn_rev_count):
646 Log().quiet("Starting Subversion Dumpfile.")
647 SVNOutputOption.setup(self, svn_rev_count)
648 if not Ctx().dry_run:
649 self.add_delegate(
650 DumpstreamDelegate(Ctx().revision_reader, self.dumpfile_path)
654 class RepositoryOutputOption(SVNOutputOption):
655 """Output the result of the conversion into an SVN repository."""
657 def __init__(self, target, author_transforms=None):
658 SVNOutputOption.__init__(self, author_transforms)
659 self.target = target
661 def check(self):
662 if not Ctx().dry_run:
663 # Verify that svnadmin can be executed. The 'help' subcommand
664 # should be harmless.
665 try:
666 check_command_runs([Ctx().svnadmin_executable, 'help'], 'svnadmin')
667 except CommandFailedException, e:
668 raise FatalError(
669 '%s\n'
670 'svnadmin could not be executed. Please ensure that it is\n'
671 'installed and/or use the --svnadmin option.' % (e,))
673 def setup(self, svn_rev_count):
674 Log().quiet("Starting Subversion Repository.")
675 SVNOutputOption.setup(self, svn_rev_count)
676 if not Ctx().dry_run:
677 self.add_delegate(
678 RepositoryDelegate(Ctx().revision_reader, self.target)
682 class NewRepositoryOutputOption(RepositoryOutputOption):
683 """Output the result of the conversion into a new SVN repository."""
685 def __init__(
686 self, target, fs_type=None, bdb_txn_nosync=None, author_transforms=None, create_options=[]
688 RepositoryOutputOption.__init__(self, target, author_transforms)
689 self.bdb_txn_nosync = bdb_txn_nosync
691 # Determine the options to be passed to "svnadmin create":
692 if not fs_type:
693 # User didn't say what kind repository (bdb, fsfs, etc). We
694 # still pass --bdb-txn-nosync. It's a no-op if the default
695 # repository type doesn't support it, but we definitely want it
696 # if BDB is the default.
697 self.create_options = ['--bdb-txn-nosync']
698 elif fs_type == 'bdb':
699 # User explicitly specified bdb.
701 # Since this is a BDB repository, pass --bdb-txn-nosync, because
702 # it gives us a 4-5x speed boost (if cvs2svn is creating the
703 # repository, cvs2svn should be the only program accessing the
704 # svn repository until cvs2svn is done). But we'll turn no-sync
705 # off in self.finish(), unless instructed otherwise.
706 self.create_options = ['--fs-type=bdb', '--bdb-txn-nosync']
707 else:
708 # User specified something other than bdb.
709 self.create_options = ['--fs-type=%s' % fs_type]
711 # Now append the user's explicitly-set create options:
712 self.create_options += create_options
714 def check(self):
715 RepositoryOutputOption.check(self)
716 if not Ctx().dry_run and os.path.exists(self.target):
717 raise FatalError("the svn-repos-path '%s' exists.\n"
718 "Remove it, or pass '--existing-svnrepos'."
719 % self.target)
721 def setup(self, svn_rev_count):
722 Log().normal("Creating new repository '%s'" % (self.target))
723 if Ctx().dry_run:
724 # Do not actually create repository:
725 pass
726 else:
727 call_command([
728 Ctx().svnadmin_executable, 'create',
729 ] + self.create_options + [
730 self.target
733 RepositoryOutputOption.setup(self, svn_rev_count)
735 def cleanup(self):
736 RepositoryOutputOption.cleanup(self)
738 # If this is a BDB repository, and we created the repository, and
739 # --bdb-no-sync wasn't passed, then comment out the DB_TXN_NOSYNC
740 # line in the DB_CONFIG file, because txn syncing should be on by
741 # default in BDB repositories.
743 # We determine if this is a BDB repository by looking for the
744 # DB_CONFIG file, which doesn't exist in FSFS, rather than by
745 # checking self.fs_type. That way this code will Do The Right
746 # Thing in all circumstances.
747 db_config = os.path.join(self.target, "db/DB_CONFIG")
748 if Ctx().dry_run:
749 # Do not change repository:
750 pass
751 elif not self.bdb_txn_nosync and os.path.exists(db_config):
752 no_sync = 'set_flags DB_TXN_NOSYNC\n'
754 contents = open(db_config, 'r').readlines()
755 index = contents.index(no_sync)
756 contents[index] = '# ' + no_sync
757 open(db_config, 'w').writelines(contents)
760 class ExistingRepositoryOutputOption(RepositoryOutputOption):
761 """Output the result of the conversion into an existing SVN repository."""
763 def __init__(self, target, author_transforms=None):
764 RepositoryOutputOption.__init__(self, target, author_transforms)
766 def check(self):
767 RepositoryOutputOption.check(self)
768 if not os.path.isdir(self.target):
769 raise FatalError("the svn-repos-path '%s' is not an "
770 "existing directory." % self.target)