Extract functions generate_edits_from_blocks() and write_edits().
[cvs2svn.git] / cvs2svn_lib / svn_output_option.py
blob2cb25efd21a6b838f5431cbe49642826d6ed99d2
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.svn_commit_item import SVNCommitItem
46 from cvs2svn_lib.openings_closings import SymbolingsReader
47 from cvs2svn_lib.fill_source import get_source_set
48 from cvs2svn_lib.stdout_delegate import StdoutDelegate
49 from cvs2svn_lib.dumpfile_delegate import DumpfileDelegate
50 from cvs2svn_lib.repository_delegate import RepositoryDelegate
51 from cvs2svn_lib.output_option import OutputOption
54 class SVNOutputOption(OutputOption):
55 """An OutputOption appropriate for output to Subversion."""
57 class ParentMissingError(Exception):
58 """The parent of a path is missing.
60 Exception raised if an attempt is made to add a path to the
61 repository mirror but the parent's path doesn't exist in the
62 youngest revision of the repository."""
64 pass
66 class ExpectedDirectoryError(Exception):
67 """A file was found where a directory was expected."""
69 pass
71 def __init__(self, author_transforms=None):
72 self._mirror = RepositoryMirror()
74 def to_utf8(s):
75 if isinstance(s, unicode):
76 return s.encode('utf8')
77 else:
78 return s
80 self.author_transforms = {}
81 if author_transforms is not None:
82 for (cvsauthor, name) in author_transforms.iteritems():
83 cvsauthor = to_utf8(cvsauthor)
84 name = to_utf8(name)
85 self.author_transforms[cvsauthor] = name
87 def register_artifacts(self, which_pass):
88 # These artifacts are needed for SymbolingsReader:
89 artifact_manager.register_temp_file_needed(
90 config.SYMBOL_OPENINGS_CLOSINGS_SORTED, which_pass
92 artifact_manager.register_temp_file_needed(
93 config.SYMBOL_OFFSETS_DB, which_pass
96 self._mirror.register_artifacts(which_pass)
97 Ctx().revision_reader.register_artifacts(which_pass)
99 def check_symbols(self, symbol_map):
100 """Check that the paths of all included LODs are set and disjoint."""
102 error_found = False
104 # Check that all included LODs have their base paths set, and
105 # collect the paths into a list:
106 paths = []
107 for lod in symbol_map.itervalues():
108 if isinstance(lod, LineOfDevelopment):
109 if lod.base_path is None:
110 Log().error('%s: No path was set for %r\n' % (error_prefix, lod,))
111 error_found = True
112 else:
113 paths.append(lod.base_path)
115 # Check that the SVN paths of all LODS are disjoint:
116 try:
117 verify_paths_disjoint(*paths)
118 except PathsNotDisjointException, e:
119 Log().error(str(e))
120 error_found = True
122 if error_found:
123 raise FatalException(
124 'Please fix the above errors and restart CollateSymbolsPass'
127 def setup(self, svn_rev_count):
128 self._symbolings_reader = SymbolingsReader()
129 self._mirror.open()
130 self._delegates = []
131 Ctx().revision_reader.start()
132 self.add_delegate(StdoutDelegate(svn_rev_count))
134 def _get_author(self, svn_commit):
135 author = svn_commit.get_author()
136 name = self.author_transforms.get(author, author)
137 return name
139 def _get_revprops(self, svn_commit):
140 """Return the Subversion revprops for this SVNCommit."""
142 return {
143 'svn:author' : self._get_author(svn_commit),
144 'svn:log' : svn_commit.get_log_msg(),
145 'svn:date' : format_date(svn_commit.date),
148 def start_commit(self, revnum, revprops):
149 """Start a new commit."""
151 self._mirror.start_commit(revnum)
152 self._invoke_delegates('start_commit', revnum, revprops)
154 def end_commit(self):
155 """Called at the end of each commit.
157 This method copies the newly created nodes to the on-disk nodes
158 db."""
160 self._mirror.end_commit()
161 self._invoke_delegates('end_commit')
163 def delete_lod(self, lod):
164 """Delete the main path for LOD from the tree.
166 The path must currently exist. Silently refuse to delete trunk
167 paths."""
169 if isinstance(lod, Trunk):
170 # Never delete a Trunk path.
171 return
173 self._mirror.get_current_lod_directory(lod).delete()
174 self._invoke_delegates('delete_lod', lod)
176 def delete_path(self, cvs_path, lod, should_prune=False):
177 """Delete CVS_PATH from LOD."""
179 if cvs_path.parent_directory is None:
180 self.delete_lod(lod)
181 return
183 parent_node = self._mirror.get_current_path(
184 cvs_path.parent_directory, lod
186 del parent_node[cvs_path]
187 self._invoke_delegates('delete_path', lod, cvs_path)
189 if should_prune:
190 while parent_node is not None and len(parent_node) == 0:
191 # A drawback of this code is that we issue a delete for each
192 # path and not just a single delete for the topmost directory
193 # pruned.
194 node = parent_node
195 cvs_path = node.cvs_path
196 if cvs_path.parent_directory is None:
197 parent_node = None
198 self.delete_lod(lod)
199 else:
200 parent_node = node.parent_mirror_dir
201 node.delete()
202 self._invoke_delegates('delete_path', lod, cvs_path)
204 def initialize_project(self, project):
205 """Create the basic structure for PROJECT."""
207 self._invoke_delegates('initialize_project', project)
209 # Don't invoke delegates.
210 self._mirror.add_lod(project.get_trunk())
211 if Ctx().include_empty_directories:
212 self._make_empty_subdirectories(
213 project.get_root_cvs_directory(), project.get_trunk()
216 def change_path(self, cvs_rev):
217 """Register a change in self._youngest for the CVS_REV's svn_path."""
219 # We do not have to update the nodes because our mirror is only
220 # concerned with the presence or absence of paths, and a file
221 # content change does not cause any path changes.
222 self._invoke_delegates('change_path', SVNCommitItem(cvs_rev, False))
224 def _make_empty_subdirectories(self, cvs_directory, lod):
225 """Make any empty subdirectories of CVS_DIRECTORY in LOD."""
227 for empty_subdirectory_id in cvs_directory.empty_subdirectory_ids:
228 empty_subdirectory = Ctx()._cvs_path_db.get_path(empty_subdirectory_id)
229 # There is no need to record the empty subdirectories in the
230 # mirror, since they live and die with their parent directories.
231 self._invoke_delegates('mkdir', lod, empty_subdirectory)
232 self._make_empty_subdirectories(empty_subdirectory, lod)
234 def _mkdir_p(self, cvs_directory, lod):
235 """Make sure that CVS_DIRECTORY exists in LOD.
237 If not, create it, calling delegates. Return the node for
238 CVS_DIRECTORY."""
240 ancestry = cvs_directory.get_ancestry()
242 try:
243 node = self._mirror.get_current_lod_directory(lod)
244 except KeyError:
245 node = self._mirror.add_lod(lod)
246 self._invoke_delegates('initialize_lod', lod)
247 if ancestry and Ctx().include_empty_directories:
248 self._make_empty_subdirectories(ancestry[0], lod)
250 for sub_path in ancestry[1:]:
251 try:
252 node = node[sub_path]
253 except KeyError:
254 node = node.mkdir(sub_path)
255 self._invoke_delegates('mkdir', lod, sub_path)
256 if Ctx().include_empty_directories:
257 self._make_empty_subdirectories(sub_path, lod)
258 if node is None:
259 raise self.ExpectedDirectoryError(
260 'File found at \'%s\' where directory was expected.' % (sub_path,)
263 return node
265 def add_path(self, cvs_rev):
266 """Add the CVS_REV's svn_path to the repository mirror.
268 Create any missing intermediate paths."""
270 cvs_file = cvs_rev.cvs_file
271 parent_path = cvs_file.parent_directory
272 lod = cvs_rev.lod
273 parent_node = self._mkdir_p(parent_path, lod)
274 parent_node.add_file(cvs_file)
275 self._invoke_delegates('add_path', SVNCommitItem(cvs_rev, True))
277 def copy_lod(self, src_lod, dest_lod, src_revnum):
278 """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD.
280 In the youngest revision of the repository, the destination LOD
281 *must not* already exist.
283 Return the new node at DEST_LOD. Note that this node is not
284 necessarily writable, though its parent node necessarily is."""
286 node = self._mirror.copy_lod(src_lod, dest_lod, src_revnum)
287 self._invoke_delegates('copy_lod', src_lod, dest_lod, src_revnum)
288 return node
290 def copy_path(
291 self, cvs_path, src_lod, dest_lod, src_revnum, create_parent=False
293 """Copy CVS_PATH from SRC_LOD at SRC_REVNUM to DST_LOD.
295 In the youngest revision of the repository, the destination's
296 parent *must* exist unless CREATE_PARENT is specified. But the
297 destination itself *must not* exist.
299 Return the new node at (CVS_PATH, DEST_LOD), as a
300 CurrentMirrorDirectory."""
302 if cvs_path.parent_directory is None:
303 return self.copy_lod(src_lod, dest_lod, src_revnum)
305 # Get the node of our source, or None if it is a file:
306 src_node = self._mirror.get_old_path(cvs_path, src_lod, src_revnum)
308 # Get the parent path of the destination:
309 if create_parent:
310 dest_parent_node = self._mkdir_p(cvs_path.parent_directory, dest_lod)
311 else:
312 try:
313 dest_parent_node = self._mirror.get_current_path(
314 cvs_path.parent_directory, dest_lod
316 except KeyError:
317 raise self.ParentMissingError(
318 'Attempt to add path \'%s\' to repository mirror, '
319 'but its parent directory doesn\'t exist in the mirror.'
320 % (dest_lod.get_path(cvs_path.cvs_path),)
323 if cvs_path in dest_parent_node:
324 raise PathExistsError(
325 'Attempt to add path \'%s\' to repository mirror '
326 'when it already exists in the mirror.'
327 % (dest_lod.get_path(cvs_path.cvs_path),)
330 dest_parent_node[cvs_path] = src_node
331 self._invoke_delegates(
332 'copy_path',
333 cvs_path, src_lod, dest_lod, src_revnum
336 return dest_parent_node[cvs_path]
338 def fill_symbol(self, svn_symbol_commit, fill_source):
339 """Perform all copies for the CVSSymbols in SVN_SYMBOL_COMMIT.
341 The symbolic name is guaranteed to exist in the Subversion
342 repository by the end of this call, even if there are no paths
343 under it."""
345 symbol = svn_symbol_commit.symbol
347 try:
348 dest_node = self._mirror.get_current_lod_directory(symbol)
349 except KeyError:
350 self._fill_directory(symbol, None, fill_source, None)
351 else:
352 self._fill_directory(symbol, dest_node, fill_source, None)
354 def _fill_directory(self, symbol, dest_node, fill_source, parent_source):
355 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
357 Use items from FILL_SOURCE, and recurse into the child items.
359 Fill SYMBOL starting at the path FILL_SOURCE.cvs_path. DEST_NODE
360 is the node of this destination path, or None if the destination
361 does not yet exist. All directories above this path have already
362 been filled. FILL_SOURCE is a FillSource instance describing the
363 items within a subtree of the repository that still need to be
364 copied to the destination.
366 PARENT_SOURCE is the SVNRevisionRange that was used to copy the
367 parent directory, if it was copied in this commit. We prefer to
368 copy from the same source as was used for the parent, since it
369 typically requires less touching-up. If PARENT_SOURCE is None,
370 then the parent directory was not copied in this commit, so no
371 revision is preferable to any other."""
373 copy_source = fill_source.compute_best_source(parent_source)
375 # Figure out if we shall copy to this destination and delete any
376 # destination path that is in the way.
377 if dest_node is None:
378 # The destination does not exist at all, so it definitely has to
379 # be copied:
380 dest_node = self.copy_path(
381 fill_source.cvs_path, copy_source.source_lod,
382 symbol, copy_source.opening_revnum
384 elif (parent_source is not None) and (
385 copy_source.source_lod != parent_source.source_lod
386 or copy_source.opening_revnum != parent_source.opening_revnum
388 # The parent path was copied from a different source than we
389 # need to use, so we have to delete the version that was copied
390 # with the parent then re-copy from the correct source:
391 self.delete_path(fill_source.cvs_path, symbol)
392 dest_node = self.copy_path(
393 fill_source.cvs_path, copy_source.source_lod,
394 symbol, copy_source.opening_revnum
396 else:
397 copy_source = parent_source
399 # The map {CVSPath : FillSource} of entries within this directory
400 # that need filling:
401 src_entries = fill_source.get_subsource_map()
403 if copy_source is not None:
404 self._prune_extra_entries(
405 fill_source.cvs_path, symbol, dest_node, src_entries
408 return self._cleanup_filled_directory(
409 symbol, dest_node, src_entries, copy_source
412 def _cleanup_filled_directory(
413 self, symbol, dest_node, src_entries, copy_source
415 """The directory at DEST_NODE has been filled and pruned; recurse.
417 Recurse into the SRC_ENTRIES, in alphabetical order. If DEST_NODE
418 was copied in this revision, COPY_SOURCE should indicate where it
419 was copied from; otherwise, COPY_SOURCE should be None."""
421 cvs_paths = src_entries.keys()
422 cvs_paths.sort()
423 for cvs_path in cvs_paths:
424 if isinstance(cvs_path, CVSDirectory):
425 # Path is a CVSDirectory:
426 try:
427 dest_subnode = dest_node[cvs_path]
428 except KeyError:
429 # Path doesn't exist yet; it has to be created:
430 dest_node = self._fill_directory(
431 symbol, None, src_entries[cvs_path], None
432 ).parent_mirror_dir
433 else:
434 # Path already exists, but might have to be cleaned up:
435 dest_node = self._fill_directory(
436 symbol, dest_subnode, src_entries[cvs_path], copy_source
437 ).parent_mirror_dir
438 else:
439 # Path is a CVSFile:
440 self._fill_file(
441 symbol, cvs_path in dest_node, src_entries[cvs_path], copy_source
443 # Reread dest_node since the call to _fill_file() might have
444 # made it writable:
445 dest_node = self._mirror.get_current_path(
446 dest_node.cvs_path, dest_node.lod
449 return dest_node
451 def _fill_file(self, symbol, dest_existed, fill_source, parent_source):
452 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
454 Use items from FILL_SOURCE.
456 Fill SYMBOL at path FILL_SOURCE.cvs_path. DEST_NODE is the node
457 of this destination path, or None if the destination does not yet
458 exist. All directories above this path have already been filled
459 as needed. FILL_SOURCE is a FillSource instance describing the
460 item that needs to be copied to the destination.
462 PARENT_SOURCE is the source from which the parent directory was
463 copied, or None if the parent directory was not copied during this
464 commit. We prefer to copy from PARENT_SOURCE, since it typically
465 requires less touching-up. If PARENT_SOURCE is None, then the
466 parent directory was not copied in this commit, so no revision is
467 preferable to any other."""
469 copy_source = fill_source.compute_best_source(parent_source)
471 # Figure out if we shall copy to this destination and delete any
472 # destination path that is in the way.
473 if not dest_existed:
474 # The destination does not exist at all, so it definitely has to
475 # be copied:
476 self.copy_path(
477 fill_source.cvs_path, copy_source.source_lod,
478 symbol, copy_source.opening_revnum
480 elif (parent_source is not None) and (
481 copy_source.source_lod != parent_source.source_lod
482 or copy_source.opening_revnum != parent_source.opening_revnum
484 # The parent path was copied from a different source than we
485 # need to use, so we have to delete the version that was copied
486 # with the parent and then re-copy from the correct source:
487 self.delete_path(fill_source.cvs_path, symbol)
488 self.copy_path(
489 fill_source.cvs_path, copy_source.source_lod,
490 symbol, copy_source.opening_revnum
493 def _prune_extra_entries(
494 self, dest_cvs_path, symbol, dest_node, src_entries
496 """Delete any entries in DEST_NODE that are not in SRC_ENTRIES."""
498 delete_list = [
499 cvs_path
500 for cvs_path in dest_node
501 if cvs_path not in src_entries
504 # Sort the delete list so that the output is in a consistent
505 # order:
506 delete_list.sort()
507 for cvs_path in delete_list:
508 del dest_node[cvs_path]
509 self._invoke_delegates('delete_path', symbol, cvs_path)
511 def add_delegate(self, delegate):
512 """Adds DELEGATE to self._delegates.
514 For every delegate you add, whenever a repository action method is
515 performed, delegate's corresponding repository action method is
516 called. Multiple delegates will be called in the order that they
517 are added. See SVNRepositoryDelegate for more information."""
519 self._delegates.append(delegate)
521 def _invoke_delegates(self, method, *args):
522 """Invoke a method on each delegate.
524 Iterate through each of our delegates, in the order that they were
525 added, and call the delegate's method named METHOD with the
526 arguments in ARGS."""
528 for delegate in self._delegates:
529 getattr(delegate, method)(*args)
531 def process_initial_project_commit(self, svn_commit):
532 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
534 for project in svn_commit.projects:
535 self.initialize_project(project)
537 self.end_commit()
539 def process_primary_commit(self, svn_commit):
540 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
542 # This actually commits CVSRevisions
543 if len(svn_commit.cvs_revs) > 1:
544 plural = "s"
545 else:
546 plural = ""
547 Log().verbose("Committing %d CVSRevision%s"
548 % (len(svn_commit.cvs_revs), plural))
549 for cvs_rev in svn_commit.cvs_revs:
550 if isinstance(cvs_rev, CVSRevisionNoop):
551 pass
553 elif isinstance(cvs_rev, CVSRevisionDelete):
554 self.delete_path(cvs_rev.cvs_file, cvs_rev.lod, Ctx().prune)
556 elif isinstance(cvs_rev, CVSRevisionAdd):
557 self.add_path(cvs_rev)
559 elif isinstance(cvs_rev, CVSRevisionChange):
560 self.change_path(cvs_rev)
562 self.end_commit()
564 def process_post_commit(self, svn_commit):
565 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
567 Log().verbose(
568 'Synchronizing default branch motivated by %d'
569 % (svn_commit.motivating_revnum,)
572 for cvs_rev in svn_commit.cvs_revs:
573 trunk = cvs_rev.cvs_file.project.get_trunk()
574 if isinstance(cvs_rev, CVSRevisionAdd):
575 # Copy from branch to trunk:
576 self.copy_path(
577 cvs_rev.cvs_file, cvs_rev.lod, trunk,
578 svn_commit.motivating_revnum, True
580 elif isinstance(cvs_rev, CVSRevisionChange):
581 # Delete old version of the path on trunk...
582 self.delete_path(cvs_rev.cvs_file, trunk)
583 # ...and copy the new version over from branch:
584 self.copy_path(
585 cvs_rev.cvs_file, cvs_rev.lod, trunk,
586 svn_commit.motivating_revnum, True
588 elif isinstance(cvs_rev, CVSRevisionDelete):
589 # Delete trunk path:
590 self.delete_path(cvs_rev.cvs_file, trunk)
591 elif isinstance(cvs_rev, CVSRevisionNoop):
592 # Do nothing
593 pass
594 else:
595 raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev,))
597 self.end_commit()
599 def process_branch_commit(self, svn_commit):
600 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
601 Log().verbose('Filling branch:', svn_commit.symbol.name)
603 # Get the set of sources for the symbolic name:
604 source_set = get_source_set(
605 svn_commit.symbol,
606 self._symbolings_reader.get_range_map(svn_commit),
609 self.fill_symbol(svn_commit, source_set)
611 self.end_commit()
613 def process_tag_commit(self, svn_commit):
614 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
615 Log().verbose('Filling tag:', svn_commit.symbol.name)
617 # Get the set of sources for the symbolic name:
618 source_set = get_source_set(
619 svn_commit.symbol,
620 self._symbolings_reader.get_range_map(svn_commit),
623 self.fill_symbol(svn_commit, source_set)
625 self.end_commit()
627 def cleanup(self):
628 self._invoke_delegates('finish')
629 self._mirror.close()
630 self._mirror = None
631 Ctx().revision_reader.finish()
632 self._symbolings_reader.close()
633 del self._symbolings_reader
636 class DumpfileOutputOption(SVNOutputOption):
637 """Output the result of the conversion into a dumpfile."""
639 def __init__(self, dumpfile_path, author_transforms=None):
640 SVNOutputOption.__init__(self, author_transforms)
641 self.dumpfile_path = dumpfile_path
643 def check(self):
644 pass
646 def setup(self, svn_rev_count):
647 Log().quiet("Starting Subversion Dumpfile.")
648 SVNOutputOption.setup(self, svn_rev_count)
649 if not Ctx().dry_run:
650 self.add_delegate(
651 DumpfileDelegate(Ctx().revision_reader, self.dumpfile_path)
655 class RepositoryOutputOption(SVNOutputOption):
656 """Output the result of the conversion into an SVN repository."""
658 def __init__(self, target, author_transforms=None):
659 SVNOutputOption.__init__(self, author_transforms)
660 self.target = target
662 def check(self):
663 if not Ctx().dry_run:
664 # Verify that svnadmin can be executed. The 'help' subcommand
665 # should be harmless.
666 try:
667 check_command_runs([Ctx().svnadmin_executable, 'help'], 'svnadmin')
668 except CommandFailedException, e:
669 raise FatalError(
670 '%s\n'
671 'svnadmin could not be executed. Please ensure that it is\n'
672 'installed and/or use the --svnadmin option.' % (e,))
674 def setup(self, svn_rev_count):
675 Log().quiet("Starting Subversion Repository.")
676 SVNOutputOption.setup(self, svn_rev_count)
677 if not Ctx().dry_run:
678 self.add_delegate(
679 RepositoryDelegate(Ctx().revision_reader, self.target)
683 class NewRepositoryOutputOption(RepositoryOutputOption):
684 """Output the result of the conversion into a new SVN repository."""
686 def __init__(
687 self, target, fs_type=None, bdb_txn_nosync=None, author_transforms=None, create_options=[]
689 RepositoryOutputOption.__init__(self, target, author_transforms)
690 self.bdb_txn_nosync = bdb_txn_nosync
692 # Determine the options to be passed to "svnadmin create":
693 if not fs_type:
694 # User didn't say what kind repository (bdb, fsfs, etc). We
695 # still pass --bdb-txn-nosync. It's a no-op if the default
696 # repository type doesn't support it, but we definitely want it
697 # if BDB is the default.
698 self.create_options = ['--bdb-txn-nosync']
699 elif fs_type == 'bdb':
700 # User explicitly specified bdb.
702 # Since this is a BDB repository, pass --bdb-txn-nosync, because
703 # it gives us a 4-5x speed boost (if cvs2svn is creating the
704 # repository, cvs2svn should be the only program accessing the
705 # svn repository until cvs2svn is done). But we'll turn no-sync
706 # off in self.finish(), unless instructed otherwise.
707 self.create_options = ['--fs-type=bdb', '--bdb-txn-nosync']
708 else:
709 # User specified something other than bdb.
710 self.create_options = ['--fs-type=%s' % fs_type]
712 # Now append the user's explicitly-set create options:
713 self.create_options += create_options
715 def check(self):
716 RepositoryOutputOption.check(self)
717 if not Ctx().dry_run and os.path.exists(self.target):
718 raise FatalError("the svn-repos-path '%s' exists.\n"
719 "Remove it, or pass '--existing-svnrepos'."
720 % self.target)
722 def setup(self, svn_rev_count):
723 Log().normal("Creating new repository '%s'" % (self.target))
724 if Ctx().dry_run:
725 # Do not actually create repository:
726 pass
727 else:
728 call_command([
729 Ctx().svnadmin_executable, 'create',
730 ] + self.create_options + [
731 self.target
734 RepositoryOutputOption.setup(self, svn_rev_count)
736 def cleanup(self):
737 RepositoryOutputOption.cleanup(self)
739 # If this is a BDB repository, and we created the repository, and
740 # --bdb-no-sync wasn't passed, then comment out the DB_TXN_NOSYNC
741 # line in the DB_CONFIG file, because txn syncing should be on by
742 # default in BDB repositories.
744 # We determine if this is a BDB repository by looking for the
745 # DB_CONFIG file, which doesn't exist in FSFS, rather than by
746 # checking self.fs_type. That way this code will Do The Right
747 # Thing in all circumstances.
748 db_config = os.path.join(self.target, "db/DB_CONFIG")
749 if Ctx().dry_run:
750 # Do not change repository:
751 pass
752 elif not self.bdb_txn_nosync and os.path.exists(db_config):
753 no_sync = 'set_flags DB_TXN_NOSYNC\n'
755 contents = open(db_config, 'r').readlines()
756 index = contents.index(no_sync)
757 contents[index] = '# ' + no_sync
758 open(db_config, 'w').writelines(contents)
761 class ExistingRepositoryOutputOption(RepositoryOutputOption):
762 """Output the result of the conversion into an existing SVN repository."""
764 def __init__(self, target, author_transforms=None):
765 RepositoryOutputOption.__init__(self, target, author_transforms)
767 def check(self):
768 RepositoryOutputOption.check(self)
769 if not os.path.isdir(self.target):
770 raise FatalError("the svn-repos-path '%s' is not an "
771 "existing directory." % self.target)