Require all OutputOption classes to have a "name" attribute.
[cvs2svn.git] / cvs2svn_lib / svn_output_option.py
blobf9161007b8fd0430f1e089764e41746e19843738
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 logger
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.svn_dump import LoaderPipe
50 from cvs2svn_lib.output_option import OutputOption
53 class SVNOutputOption(OutputOption):
54 """An OutputOption appropriate for output to Subversion."""
56 name = 'Subversion'
58 class ParentMissingError(Exception):
59 """The parent of a path is missing.
61 Exception raised if an attempt is made to add a path to the
62 repository mirror but the parent's path doesn't exist in the
63 youngest revision of the repository."""
65 pass
67 class ExpectedDirectoryError(Exception):
68 """A file was found where a directory was expected."""
70 pass
72 def __init__(self, author_transforms=None):
73 self._mirror = RepositoryMirror()
75 def to_utf8(s):
76 if isinstance(s, unicode):
77 return s.encode('utf8')
78 else:
79 return s
81 self.author_transforms = {}
82 if author_transforms is not None:
83 for (cvsauthor, name) in author_transforms.iteritems():
84 cvsauthor = to_utf8(cvsauthor)
85 name = to_utf8(name)
86 self.author_transforms[cvsauthor] = name
88 def register_artifacts(self, which_pass):
89 # These artifacts are needed for SymbolingsReader:
90 artifact_manager.register_temp_file_needed(
91 config.SYMBOL_OPENINGS_CLOSINGS_SORTED, which_pass
93 artifact_manager.register_temp_file_needed(
94 config.SYMBOL_OFFSETS_DB, which_pass
97 self._mirror.register_artifacts(which_pass)
98 Ctx().revision_reader.register_artifacts(which_pass)
100 def check_symbols(self, symbol_map):
101 """Check that the paths of all included LODs are set and disjoint."""
103 error_found = False
105 # Check that all included LODs have their base paths set, and
106 # collect the paths into a list:
107 paths = []
108 for lod in symbol_map.itervalues():
109 if isinstance(lod, LineOfDevelopment):
110 if lod.base_path is None:
111 logger.error('%s: No path was set for %r\n' % (error_prefix, lod,))
112 error_found = True
113 else:
114 paths.append(lod.base_path)
116 # Check that the SVN paths of all LODS are disjoint:
117 try:
118 verify_paths_disjoint(*paths)
119 except PathsNotDisjointException, e:
120 logger.error(str(e))
121 error_found = True
123 if error_found:
124 raise FatalException(
125 'Please fix the above errors and restart CollateSymbolsPass'
128 def setup(self, svn_rev_count):
129 self._symbolings_reader = SymbolingsReader()
130 self._mirror.open()
131 self._delegates = []
132 Ctx().revision_reader.start()
133 self.add_delegate(StdoutDelegate(svn_rev_count))
135 def _get_author(self, svn_commit):
136 author = svn_commit.get_author()
137 name = self.author_transforms.get(author, author)
138 return name
140 def _get_revprops(self, svn_commit):
141 """Return the Subversion revprops for this SVNCommit."""
143 return {
144 'svn:author' : self._get_author(svn_commit),
145 'svn:log' : svn_commit.get_log_msg(),
146 'svn:date' : format_date(svn_commit.date),
149 def start_commit(self, revnum, revprops):
150 """Start a new commit."""
152 self._mirror.start_commit(revnum)
153 self._invoke_delegates('start_commit', revnum, revprops)
155 def end_commit(self):
156 """Called at the end of each commit.
158 This method copies the newly created nodes to the on-disk nodes
159 db."""
161 self._mirror.end_commit()
162 self._invoke_delegates('end_commit')
164 def delete_lod(self, lod):
165 """Delete the main path for LOD from the tree.
167 The path must currently exist. Silently refuse to delete trunk
168 paths."""
170 if isinstance(lod, Trunk):
171 # Never delete a Trunk path.
172 return
174 self._mirror.get_current_lod_directory(lod).delete()
175 self._invoke_delegates('delete_lod', lod)
177 def delete_path(self, cvs_path, lod, should_prune=False):
178 """Delete CVS_PATH from LOD."""
180 if cvs_path.parent_directory is None:
181 self.delete_lod(lod)
182 return
184 parent_node = self._mirror.get_current_path(
185 cvs_path.parent_directory, lod
187 del parent_node[cvs_path]
188 self._invoke_delegates('delete_path', lod, cvs_path)
190 if should_prune:
191 while parent_node is not None and len(parent_node) == 0:
192 # A drawback of this code is that we issue a delete for each
193 # path and not just a single delete for the topmost directory
194 # pruned.
195 node = parent_node
196 cvs_path = node.cvs_path
197 if cvs_path.parent_directory is None:
198 parent_node = None
199 self.delete_lod(lod)
200 else:
201 parent_node = node.parent_mirror_dir
202 node.delete()
203 self._invoke_delegates('delete_path', lod, cvs_path)
205 def initialize_project(self, project):
206 """Create the basic structure for PROJECT."""
208 self._invoke_delegates('initialize_project', project)
210 # Don't invoke delegates.
211 self._mirror.add_lod(project.get_trunk())
212 if Ctx().include_empty_directories:
213 self._make_empty_subdirectories(
214 project.get_root_cvs_directory(), project.get_trunk()
217 def change_path(self, cvs_rev):
218 """Register a change in self._youngest for the CVS_REV's svn_path."""
220 # We do not have to update the nodes because our mirror is only
221 # concerned with the presence or absence of paths, and a file
222 # content change does not cause any path changes.
223 self._invoke_delegates('change_path', cvs_rev)
225 def _make_empty_subdirectories(self, cvs_directory, lod):
226 """Make any empty subdirectories of CVS_DIRECTORY in LOD."""
228 for empty_subdirectory_id in cvs_directory.empty_subdirectory_ids:
229 empty_subdirectory = Ctx()._cvs_path_db.get_path(empty_subdirectory_id)
230 # There is no need to record the empty subdirectories in the
231 # mirror, since they live and die with their parent directories.
232 self._invoke_delegates('mkdir', lod, empty_subdirectory)
233 self._make_empty_subdirectories(empty_subdirectory, lod)
235 def _mkdir_p(self, cvs_directory, lod):
236 """Make sure that CVS_DIRECTORY exists in LOD.
238 If not, create it, calling delegates. Return the node for
239 CVS_DIRECTORY."""
241 ancestry = cvs_directory.get_ancestry()
243 try:
244 node = self._mirror.get_current_lod_directory(lod)
245 except KeyError:
246 node = self._mirror.add_lod(lod)
247 self._invoke_delegates('initialize_lod', lod)
248 if ancestry and Ctx().include_empty_directories:
249 self._make_empty_subdirectories(ancestry[0], lod)
251 for sub_path in ancestry[1:]:
252 try:
253 node = node[sub_path]
254 except KeyError:
255 node = node.mkdir(sub_path)
256 self._invoke_delegates('mkdir', lod, sub_path)
257 if Ctx().include_empty_directories:
258 self._make_empty_subdirectories(sub_path, lod)
259 if node is None:
260 raise self.ExpectedDirectoryError(
261 'File found at \'%s\' where directory was expected.' % (sub_path,)
264 return node
266 def add_path(self, cvs_rev):
267 """Add the CVS_REV's svn_path to the repository mirror.
269 Create any missing intermediate paths."""
271 cvs_file = cvs_rev.cvs_file
272 parent_path = cvs_file.parent_directory
273 lod = cvs_rev.lod
274 parent_node = self._mkdir_p(parent_path, lod)
275 parent_node.add_file(cvs_file)
276 self._invoke_delegates('add_path', cvs_rev)
278 def copy_lod(self, src_lod, dest_lod, src_revnum):
279 """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD.
281 In the youngest revision of the repository, the destination LOD
282 *must not* already exist.
284 Return the new node at DEST_LOD. Note that this node is not
285 necessarily writable, though its parent node necessarily is."""
287 node = self._mirror.copy_lod(src_lod, dest_lod, src_revnum)
288 self._invoke_delegates('copy_lod', src_lod, dest_lod, src_revnum)
289 return node
291 def copy_path(
292 self, cvs_path, src_lod, dest_lod, src_revnum, create_parent=False
294 """Copy CVS_PATH from SRC_LOD at SRC_REVNUM to DST_LOD.
296 In the youngest revision of the repository, the destination's
297 parent *must* exist unless CREATE_PARENT is specified. But the
298 destination itself *must not* exist.
300 Return the new node at (CVS_PATH, DEST_LOD), as a
301 CurrentMirrorDirectory."""
303 if cvs_path.parent_directory is None:
304 return self.copy_lod(src_lod, dest_lod, src_revnum)
306 # Get the node of our source, or None if it is a file:
307 src_node = self._mirror.get_old_path(cvs_path, src_lod, src_revnum)
309 # Get the parent path of the destination:
310 if create_parent:
311 dest_parent_node = self._mkdir_p(cvs_path.parent_directory, dest_lod)
312 else:
313 try:
314 dest_parent_node = self._mirror.get_current_path(
315 cvs_path.parent_directory, dest_lod
317 except KeyError:
318 raise self.ParentMissingError(
319 'Attempt to add path \'%s\' to repository mirror, '
320 'but its parent directory doesn\'t exist in the mirror.'
321 % (dest_lod.get_path(cvs_path.cvs_path),)
324 if cvs_path in dest_parent_node:
325 raise PathExistsError(
326 'Attempt to add path \'%s\' to repository mirror '
327 'when it already exists in the mirror.'
328 % (dest_lod.get_path(cvs_path.cvs_path),)
331 dest_parent_node[cvs_path] = src_node
332 self._invoke_delegates(
333 'copy_path',
334 cvs_path, src_lod, dest_lod, src_revnum
337 return dest_parent_node[cvs_path]
339 def fill_symbol(self, svn_symbol_commit, fill_source):
340 """Perform all copies for the CVSSymbols in SVN_SYMBOL_COMMIT.
342 The symbolic name is guaranteed to exist in the Subversion
343 repository by the end of this call, even if there are no paths
344 under it."""
346 symbol = svn_symbol_commit.symbol
348 try:
349 dest_node = self._mirror.get_current_lod_directory(symbol)
350 except KeyError:
351 self._fill_directory(symbol, None, fill_source, None)
352 else:
353 self._fill_directory(symbol, dest_node, fill_source, None)
355 def _fill_directory(self, symbol, dest_node, fill_source, parent_source):
356 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
358 Use items from FILL_SOURCE, and recurse into the child items.
360 Fill SYMBOL starting at the path FILL_SOURCE.cvs_path. DEST_NODE
361 is the node of this destination path, or None if the destination
362 does not yet exist. All directories above this path have already
363 been filled. FILL_SOURCE is a FillSource instance describing the
364 items within a subtree of the repository that still need to be
365 copied to the destination.
367 PARENT_SOURCE is the SVNRevisionRange that was used to copy the
368 parent directory, if it was copied in this commit. We prefer to
369 copy from the same source as was used for the parent, since it
370 typically requires less touching-up. If PARENT_SOURCE is None,
371 then the parent directory was not copied in this commit, so no
372 revision is preferable to any other."""
374 copy_source = fill_source.compute_best_source(parent_source)
376 # Figure out if we shall copy to this destination and delete any
377 # destination path that is in the way.
378 if dest_node is None:
379 # The destination does not exist at all, so it definitely has to
380 # be copied:
381 dest_node = self.copy_path(
382 fill_source.cvs_path, copy_source.source_lod,
383 symbol, copy_source.opening_revnum
385 elif (parent_source is not None) and (
386 copy_source.source_lod != parent_source.source_lod
387 or copy_source.opening_revnum != parent_source.opening_revnum
389 # The parent path was copied from a different source than we
390 # need to use, so we have to delete the version that was copied
391 # with the parent then re-copy from the correct source:
392 self.delete_path(fill_source.cvs_path, symbol)
393 dest_node = self.copy_path(
394 fill_source.cvs_path, copy_source.source_lod,
395 symbol, copy_source.opening_revnum
397 else:
398 copy_source = parent_source
400 # The map {CVSPath : FillSource} of entries within this directory
401 # that need filling:
402 src_entries = fill_source.get_subsource_map()
404 if copy_source is not None:
405 self._prune_extra_entries(
406 fill_source.cvs_path, symbol, dest_node, src_entries
409 return self._cleanup_filled_directory(
410 symbol, dest_node, src_entries, copy_source
413 def _cleanup_filled_directory(
414 self, symbol, dest_node, src_entries, copy_source
416 """The directory at DEST_NODE has been filled and pruned; recurse.
418 Recurse into the SRC_ENTRIES, in alphabetical order. If DEST_NODE
419 was copied in this revision, COPY_SOURCE should indicate where it
420 was copied from; otherwise, COPY_SOURCE should be None."""
422 cvs_paths = src_entries.keys()
423 cvs_paths.sort()
424 for cvs_path in cvs_paths:
425 if isinstance(cvs_path, CVSDirectory):
426 # Path is a CVSDirectory:
427 try:
428 dest_subnode = dest_node[cvs_path]
429 except KeyError:
430 # Path doesn't exist yet; it has to be created:
431 dest_node = self._fill_directory(
432 symbol, None, src_entries[cvs_path], None
433 ).parent_mirror_dir
434 else:
435 # Path already exists, but might have to be cleaned up:
436 dest_node = self._fill_directory(
437 symbol, dest_subnode, src_entries[cvs_path], copy_source
438 ).parent_mirror_dir
439 else:
440 # Path is a CVSFile:
441 self._fill_file(
442 symbol, cvs_path in dest_node, src_entries[cvs_path], copy_source
444 # Reread dest_node since the call to _fill_file() might have
445 # made it writable:
446 dest_node = self._mirror.get_current_path(
447 dest_node.cvs_path, dest_node.lod
450 return dest_node
452 def _fill_file(self, symbol, dest_existed, fill_source, parent_source):
453 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
455 Use items from FILL_SOURCE.
457 Fill SYMBOL at path FILL_SOURCE.cvs_path. DEST_NODE is the node
458 of this destination path, or None if the destination does not yet
459 exist. All directories above this path have already been filled
460 as needed. FILL_SOURCE is a FillSource instance describing the
461 item that needs to be copied to the destination.
463 PARENT_SOURCE is the source from which the parent directory was
464 copied, or None if the parent directory was not copied during this
465 commit. We prefer to copy from PARENT_SOURCE, since it typically
466 requires less touching-up. If PARENT_SOURCE is None, then the
467 parent directory was not copied in this commit, so no revision is
468 preferable to any other."""
470 copy_source = fill_source.compute_best_source(parent_source)
472 # Figure out if we shall copy to this destination and delete any
473 # destination path that is in the way.
474 if not dest_existed:
475 # The destination does not exist at all, so it definitely has to
476 # be copied:
477 self.copy_path(
478 fill_source.cvs_path, copy_source.source_lod,
479 symbol, copy_source.opening_revnum
481 elif (parent_source is not None) and (
482 copy_source.source_lod != parent_source.source_lod
483 or copy_source.opening_revnum != parent_source.opening_revnum
485 # The parent path was copied from a different source than we
486 # need to use, so we have to delete the version that was copied
487 # with the parent and then re-copy from the correct source:
488 self.delete_path(fill_source.cvs_path, symbol)
489 self.copy_path(
490 fill_source.cvs_path, copy_source.source_lod,
491 symbol, copy_source.opening_revnum
494 def _prune_extra_entries(
495 self, dest_cvs_path, symbol, dest_node, src_entries
497 """Delete any entries in DEST_NODE that are not in SRC_ENTRIES."""
499 delete_list = [
500 cvs_path
501 for cvs_path in dest_node
502 if cvs_path not in src_entries
505 # Sort the delete list so that the output is in a consistent
506 # order:
507 delete_list.sort()
508 for cvs_path in delete_list:
509 del dest_node[cvs_path]
510 self._invoke_delegates('delete_path', symbol, cvs_path)
512 def add_delegate(self, delegate):
513 """Adds DELEGATE to self._delegates.
515 For every delegate you add, whenever a repository action method is
516 performed, delegate's corresponding repository action method is
517 called. Multiple delegates will be called in the order that they
518 are added. See SVNRepositoryDelegate for more information."""
520 self._delegates.append(delegate)
522 def _invoke_delegates(self, method, *args):
523 """Invoke a method on each delegate.
525 Iterate through each of our delegates, in the order that they were
526 added, and call the delegate's method named METHOD with the
527 arguments in ARGS."""
529 for delegate in self._delegates:
530 getattr(delegate, method)(*args)
532 def process_initial_project_commit(self, svn_commit):
533 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
535 for project in svn_commit.projects:
536 self.initialize_project(project)
538 self.end_commit()
540 def process_primary_commit(self, svn_commit):
541 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
543 # This actually commits CVSRevisions
544 if len(svn_commit.cvs_revs) > 1:
545 plural = "s"
546 else:
547 plural = ""
548 logger.verbose("Committing %d CVSRevision%s"
549 % (len(svn_commit.cvs_revs), plural))
550 for cvs_rev in svn_commit.cvs_revs:
551 if isinstance(cvs_rev, CVSRevisionNoop):
552 pass
554 elif isinstance(cvs_rev, CVSRevisionDelete):
555 self.delete_path(cvs_rev.cvs_file, cvs_rev.lod, Ctx().prune)
557 elif isinstance(cvs_rev, CVSRevisionAdd):
558 self.add_path(cvs_rev)
560 elif isinstance(cvs_rev, CVSRevisionChange):
561 self.change_path(cvs_rev)
563 self.end_commit()
565 def process_post_commit(self, svn_commit):
566 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
568 logger.verbose(
569 'Synchronizing default branch motivated by %d'
570 % (svn_commit.motivating_revnum,)
573 for cvs_rev in svn_commit.cvs_revs:
574 trunk = cvs_rev.cvs_file.project.get_trunk()
575 if isinstance(cvs_rev, CVSRevisionAdd):
576 # Copy from branch to trunk:
577 self.copy_path(
578 cvs_rev.cvs_file, cvs_rev.lod, trunk,
579 svn_commit.motivating_revnum, True
581 elif isinstance(cvs_rev, CVSRevisionChange):
582 # Delete old version of the path on trunk...
583 self.delete_path(cvs_rev.cvs_file, trunk)
584 # ...and copy the new version over from branch:
585 self.copy_path(
586 cvs_rev.cvs_file, cvs_rev.lod, trunk,
587 svn_commit.motivating_revnum, True
589 elif isinstance(cvs_rev, CVSRevisionDelete):
590 # Delete trunk path:
591 self.delete_path(cvs_rev.cvs_file, trunk)
592 elif isinstance(cvs_rev, CVSRevisionNoop):
593 # Do nothing
594 pass
595 else:
596 raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev,))
598 self.end_commit()
600 def process_branch_commit(self, svn_commit):
601 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
602 logger.verbose('Filling branch:', svn_commit.symbol.name)
604 # Get the set of sources for the symbolic name:
605 source_set = get_source_set(
606 svn_commit.symbol,
607 self._symbolings_reader.get_range_map(svn_commit),
610 self.fill_symbol(svn_commit, source_set)
612 self.end_commit()
614 def process_tag_commit(self, svn_commit):
615 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
616 logger.verbose('Filling tag:', svn_commit.symbol.name)
618 # Get the set of sources for the symbolic name:
619 source_set = get_source_set(
620 svn_commit.symbol,
621 self._symbolings_reader.get_range_map(svn_commit),
624 self.fill_symbol(svn_commit, source_set)
626 self.end_commit()
628 def cleanup(self):
629 self._invoke_delegates('finish')
630 self._mirror.close()
631 self._mirror = None
632 Ctx().revision_reader.finish()
633 self._symbolings_reader.close()
634 del self._symbolings_reader
637 class DumpfileOutputOption(SVNOutputOption):
638 """Output the result of the conversion into a dumpfile."""
640 def __init__(self, dumpfile_path, author_transforms=None):
641 SVNOutputOption.__init__(self, author_transforms)
642 self.dumpfile_path = dumpfile_path
644 def check(self):
645 pass
647 def setup(self, svn_rev_count):
648 logger.quiet("Starting Subversion Dumpfile.")
649 SVNOutputOption.setup(self, svn_rev_count)
650 if not Ctx().dry_run:
651 self.add_delegate(
652 DumpstreamDelegate(
653 Ctx().revision_reader, open(self.dumpfile_path, 'wb')
658 class RepositoryOutputOption(SVNOutputOption):
659 """Output the result of the conversion into an SVN repository."""
661 def __init__(self, target, author_transforms=None):
662 SVNOutputOption.__init__(self, author_transforms)
663 self.target = target
665 def check(self):
666 if not Ctx().dry_run:
667 # Verify that svnadmin can be executed. The 'help' subcommand
668 # should be harmless.
669 try:
670 check_command_runs([Ctx().svnadmin_executable, 'help'], 'svnadmin')
671 except CommandFailedException, e:
672 raise FatalError(
673 '%s\n'
674 'svnadmin could not be executed. Please ensure that it is\n'
675 'installed and/or use the --svnadmin option.' % (e,))
677 def setup(self, svn_rev_count):
678 logger.quiet("Starting Subversion Repository.")
679 SVNOutputOption.setup(self, svn_rev_count)
680 if not Ctx().dry_run:
681 self.add_delegate(
682 DumpstreamDelegate(Ctx().revision_reader, LoaderPipe(self.target))
686 class NewRepositoryOutputOption(RepositoryOutputOption):
687 """Output the result of the conversion into a new SVN repository."""
689 def __init__(
690 self, target,
691 fs_type=None, bdb_txn_nosync=None,
692 author_transforms=None, create_options=[],
694 RepositoryOutputOption.__init__(self, target, author_transforms)
695 self.bdb_txn_nosync = bdb_txn_nosync
697 # Determine the options to be passed to "svnadmin create":
698 if not fs_type:
699 # User didn't say what kind repository (bdb, fsfs, etc). We
700 # still pass --bdb-txn-nosync. It's a no-op if the default
701 # repository type doesn't support it, but we definitely want it
702 # if BDB is the default.
703 self.create_options = ['--bdb-txn-nosync']
704 elif fs_type == 'bdb':
705 # User explicitly specified bdb.
707 # Since this is a BDB repository, pass --bdb-txn-nosync, because
708 # it gives us a 4-5x speed boost (if cvs2svn is creating the
709 # repository, cvs2svn should be the only program accessing the
710 # svn repository until cvs2svn is done). But we'll turn no-sync
711 # off in self.finish(), unless instructed otherwise.
712 self.create_options = ['--fs-type=bdb', '--bdb-txn-nosync']
713 else:
714 # User specified something other than bdb.
715 self.create_options = ['--fs-type=%s' % fs_type]
717 # Now append the user's explicitly-set create options:
718 self.create_options += create_options
720 def check(self):
721 RepositoryOutputOption.check(self)
722 if not Ctx().dry_run and os.path.exists(self.target):
723 raise FatalError("the svn-repos-path '%s' exists.\n"
724 "Remove it, or pass '--existing-svnrepos'."
725 % self.target)
727 def setup(self, svn_rev_count):
728 logger.normal("Creating new repository '%s'" % (self.target))
729 if Ctx().dry_run:
730 # Do not actually create repository:
731 pass
732 else:
733 call_command([
734 Ctx().svnadmin_executable, 'create',
735 ] + self.create_options + [
736 self.target
739 RepositoryOutputOption.setup(self, svn_rev_count)
741 def cleanup(self):
742 RepositoryOutputOption.cleanup(self)
744 # If this is a BDB repository, and we created the repository, and
745 # --bdb-no-sync wasn't passed, then comment out the DB_TXN_NOSYNC
746 # line in the DB_CONFIG file, because txn syncing should be on by
747 # default in BDB repositories.
749 # We determine if this is a BDB repository by looking for the
750 # DB_CONFIG file, which doesn't exist in FSFS, rather than by
751 # checking self.fs_type. That way this code will Do The Right
752 # Thing in all circumstances.
753 db_config = os.path.join(self.target, "db/DB_CONFIG")
754 if Ctx().dry_run:
755 # Do not change repository:
756 pass
757 elif not self.bdb_txn_nosync and os.path.exists(db_config):
758 no_sync = 'set_flags DB_TXN_NOSYNC\n'
760 contents = open(db_config, 'r').readlines()
761 index = contents.index(no_sync)
762 contents[index] = '# ' + no_sync
763 open(db_config, 'w').writelines(contents)
766 class ExistingRepositoryOutputOption(RepositoryOutputOption):
767 """Output the result of the conversion into an existing SVN repository."""
769 def __init__(self, target, author_transforms=None):
770 RepositoryOutputOption.__init__(self, target, author_transforms)
772 def check(self):
773 RepositoryOutputOption.check(self)
774 if not os.path.isdir(self.target):
775 raise FatalError("the svn-repos-path '%s' is not an "
776 "existing directory." % self.target)