diff options
Diffstat (limited to 'cvs2svn_lib/svn_commit_creator.py')
-rw-r--r-- | cvs2svn_lib/svn_commit_creator.py | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/cvs2svn_lib/svn_commit_creator.py b/cvs2svn_lib/svn_commit_creator.py new file mode 100644 index 0000000..c87db38 --- /dev/null +++ b/cvs2svn_lib/svn_commit_creator.py @@ -0,0 +1,217 @@ +# (Be in -*- python -*- mode.) +# +# ==================================================================== +# Copyright (c) 2000-2008 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +# This software consists of voluntary contributions made by many +# individuals. For exact contribution history, see the revision +# history and logs, available at http://cvs2svn.tigris.org/. +# ==================================================================== + +"""This module contains the SVNCommitCreator class.""" + + +import time + +from cvs2svn_lib.common import InternalError +from cvs2svn_lib.log import Log +from cvs2svn_lib.context import Ctx +from cvs2svn_lib.cvs_item import CVSRevisionNoop +from cvs2svn_lib.cvs_item import CVSBranchNoop +from cvs2svn_lib.cvs_item import CVSTagNoop +from cvs2svn_lib.changeset import OrderedChangeset +from cvs2svn_lib.changeset import BranchChangeset +from cvs2svn_lib.changeset import TagChangeset +from cvs2svn_lib.svn_commit import SVNInitialProjectCommit +from cvs2svn_lib.svn_commit import SVNPrimaryCommit +from cvs2svn_lib.svn_commit import SVNPostCommit +from cvs2svn_lib.svn_commit import SVNBranchCommit +from cvs2svn_lib.svn_commit import SVNTagCommit +from cvs2svn_lib.key_generator import KeyGenerator + + +class SVNCommitCreator: + """This class creates and yields SVNCommits via process_changeset().""" + + def __init__(self): + # The revision number to assign to the next new SVNCommit. + self.revnum_generator = KeyGenerator() + + # A set containing the Projects that have already been + # initialized: + self._initialized_projects = set() + + def _post_commit(self, cvs_revs, motivating_revnum, timestamp): + """Generate any SVNCommits needed to follow CVS_REVS. + + That is, handle non-trunk default branches. A revision on a CVS + non-trunk default branch is visible in a default CVS checkout of + HEAD. So we copy such commits over to Subversion's trunk so that + checking out SVN trunk gives the same output as checking out of + CVS's default branch.""" + + cvs_revs = [ + cvs_rev + for cvs_rev in cvs_revs + if cvs_rev.ntdbr and not isinstance(cvs_rev, CVSRevisionNoop) + ] + + if cvs_revs: + cvs_revs.sort( + lambda a, b: cmp(a.cvs_file.filename, b.cvs_file.filename) + ) + # Generate an SVNCommit for all of our default branch cvs_revs. + yield SVNPostCommit( + motivating_revnum, cvs_revs, timestamp, + self.revnum_generator.gen_id(), + ) + + def _process_revision_changeset(self, changeset, timestamp): + """Process CHANGESET, using TIMESTAMP as the commit time. + + Create and yield one or more SVNCommits in the process. CHANGESET + must be an OrderedChangeset. TIMESTAMP is used as the timestamp + for any resulting SVNCommits.""" + + if not changeset.cvs_item_ids: + Log().warn('Changeset has no items: %r' % changeset) + return + + Log().verbose('-' * 60) + Log().verbose('CVS Revision grouping:') + Log().verbose(' Time: %s' % time.ctime(timestamp)) + + # Generate an SVNCommit unconditionally. Even if the only change in + # this group of CVSRevisions is a deletion of an already-deleted + # file (that is, a CVS revision in state 'dead' whose predecessor + # was also in state 'dead'), the conversion will still generate a + # Subversion revision containing the log message for the second dead + # revision, because we don't want to lose that information. + + cvs_revs = list(changeset.iter_cvs_items()) + if cvs_revs: + cvs_revs.sort(lambda a, b: cmp(a.cvs_file.filename, b.cvs_file.filename)) + svn_commit = SVNPrimaryCommit( + cvs_revs, timestamp, self.revnum_generator.gen_id() + ) + + yield svn_commit + + for cvs_rev in cvs_revs: + Ctx()._symbolings_logger.log_revision(cvs_rev, svn_commit.revnum) + + # Generate an SVNPostCommit if we have default branch revs. If + # some of the revisions in this commit happened on a non-trunk + # default branch, then those files have to be copied into trunk + # manually after being changed on the branch (because the RCS + # "default branch" appears as head, i.e., trunk, in practice). + # Unfortunately, Subversion doesn't support copies with sources + # in the current txn. All copies must be based in committed + # revisions. Therefore, we generate the copies in a new + # revision. + for svn_post_commit in self._post_commit( + cvs_revs, svn_commit.revnum, timestamp + ): + yield svn_post_commit + + def _process_tag_changeset(self, changeset, timestamp): + """Process TagChangeset CHANGESET, producing a SVNTagCommit. + + Filter out CVSTagNoops. If no CVSTags are left, don't generate a + SVNTagCommit.""" + + if Ctx().trunk_only: + raise InternalError( + 'TagChangeset encountered during a --trunk-only conversion') + + cvs_tag_ids = [ + cvs_tag.id + for cvs_tag in changeset.iter_cvs_items() + if not isinstance(cvs_tag, CVSTagNoop) + ] + if cvs_tag_ids: + yield SVNTagCommit( + changeset.symbol, cvs_tag_ids, timestamp, + self.revnum_generator.gen_id(), + ) + else: + Log().debug( + 'Omitting %r because it contains only CVSTagNoops' % (changeset,) + ) + + def _process_branch_changeset(self, changeset, timestamp): + """Process BranchChangeset CHANGESET, producing a SVNBranchCommit. + + Filter out CVSBranchNoops. If no CVSBranches are left, don't + generate a SVNBranchCommit.""" + + if Ctx().trunk_only: + raise InternalError( + 'BranchChangeset encountered during a --trunk-only conversion') + + cvs_branches = [ + cvs_branch + for cvs_branch in changeset.iter_cvs_items() + if not isinstance(cvs_branch, CVSBranchNoop) + ] + if cvs_branches: + svn_commit = SVNBranchCommit( + changeset.symbol, + [cvs_branch.id for cvs_branch in cvs_branches], + timestamp, + self.revnum_generator.gen_id(), + ) + yield svn_commit + for cvs_branch in cvs_branches: + Ctx()._symbolings_logger.log_branch_revision( + cvs_branch, svn_commit.revnum + ) + else: + Log().debug( + 'Omitting %r because it contains only CVSBranchNoops' % (changeset,) + ) + + def process_changeset(self, changeset, timestamp): + """Process CHANGESET, using TIMESTAMP for all of its entries. + + Return a generator that generates the resulting SVNCommits. + + The changesets must be fed to this function in proper dependency + order.""" + + # First create any new projects that might be opened by the + # changeset: + projects_opened = \ + changeset.get_projects_opened() - self._initialized_projects + if projects_opened: + if Ctx().cross_project_commits: + yield SVNInitialProjectCommit( + timestamp, projects_opened, self.revnum_generator.gen_id() + ) + else: + for project in projects_opened: + yield SVNInitialProjectCommit( + timestamp, [project], self.revnum_generator.gen_id() + ) + self._initialized_projects.update(projects_opened) + + if isinstance(changeset, OrderedChangeset): + for svn_commit \ + in self._process_revision_changeset(changeset, timestamp): + yield svn_commit + elif isinstance(changeset, TagChangeset): + for svn_commit in self._process_tag_changeset(changeset, timestamp): + yield svn_commit + elif isinstance(changeset, BranchChangeset): + for svn_commit in self._process_branch_changeset(changeset, timestamp): + yield svn_commit + else: + raise TypeError('Illegal changeset %r' % changeset) + + |