diff options
Diffstat (limited to 'cvs2svn_lib/svn_commit.py')
-rw-r--r-- | cvs2svn_lib/svn_commit.py | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/cvs2svn_lib/svn_commit.py b/cvs2svn_lib/svn_commit.py new file mode 100644 index 0000000..25dc38e --- /dev/null +++ b/cvs2svn_lib/svn_commit.py @@ -0,0 +1,381 @@ +# (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 SVNCommit classes. + +There are five types of SVNCommits: + + SVNInitialProjectCommit -- Initializes a project (creates its trunk, + branches, and tags directories). + + SVNPrimaryCommit -- Commits one or more CVSRevisions on one or more + lines of development. + + SVNBranchCommit -- Creates or fills a branch; that is, copies files + from a source line of development to a target branch. + + SVNTagCommit -- Creates or fills a tag; that is, copies files from a + source line of development to a target tag. + + SVNPostCommit -- Updates trunk to reflect changes on a non-trunk + default branch. + +""" + + +import textwrap + +from cvs2svn_lib.common import InternalError +from cvs2svn_lib.context import Ctx +from cvs2svn_lib.symbol import Branch +from cvs2svn_lib.symbol import Tag + + +class SVNCommit: + """This represents one commit to the Subversion Repository.""" + + # textwrap.TextWrapper instance to be used for wrapping log messages: + text_wrapper = textwrap.TextWrapper(width=76) + + def __init__(self, date, revnum): + """Instantiate an SVNCommit. + + REVNUM is the SVN revision number of this commit.""" + + # The date of the commit, as an integer. While the SVNCommit is + # being built up, this contains the latest date seen so far. This + # member is set externally. + self.date = date + + # The SVN revision number of this commit, as an integer. + self.revnum = revnum + + def __getstate__(self): + return (self.date, self.revnum,) + + def __setstate__(self, state): + (self.date, self.revnum,) = state + + def get_cvs_items(self): + """Return a list containing the CVSItems in this commit.""" + + raise NotImplementedError() + + def get_author(self): + """Return the author or this commit, or None if none is to be used. + + The return value is exactly as the author appeared in the RCS + file, with undefined character encoding.""" + + raise NotImplementedError() + + def get_log_msg(self): + """Return a log message for this commit. + + The return value is exactly as the log message appeared in the RCS + file, with undefined character encoding.""" + + raise NotImplementedError() + + def get_warning_summary(self): + """Return a summary of this commit that can be used in warnings.""" + + return '(subversion rev %s)' % (self.revnum,) + + def get_description(self): + """Return a partial description of this SVNCommit, for logging.""" + + raise NotImplementedError() + + def output(self, output_option): + """Cause this commit to be output to OUTPUT_OPTION. + + This method is used for double-dispatch. Derived classes should + call the OutputOption.process_*_commit() method appropriate for + the type of SVNCommit.""" + + raise NotImplementedError() + + def __str__(self): + """ Print a human-readable description of this SVNCommit. + + This description is not intended to be machine-parseable.""" + + ret = "SVNCommit #: " + str(self.revnum) + "\n" + ret += " debug description: " + self.get_description() + "\n" + return ret + + +class SVNInitialProjectCommit(SVNCommit): + def __init__(self, date, projects, revnum): + SVNCommit.__init__(self, date, revnum) + self.projects = list(projects) + + def __getstate__(self): + return ( + SVNCommit.__getstate__(self), + [project.id for project in self.projects], + ) + + def __setstate__(self, state): + (svn_commit_state, project_ids,) = state + SVNCommit.__setstate__(self, svn_commit_state) + self.projects = [ + Ctx()._projects[project_id] for project_id in project_ids + ] + + def get_cvs_items(self): + return [] + + def get_author(self): + return Ctx().username + + def get_log_msg(self): + return self.text_wrapper.fill( + Ctx().initial_project_commit_message % {} + ) + + def get_description(self): + return 'Project initialization' + + def output(self, output_option): + output_option.process_initial_project_commit(self) + + +class SVNRevisionCommit(SVNCommit): + """A SVNCommit that includes actual CVS revisions.""" + + def __init__(self, cvs_revs, date, revnum): + SVNCommit.__init__(self, date, revnum) + + self.cvs_revs = list(cvs_revs) + + # This value is set lazily by _get_metadata(): + self._metadata = None + + def __getstate__(self): + """Return the part of the state represented by this mixin.""" + + return ( + SVNCommit.__getstate__(self), + [cvs_rev.id for cvs_rev in self.cvs_revs], + ) + + def __setstate__(self, state): + """Restore the part of the state represented by this mixin.""" + + (svn_commit_state, cvs_rev_ids) = state + SVNCommit.__setstate__(self, svn_commit_state) + + self.cvs_revs = [ + cvs_rev + for (id, cvs_rev) in Ctx()._cvs_items_db.get_many(cvs_rev_ids) + ] + self._metadata = None + + def get_cvs_items(self): + return self.cvs_revs + + def _get_metadata(self): + """Return the Metadata instance for this commit.""" + + if self._metadata is None: + # Set self._metadata for this commit from that of the first cvs + # revision. + if not self.cvs_revs: + raise InternalError('SVNPrimaryCommit contains no CVS revisions') + + metadata_id = self.cvs_revs[0].metadata_id + self._metadata = Ctx()._metadata_db[metadata_id] + + return self._metadata + + def get_author(self): + return self._get_metadata().author + + def get_warning_summary(self): + retval = [] + retval.append(SVNCommit.get_warning_summary(self) + ' Related files:') + for cvs_rev in self.cvs_revs: + retval.append(' ' + cvs_rev.cvs_file.filename) + return '\n'.join(retval) + + def __str__(self): + """Return the revision part of a description of this SVNCommit. + + Derived classes should append the output of this method to the + output of SVNCommit.__str__().""" + + ret = [] + ret.append(SVNCommit.__str__(self)) + ret.append(' cvs_revs:\n') + for cvs_rev in self.cvs_revs: + ret.append(' %x\n' % (cvs_rev.id,)) + return ''.join(ret) + + +class SVNPrimaryCommit(SVNRevisionCommit): + def __init__(self, cvs_revs, date, revnum): + SVNRevisionCommit.__init__(self, cvs_revs, date, revnum) + + def get_log_msg(self): + """Return the actual log message for this commit.""" + + return self._get_metadata().log_msg + + def get_description(self): + return 'commit' + + def output(self, output_option): + output_option.process_primary_commit(self) + + +class SVNPostCommit(SVNRevisionCommit): + def __init__(self, motivating_revnum, cvs_revs, date, revnum): + SVNRevisionCommit.__init__(self, cvs_revs, date, revnum) + + # The subversion revision number of the *primary* commit where the + # default branch changes actually happened. (NOTE: Secondary + # commits that fill branches and tags also have a motivating + # commit, but we do not record it because it is (currently) not + # needed for anything.) motivating_revnum is used when generating + # the log message for the commit that synchronizes the default + # branch with trunk. + # + # It is possible for multiple synchronization commits to refer to + # the same motivating commit revision number, and it is possible + # for a single synchronization commit to contain CVSRevisions on + # multiple different default branches. + self.motivating_revnum = motivating_revnum + + def __getstate__(self): + return ( + SVNRevisionCommit.__getstate__(self), + self.motivating_revnum, + ) + + def __setstate__(self, state): + (rev_state, self.motivating_revnum,) = state + SVNRevisionCommit.__setstate__(self, rev_state) + + def get_cvs_items(self): + # It might seem that we should return + # SVNRevisionCommit.get_cvs_items(self) here, but this commit + # doesn't really include those CVSItems, but rather followup + # commits to those. + return [] + + def get_log_msg(self): + """Return a manufactured log message for this commit.""" + + return self.text_wrapper.fill( + Ctx().post_commit_message % {'revnum' : self.motivating_revnum} + ) + + def get_description(self): + return 'post-commit default branch(es)' + + def output(self, output_option): + output_option.process_post_commit(self) + + +class SVNSymbolCommit(SVNCommit): + def __init__(self, symbol, cvs_symbol_ids, date, revnum): + SVNCommit.__init__(self, date, revnum) + + # The TypedSymbol that is filled in this SVNCommit. + self.symbol = symbol + + self.cvs_symbol_ids = cvs_symbol_ids + + def __getstate__(self): + return ( + SVNCommit.__getstate__(self), + self.symbol.id, self.cvs_symbol_ids, + ) + + def __setstate__(self, state): + (svn_commit_state, symbol_id, self.cvs_symbol_ids) = state + SVNCommit.__setstate__(self, svn_commit_state) + self.symbol = Ctx()._symbol_db.get_symbol(symbol_id) + + def get_cvs_items(self): + return [ + cvs_symbol + for (id, cvs_symbol) + in Ctx()._cvs_items_db.get_many(self.cvs_symbol_ids) + ] + + def _get_symbol_type(self): + """Return the type of the self.symbol ('branch' or 'tag').""" + + raise NotImplementedError() + + def get_author(self): + return Ctx().username + + def get_log_msg(self): + """Return a manufactured log message for this commit.""" + + return self.text_wrapper.fill( + Ctx().symbol_commit_message % { + 'symbol_type' : self._get_symbol_type(), + 'symbol_name' : self.symbol.name, + } + ) + + def get_description(self): + return 'copying to %s %r' % (self._get_symbol_type(), self.symbol.name,) + + def __str__(self): + """ Print a human-readable description of this SVNCommit. + + This description is not intended to be machine-parseable.""" + + return ( + SVNCommit.__str__(self) + + " symbolic name: %s\n" % (self.symbol.name,) + ) + + +class SVNBranchCommit(SVNSymbolCommit): + def __init__(self, symbol, cvs_symbol_ids, date, revnum): + if not isinstance(symbol, Branch): + raise InternalError('Incorrect symbol type %r' % (symbol,)) + + SVNSymbolCommit.__init__(self, symbol, cvs_symbol_ids, date, revnum) + + def _get_symbol_type(self): + return 'branch' + + def output(self, output_option): + output_option.process_branch_commit(self) + + +class SVNTagCommit(SVNSymbolCommit): + def __init__(self, symbol, cvs_symbol_ids, date, revnum): + if not isinstance(symbol, Tag): + raise InternalError('Incorrect symbol type %r' % (symbol,)) + + SVNSymbolCommit.__init__(self, symbol, cvs_symbol_ids, date, revnum) + + def _get_symbol_type(self): + return 'tag' + + def output(self, output_option): + output_option.process_tag_commit(self) + + |