# (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)