Move definitions of generate_ignores() higher in the file.
[cvs2svn.git] / cvs2svn_lib / svn_output_option.py
blob7eb417da8e093af970fd15f4141f6647cdf63f11
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
21 import re
23 from cvs2svn_lib import config
24 from cvs2svn_lib.common import InternalError
25 from cvs2svn_lib.common import FatalError
26 from cvs2svn_lib.common import FatalException
27 from cvs2svn_lib.common import error_prefix
28 from cvs2svn_lib.common import format_date
29 from cvs2svn_lib.common import IllegalSVNPathError
30 from cvs2svn_lib.common import PathsNotDisjointException
31 from cvs2svn_lib.common import verify_paths_disjoint
32 from cvs2svn_lib.log import logger
33 from cvs2svn_lib.context import Ctx
34 from cvs2svn_lib.artifact_manager import artifact_manager
35 from cvs2svn_lib.process import CommandFailedException
36 from cvs2svn_lib.process import check_command_runs
37 from cvs2svn_lib.process import call_command
38 from cvs2svn_lib.cvs_path import CVSDirectory
39 from cvs2svn_lib.symbol import Trunk
40 from cvs2svn_lib.symbol import LineOfDevelopment
41 from cvs2svn_lib.cvs_item import CVSRevisionAdd
42 from cvs2svn_lib.cvs_item import CVSRevisionChange
43 from cvs2svn_lib.cvs_item import CVSRevisionDelete
44 from cvs2svn_lib.cvs_item import CVSRevisionNoop
45 from cvs2svn_lib.repository_mirror import RepositoryMirror
46 from cvs2svn_lib.repository_mirror import PathExistsError
47 from cvs2svn_lib.openings_closings import SymbolingsReader
48 from cvs2svn_lib.fill_source import get_source_set
49 from cvs2svn_lib.svn_dump import DumpstreamDelegate
50 from cvs2svn_lib.svn_dump import LoaderPipe
51 from cvs2svn_lib.output_option import OutputOption
54 class SVNOutputOption(OutputOption):
55 """An OutputOption appropriate for output to Subversion."""
57 name = 'Subversion'
59 class ParentMissingError(Exception):
60 """The parent of a path is missing.
62 Exception raised if an attempt is made to add a path to the
63 repository mirror but the parent's path doesn't exist in the
64 youngest revision of the repository."""
66 pass
68 class ExpectedDirectoryError(Exception):
69 """A file was found where a directory was expected."""
71 pass
73 def __init__(self, author_transforms=None):
74 self._mirror = RepositoryMirror()
76 def to_utf8(s):
77 if isinstance(s, unicode):
78 return s.encode('utf8')
79 else:
80 return s
82 self.author_transforms = {}
83 if author_transforms is not None:
84 for (cvsauthor, name) in author_transforms.iteritems():
85 cvsauthor = to_utf8(cvsauthor)
86 name = to_utf8(name)
87 self.author_transforms[cvsauthor] = name
89 def register_artifacts(self, which_pass):
90 # These artifacts are needed for SymbolingsReader:
91 artifact_manager.register_temp_file_needed(
92 config.SYMBOL_OPENINGS_CLOSINGS_SORTED, which_pass
94 artifact_manager.register_temp_file_needed(
95 config.SYMBOL_OFFSETS_DB, which_pass
98 self._mirror.register_artifacts(which_pass)
99 Ctx().revision_reader.register_artifacts(which_pass)
101 # Characters not allowed in Subversion filenames:
102 illegal_filename_characters_re = re.compile('[\\\x00-\\\x1f\\\x7f]')
104 def verify_filename_legal(self, filename):
105 OutputOption.verify_filename_legal(self, filename)
107 m = SVNOutputOption.illegal_filename_characters_re.search(filename)
108 if m:
109 raise IllegalSVNPathError(
110 '%s does not allow character %r in filename %r.'
111 % (self.name, m.group(), filename,)
114 def check_symbols(self, symbol_map):
115 """Check that the paths of all included LODs are set and disjoint."""
117 error_found = False
119 # Check that all included LODs have their base paths set, and
120 # collect the paths into a list:
121 paths = []
122 for lod in symbol_map.itervalues():
123 if isinstance(lod, LineOfDevelopment):
124 if lod.base_path is None:
125 logger.error('%s: No path was set for %r\n' % (error_prefix, lod,))
126 error_found = True
127 else:
128 paths.append(lod.base_path)
130 # Check that the SVN paths of all LODS are disjoint:
131 try:
132 verify_paths_disjoint(*paths)
133 except PathsNotDisjointException, e:
134 logger.error(str(e))
135 error_found = True
137 if error_found:
138 raise FatalException(
139 'Please fix the above errors and restart CollateSymbolsPass'
142 def setup(self, svn_rev_count):
143 self._symbolings_reader = SymbolingsReader()
144 self._mirror.open()
145 self._delegates = []
146 Ctx().revision_reader.start()
147 self.svn_rev_count = svn_rev_count
149 def _get_author(self, svn_commit):
150 author = svn_commit.get_author()
151 name = self.author_transforms.get(author, author)
152 return name
154 def _get_revprops(self, svn_commit):
155 """Return the Subversion revprops for this SVNCommit."""
157 return {
158 'svn:author' : self._get_author(svn_commit),
159 'svn:log' : svn_commit.get_log_msg(),
160 'svn:date' : format_date(svn_commit.date),
163 def start_commit(self, revnum, revprops):
164 """Start a new commit."""
166 logger.verbose("=" * 60)
167 logger.normal(
168 "Starting Subversion r%d / %d" % (revnum, self.svn_rev_count)
171 self._mirror.start_commit(revnum)
172 self._invoke_delegates('start_commit', revnum, revprops)
174 def end_commit(self):
175 """Called at the end of each commit.
177 This method copies the newly created nodes to the on-disk nodes
178 db."""
180 self._mirror.end_commit()
181 self._invoke_delegates('end_commit')
183 def delete_lod(self, lod):
184 """Delete the main path for LOD from the tree.
186 The path must currently exist. Silently refuse to delete trunk
187 paths."""
189 if isinstance(lod, Trunk):
190 # Never delete a Trunk path.
191 return
193 logger.verbose(" Deleting %s" % (lod.get_path(),))
194 self._mirror.get_current_lod_directory(lod).delete()
195 self._invoke_delegates('delete_lod', lod)
197 def delete_path(self, cvs_path, lod, should_prune=False):
198 """Delete CVS_PATH from LOD."""
200 if cvs_path.parent_directory is None:
201 self.delete_lod(lod)
202 return
204 logger.verbose(" Deleting %s" % (lod.get_path(cvs_path.cvs_path),))
205 parent_node = self._mirror.get_current_path(
206 cvs_path.parent_directory, lod
208 del parent_node[cvs_path]
209 self._invoke_delegates('delete_path', lod, cvs_path)
211 if should_prune:
212 while parent_node is not None and len(parent_node) == 0:
213 # A drawback of this code is that we issue a delete for each
214 # path and not just a single delete for the topmost directory
215 # pruned.
216 node = parent_node
217 cvs_path = node.cvs_path
218 if cvs_path.parent_directory is None:
219 parent_node = None
220 self.delete_lod(lod)
221 else:
222 parent_node = node.parent_mirror_dir
223 node.delete()
224 logger.verbose(" Deleting %s" % (lod.get_path(cvs_path.cvs_path),))
225 self._invoke_delegates('delete_path', lod, cvs_path)
227 def initialize_project(self, project):
228 """Create the basic structure for PROJECT."""
230 logger.verbose(" Initializing project %s" % (project,))
231 self._invoke_delegates('initialize_project', project)
233 # Don't invoke delegates.
234 self._mirror.add_lod(project.get_trunk())
235 if Ctx().include_empty_directories:
236 self._make_empty_subdirectories(
237 project.get_root_cvs_directory(), project.get_trunk()
240 def change_path(self, cvs_rev):
241 """Register a change in self._youngest for the CVS_REV's svn_path."""
243 logger.verbose(" Changing %s" % (cvs_rev.get_svn_path(),))
244 # We do not have to update the nodes because our mirror is only
245 # concerned with the presence or absence of paths, and a file
246 # content change does not cause any path changes.
247 self._invoke_delegates('change_path', cvs_rev)
249 def _make_empty_subdirectories(self, cvs_directory, lod):
250 """Make any empty subdirectories of CVS_DIRECTORY in LOD."""
252 for empty_subdirectory_id in cvs_directory.empty_subdirectory_ids:
253 empty_subdirectory = Ctx()._cvs_path_db.get_path(empty_subdirectory_id)
254 logger.verbose(
255 " New Directory %s" % (lod.get_path(empty_subdirectory.cvs_path),)
257 # There is no need to record the empty subdirectories in the
258 # mirror, since they live and die with their parent directories.
259 self._invoke_delegates('mkdir', lod, empty_subdirectory)
260 self._make_empty_subdirectories(empty_subdirectory, lod)
262 def _mkdir_p(self, cvs_directory, lod):
263 """Make sure that CVS_DIRECTORY exists in LOD.
265 If not, create it, calling delegates. Return the node for
266 CVS_DIRECTORY."""
268 ancestry = cvs_directory.get_ancestry()
270 try:
271 node = self._mirror.get_current_lod_directory(lod)
272 except KeyError:
273 logger.verbose(" Initializing %s" % (lod,))
274 node = self._mirror.add_lod(lod)
275 self._invoke_delegates('initialize_lod', lod)
276 if ancestry and Ctx().include_empty_directories:
277 self._make_empty_subdirectories(ancestry[0], lod)
279 for sub_path in ancestry[1:]:
280 try:
281 node = node[sub_path]
282 except KeyError:
283 logger.verbose(
284 " New Directory %s" % (lod.get_path(sub_path.cvs_path),)
286 node = node.mkdir(sub_path)
287 self._invoke_delegates('mkdir', lod, sub_path)
288 if Ctx().include_empty_directories:
289 self._make_empty_subdirectories(sub_path, lod)
290 if node is None:
291 raise self.ExpectedDirectoryError(
292 'File found at \'%s\' where directory was expected.' % (sub_path,)
295 return node
297 def add_path(self, cvs_rev):
298 """Add the CVS_REV's svn_path to the repository mirror.
300 Create any missing intermediate paths."""
302 cvs_file = cvs_rev.cvs_file
303 parent_path = cvs_file.parent_directory
304 lod = cvs_rev.lod
305 parent_node = self._mkdir_p(parent_path, lod)
306 logger.verbose(" Adding %s" % (cvs_rev.get_svn_path(),))
307 parent_node.add_file(cvs_file)
308 self._invoke_delegates('add_path', cvs_rev)
310 def _show_copy(self, src_path, dest_path, src_revnum):
311 """Print a line stating that we are 'copying' revision SRC_REVNUM
312 of SRC_PATH to DEST_PATH."""
314 logger.verbose(
315 " Copying revision %d of %s\n"
316 " to %s\n"
317 % (src_revnum, src_path, dest_path,)
320 def copy_lod(self, src_lod, dest_lod, src_revnum):
321 """Copy all of SRC_LOD at SRC_REVNUM to DST_LOD.
323 In the youngest revision of the repository, the destination LOD
324 *must not* already exist.
326 Return the new node at DEST_LOD. Note that this node is not
327 necessarily writable, though its parent node necessarily is."""
329 self._show_copy(src_lod.get_path(), dest_lod.get_path(), src_revnum)
330 node = self._mirror.copy_lod(src_lod, dest_lod, src_revnum)
331 self._invoke_delegates('copy_lod', src_lod, dest_lod, src_revnum)
332 return node
334 def copy_path(
335 self, cvs_path, src_lod, dest_lod, src_revnum, create_parent=False
337 """Copy CVS_PATH from SRC_LOD at SRC_REVNUM to DST_LOD.
339 In the youngest revision of the repository, the destination's
340 parent *must* exist unless CREATE_PARENT is specified. But the
341 destination itself *must not* exist.
343 Return the new node at (CVS_PATH, DEST_LOD), as a
344 CurrentMirrorDirectory."""
346 if cvs_path.parent_directory is None:
347 return self.copy_lod(src_lod, dest_lod, src_revnum)
349 # Get the node of our source, or None if it is a file:
350 src_node = self._mirror.get_old_path(cvs_path, src_lod, src_revnum)
352 # Get the parent path of the destination:
353 if create_parent:
354 dest_parent_node = self._mkdir_p(cvs_path.parent_directory, dest_lod)
355 else:
356 try:
357 dest_parent_node = self._mirror.get_current_path(
358 cvs_path.parent_directory, dest_lod
360 except KeyError:
361 raise self.ParentMissingError(
362 'Attempt to add path \'%s\' to repository mirror, '
363 'but its parent directory doesn\'t exist in the mirror.'
364 % (dest_lod.get_path(cvs_path.cvs_path),)
367 if cvs_path in dest_parent_node:
368 raise PathExistsError(
369 'Attempt to add path \'%s\' to repository mirror '
370 'when it already exists in the mirror.'
371 % (dest_lod.get_path(cvs_path.cvs_path),)
374 self._show_copy(
375 src_lod.get_path(cvs_path.cvs_path),
376 dest_lod.get_path(cvs_path.cvs_path),
377 src_revnum,
379 dest_parent_node[cvs_path] = src_node
380 self._invoke_delegates(
381 'copy_path', cvs_path, src_lod, dest_lod, src_revnum
384 return dest_parent_node[cvs_path]
386 def fill_symbol(self, svn_symbol_commit, fill_source):
387 """Perform all copies for the CVSSymbols in SVN_SYMBOL_COMMIT.
389 The symbolic name is guaranteed to exist in the Subversion
390 repository by the end of this call, even if there are no paths
391 under it."""
393 symbol = svn_symbol_commit.symbol
395 try:
396 dest_node = self._mirror.get_current_lod_directory(symbol)
397 except KeyError:
398 self._fill_directory(symbol, None, fill_source, None)
399 else:
400 self._fill_directory(symbol, dest_node, fill_source, None)
402 def _fill_directory(self, symbol, dest_node, fill_source, parent_source):
403 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
405 Use items from FILL_SOURCE, and recurse into the child items.
407 Fill SYMBOL starting at the path FILL_SOURCE.cvs_path. DEST_NODE
408 is the node of this destination path, or None if the destination
409 does not yet exist. All directories above this path have already
410 been filled. FILL_SOURCE is a FillSource instance describing the
411 items within a subtree of the repository that still need to be
412 copied to the destination.
414 PARENT_SOURCE is the SVNRevisionRange that was used to copy the
415 parent directory, if it was copied in this commit. We prefer to
416 copy from the same source as was used for the parent, since it
417 typically requires less touching-up. If PARENT_SOURCE is None,
418 then the parent directory was not copied in this commit, so no
419 revision is preferable to any other."""
421 copy_source = fill_source.compute_best_source(parent_source)
423 # Figure out if we shall copy to this destination and delete any
424 # destination path that is in the way.
425 if dest_node is None:
426 # The destination does not exist at all, so it definitely has to
427 # be copied:
428 dest_node = self.copy_path(
429 fill_source.cvs_path, copy_source.source_lod,
430 symbol, copy_source.opening_revnum
432 elif (parent_source is not None) and (
433 copy_source.source_lod != parent_source.source_lod
434 or copy_source.opening_revnum != parent_source.opening_revnum
436 # The parent path was copied from a different source than we
437 # need to use, so we have to delete the version that was copied
438 # with the parent then re-copy from the correct source:
439 self.delete_path(fill_source.cvs_path, symbol)
440 dest_node = self.copy_path(
441 fill_source.cvs_path, copy_source.source_lod,
442 symbol, copy_source.opening_revnum
444 else:
445 copy_source = parent_source
447 # The map {CVSPath : FillSource} of entries within this directory
448 # that need filling:
449 src_entries = fill_source.get_subsource_map()
451 if copy_source is not None:
452 self._prune_extra_entries(
453 fill_source.cvs_path, symbol, dest_node, src_entries
456 return self._cleanup_filled_directory(
457 symbol, dest_node, src_entries, copy_source
460 def _cleanup_filled_directory(
461 self, symbol, dest_node, src_entries, copy_source
463 """The directory at DEST_NODE has been filled and pruned; recurse.
465 Recurse into the SRC_ENTRIES, in alphabetical order. If DEST_NODE
466 was copied in this revision, COPY_SOURCE should indicate where it
467 was copied from; otherwise, COPY_SOURCE should be None."""
469 cvs_paths = src_entries.keys()
470 cvs_paths.sort()
471 for cvs_path in cvs_paths:
472 if isinstance(cvs_path, CVSDirectory):
473 # Path is a CVSDirectory:
474 try:
475 dest_subnode = dest_node[cvs_path]
476 except KeyError:
477 # Path doesn't exist yet; it has to be created:
478 dest_node = self._fill_directory(
479 symbol, None, src_entries[cvs_path], None
480 ).parent_mirror_dir
481 else:
482 # Path already exists, but might have to be cleaned up:
483 dest_node = self._fill_directory(
484 symbol, dest_subnode, src_entries[cvs_path], copy_source
485 ).parent_mirror_dir
486 else:
487 # Path is a CVSFile:
488 self._fill_file(
489 symbol, cvs_path in dest_node, src_entries[cvs_path], copy_source
491 # Reread dest_node since the call to _fill_file() might have
492 # made it writable:
493 dest_node = self._mirror.get_current_path(
494 dest_node.cvs_path, dest_node.lod
497 return dest_node
499 def _fill_file(self, symbol, dest_existed, fill_source, parent_source):
500 """Fill the tag or branch SYMBOL at the path indicated by FILL_SOURCE.
502 Use items from FILL_SOURCE.
504 Fill SYMBOL at path FILL_SOURCE.cvs_path. DEST_NODE is the node
505 of this destination path, or None if the destination does not yet
506 exist. All directories above this path have already been filled
507 as needed. FILL_SOURCE is a FillSource instance describing the
508 item that needs to be copied to the destination.
510 PARENT_SOURCE is the source from which the parent directory was
511 copied, or None if the parent directory was not copied during this
512 commit. We prefer to copy from PARENT_SOURCE, since it typically
513 requires less touching-up. If PARENT_SOURCE is None, then the
514 parent directory was not copied in this commit, so no revision is
515 preferable to any other."""
517 copy_source = fill_source.compute_best_source(parent_source)
519 # Figure out if we shall copy to this destination and delete any
520 # destination path that is in the way.
521 if not dest_existed:
522 # The destination does not exist at all, so it definitely has to
523 # be copied:
524 self.copy_path(
525 fill_source.cvs_path, copy_source.source_lod,
526 symbol, copy_source.opening_revnum
528 elif (parent_source is not None) and (
529 copy_source.source_lod != parent_source.source_lod
530 or copy_source.opening_revnum != parent_source.opening_revnum
532 # The parent path was copied from a different source than we
533 # need to use, so we have to delete the version that was copied
534 # with the parent and then re-copy from the correct source:
535 self.delete_path(fill_source.cvs_path, symbol)
536 self.copy_path(
537 fill_source.cvs_path, copy_source.source_lod,
538 symbol, copy_source.opening_revnum
541 def _prune_extra_entries(
542 self, dest_cvs_path, symbol, dest_node, src_entries
544 """Delete any entries in DEST_NODE that are not in SRC_ENTRIES."""
546 delete_list = [
547 cvs_path
548 for cvs_path in dest_node
549 if cvs_path not in src_entries
552 # Sort the delete list so that the output is in a consistent
553 # order:
554 delete_list.sort()
555 for cvs_path in delete_list:
556 logger.verbose(" Deleting %s" % (symbol.get_path(cvs_path.cvs_path),))
557 del dest_node[cvs_path]
558 self._invoke_delegates('delete_path', symbol, cvs_path)
560 def add_delegate(self, delegate):
561 """Adds DELEGATE to self._delegates.
563 For every delegate you add, whenever a repository action method is
564 performed, delegate's corresponding repository action method is
565 called. Multiple delegates will be called in the order that they
566 are added. See SVNRepositoryDelegate for more information."""
568 self._delegates.append(delegate)
570 def _invoke_delegates(self, method, *args):
571 """Invoke a method on each delegate.
573 Iterate through each of our delegates, in the order that they were
574 added, and call the delegate's method named METHOD with the
575 arguments in ARGS."""
577 for delegate in self._delegates:
578 getattr(delegate, method)(*args)
580 def process_initial_project_commit(self, svn_commit):
581 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
583 for project in svn_commit.projects:
584 self.initialize_project(project)
586 self.end_commit()
588 def process_primary_commit(self, svn_commit):
589 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
591 # This actually commits CVSRevisions
592 if len(svn_commit.cvs_revs) > 1:
593 plural = "s"
594 else:
595 plural = ""
596 logger.verbose("Committing %d CVSRevision%s"
597 % (len(svn_commit.cvs_revs), plural))
598 for cvs_rev in svn_commit.cvs_revs:
599 if isinstance(cvs_rev, CVSRevisionNoop):
600 pass
602 elif isinstance(cvs_rev, CVSRevisionDelete):
603 self.delete_path(cvs_rev.cvs_file, cvs_rev.lod, Ctx().prune)
605 elif isinstance(cvs_rev, CVSRevisionAdd):
606 self.add_path(cvs_rev)
608 elif isinstance(cvs_rev, CVSRevisionChange):
609 self.change_path(cvs_rev)
611 self.end_commit()
613 def process_post_commit(self, svn_commit):
614 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
616 logger.verbose(
617 'Synchronizing default branch motivated by %d'
618 % (svn_commit.motivating_revnum,)
621 for cvs_rev in svn_commit.cvs_revs:
622 trunk = cvs_rev.cvs_file.project.get_trunk()
623 if isinstance(cvs_rev, CVSRevisionAdd):
624 # Copy from branch to trunk:
625 self.copy_path(
626 cvs_rev.cvs_file, cvs_rev.lod, trunk,
627 svn_commit.motivating_revnum, True
629 elif isinstance(cvs_rev, CVSRevisionChange):
630 # Delete old version of the path on trunk...
631 self.delete_path(cvs_rev.cvs_file, trunk)
632 # ...and copy the new version over from branch:
633 self.copy_path(
634 cvs_rev.cvs_file, cvs_rev.lod, trunk,
635 svn_commit.motivating_revnum, True
637 elif isinstance(cvs_rev, CVSRevisionDelete):
638 # Delete trunk path:
639 self.delete_path(cvs_rev.cvs_file, trunk)
640 elif isinstance(cvs_rev, CVSRevisionNoop):
641 # Do nothing
642 pass
643 else:
644 raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev,))
646 self.end_commit()
648 def process_branch_commit(self, svn_commit):
649 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
650 logger.verbose('Filling branch:', svn_commit.symbol.name)
652 # Get the set of sources for the symbolic name:
653 source_set = get_source_set(
654 svn_commit.symbol,
655 self._symbolings_reader.get_range_map(svn_commit),
658 self.fill_symbol(svn_commit, source_set)
660 self.end_commit()
662 def process_tag_commit(self, svn_commit):
663 self.start_commit(svn_commit.revnum, self._get_revprops(svn_commit))
664 logger.verbose('Filling tag:', svn_commit.symbol.name)
666 # Get the set of sources for the symbolic name:
667 source_set = get_source_set(
668 svn_commit.symbol,
669 self._symbolings_reader.get_range_map(svn_commit),
672 self.fill_symbol(svn_commit, source_set)
674 self.end_commit()
676 def cleanup(self):
677 self._invoke_delegates('finish')
678 logger.verbose("Finished creating Subversion repository.")
679 logger.quiet("Done.")
680 self._mirror.close()
681 self._mirror = None
682 Ctx().revision_reader.finish()
683 self._symbolings_reader.close()
684 del self._symbolings_reader
687 class DumpfileOutputOption(SVNOutputOption):
688 """Output the result of the conversion into a dumpfile."""
690 def __init__(self, dumpfile_path, author_transforms=None):
691 SVNOutputOption.__init__(self, author_transforms)
692 self.dumpfile_path = dumpfile_path
694 def check(self):
695 pass
697 def setup(self, svn_rev_count):
698 logger.quiet("Starting Subversion Dumpfile.")
699 SVNOutputOption.setup(self, svn_rev_count)
700 if not Ctx().dry_run:
701 self.add_delegate(
702 DumpstreamDelegate(
703 Ctx().revision_reader, open(self.dumpfile_path, 'wb')
708 class RepositoryOutputOption(SVNOutputOption):
709 """Output the result of the conversion into an SVN repository."""
711 def __init__(self, target, author_transforms=None):
712 SVNOutputOption.__init__(self, author_transforms)
713 self.target = target
715 def check(self):
716 if not Ctx().dry_run:
717 # Verify that svnadmin can be executed. The 'help' subcommand
718 # should be harmless.
719 try:
720 check_command_runs([Ctx().svnadmin_executable, 'help'], 'svnadmin')
721 except CommandFailedException, e:
722 raise FatalError(
723 '%s\n'
724 'svnadmin could not be executed. Please ensure that it is\n'
725 'installed and/or use the --svnadmin option.' % (e,))
727 def setup(self, svn_rev_count):
728 logger.quiet("Starting Subversion Repository.")
729 SVNOutputOption.setup(self, svn_rev_count)
730 if not Ctx().dry_run:
731 self.add_delegate(
732 DumpstreamDelegate(Ctx().revision_reader, LoaderPipe(self.target))
736 class NewRepositoryOutputOption(RepositoryOutputOption):
737 """Output the result of the conversion into a new SVN repository."""
739 def __init__(
740 self, target,
741 fs_type=None, bdb_txn_nosync=None,
742 author_transforms=None, create_options=[],
744 RepositoryOutputOption.__init__(self, target, author_transforms)
745 self.bdb_txn_nosync = bdb_txn_nosync
747 # Determine the options to be passed to "svnadmin create":
748 if not fs_type:
749 # User didn't say what kind repository (bdb, fsfs, etc). We
750 # still pass --bdb-txn-nosync. It's a no-op if the default
751 # repository type doesn't support it, but we definitely want it
752 # if BDB is the default.
753 self.create_options = ['--bdb-txn-nosync']
754 elif fs_type == 'bdb':
755 # User explicitly specified bdb.
757 # Since this is a BDB repository, pass --bdb-txn-nosync, because
758 # it gives us a 4-5x speed boost (if cvs2svn is creating the
759 # repository, cvs2svn should be the only program accessing the
760 # svn repository until cvs2svn is done). But we'll turn no-sync
761 # off in self.finish(), unless instructed otherwise.
762 self.create_options = ['--fs-type=bdb', '--bdb-txn-nosync']
763 else:
764 # User specified something other than bdb.
765 self.create_options = ['--fs-type=%s' % fs_type]
767 # Now append the user's explicitly-set create options:
768 self.create_options += create_options
770 def check(self):
771 RepositoryOutputOption.check(self)
772 if not Ctx().dry_run and os.path.exists(self.target):
773 raise FatalError("the svn-repos-path '%s' exists.\n"
774 "Remove it, or pass '--existing-svnrepos'."
775 % self.target)
777 def setup(self, svn_rev_count):
778 logger.normal("Creating new repository '%s'" % (self.target))
779 if Ctx().dry_run:
780 # Do not actually create repository:
781 pass
782 else:
783 call_command([
784 Ctx().svnadmin_executable, 'create',
785 ] + self.create_options + [
786 self.target
789 RepositoryOutputOption.setup(self, svn_rev_count)
791 def cleanup(self):
792 RepositoryOutputOption.cleanup(self)
794 # If this is a BDB repository, and we created the repository, and
795 # --bdb-no-sync wasn't passed, then comment out the DB_TXN_NOSYNC
796 # line in the DB_CONFIG file, because txn syncing should be on by
797 # default in BDB repositories.
799 # We determine if this is a BDB repository by looking for the
800 # DB_CONFIG file, which doesn't exist in FSFS, rather than by
801 # checking self.fs_type. That way this code will Do The Right
802 # Thing in all circumstances.
803 db_config = os.path.join(self.target, "db/DB_CONFIG")
804 if Ctx().dry_run:
805 # Do not change repository:
806 pass
807 elif not self.bdb_txn_nosync and os.path.exists(db_config):
808 no_sync = 'set_flags DB_TXN_NOSYNC\n'
810 contents = open(db_config, 'r').readlines()
811 index = contents.index(no_sync)
812 contents[index] = '# ' + no_sync
813 open(db_config, 'w').writelines(contents)
816 class ExistingRepositoryOutputOption(RepositoryOutputOption):
817 """Output the result of the conversion into an existing SVN repository."""
819 def __init__(self, target, author_transforms=None):
820 RepositoryOutputOption.__init__(self, target, author_transforms)
822 def check(self):
823 RepositoryOutputOption.check(self)
824 if not os.path.isdir(self.target):
825 raise FatalError("the svn-repos-path '%s' is not an "
826 "existing directory." % self.target)