1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2006 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 """This module contains the CVSCommit class."""
22 from common
import warning_prefix
23 from context
import Ctx
28 """This represents one commit to the Subversion Repository. There
29 are three types of SVNCommits:
31 1. Commits one or more CVSRevisions (cannot fill a symbolic name).
33 2. Creates or fills a symbolic name (cannot commit CVSRevisions).
35 3. Updates trunk to reflect the contents of a particular branch
36 (this is to handle RCS default branches)."""
38 # The revision number to assign to the next new SVNCommit.
39 # We start at 2 because SVNRepositoryMirror uses the first commit
40 # to create trunk, tags, and branches.
43 class SVNCommitInternalInconsistencyError(Exception):
44 """Exception raised if we encounter an impossible state in the
45 SVNCommit Databases."""
49 def __init__(self
, description
="", revnum
=None, cvs_revs
=None):
50 """Instantiate an SVNCommit. DESCRIPTION is for debugging only.
51 If REVNUM, the SVNCommit will correspond to that revision number;
52 and if CVS_REVS, then they must be the exact set of CVSRevisions for
55 It is an error to pass CVS_REVS without REVNUM, but you may pass
56 REVNUM without CVS_REVS, and then add a revision at a time by
57 invoking add_revision()."""
59 self
._description
= description
61 # Revprop metadata for this commit.
63 # These initial values are placeholders. At least the log and the
64 # date should be different by the time these are used.
66 # They are private because their values should be returned encoded
67 # in UTF8, but callers aren't required to set them in UTF8.
68 # Therefore, accessor methods are used to set them, and
69 # self.get_revprops() is used to to get them, in dictionary form.
70 self
._author
= Ctx().username
71 self
._log
_msg
= "This log message means an SVNCommit was used too soon."
72 self
._max
_date
= 0 # Latest date seen so far.
74 self
.cvs_revs
= cvs_revs
or []
78 self
.revnum
= SVNCommit
.revnum
81 # The (uncleaned) symbolic name that is filled in this SVNCommit, if any.
82 self
.symbolic_name
= None
84 # If this commit is a default branch synchronization, this
85 # variable represents the subversion revision number of the
86 # *primary* commit where the default branch changes actually
87 # happened. It is None otherwise.
89 # It is possible for multiple synchronization commits to refer to
90 # the same motivating commit revision number, and it is possible
91 # for a single synchronization commit to contain CVSRevisions on
92 # multiple different default branches.
93 self
.motivating_revnum
= None
95 # is_tag is true only if this commit is a fill of a symbolic name
96 # that is a tag, None in all other cases.
99 def set_symbolic_name(self
, symbolic_name
):
100 """Set self.symbolic_name to SYMBOLIC_NAME."""
102 self
.symbolic_name
= symbolic_name
104 def set_motivating_revnum(self
, revnum
):
105 """Set self.motivating_revnum to REVNUM."""
107 self
.motivating_revnum
= revnum
109 def set_author(self
, author
):
110 """Set this SVNCommit's author to AUTHOR (a locally-encoded string).
111 This is the only way to set an SVNCommit's author."""
113 self
._author
= author
115 def set_log_msg(self
, msg
):
116 """Set this SVNCommit's log message to MSG (a locally-encoded string).
117 This is the only way to set an SVNCommit's log message."""
121 def set_date(self
, date
):
122 """Set this SVNCommit's date to DATE (an integer).
123 Note that self.add_revision() updates this automatically based on
124 a CVSRevision; so you may not need to call this at all, and even
125 if you do, the value may be overwritten by a later call to
126 self.add_revision()."""
128 self
._max
_date
= date
131 """Returns this SVNCommit's date as an integer."""
133 return self
._max
_date
135 def get_revprops(self
):
136 """Return the Subversion revprops for this SVNCommit."""
138 date
= common
.format_date(self
._max
_date
)
141 if self
._author
is not None:
142 utf8_author
= Ctx().to_utf8(self
._author
)
143 utf8_log
= Ctx().to_utf8(self
.get_log_msg())
144 return { 'svn:author' : utf8_author
,
145 'svn:log' : utf8_log
,
148 Log().write(Log
.WARN
, '%s: problem encoding author or log message:'
150 Log().write(Log
.WARN
, " author: '%s'" % self
._author
)
151 Log().write(Log
.WARN
, " log: '%s'" % self
.get_log_msg().rstrip())
152 Log().write(Log
.WARN
, " date: '%s'" % date
)
153 Log().write(Log
.WARN
,
154 "(subversion rev %s) Related files:" % self
.revnum
)
155 for c_rev
in self
.cvs_revs
:
156 Log().write(Log
.WARN
, " ", c_rev
.fname
)
158 Log().write(Log
.WARN
, "Consider rerunning with one or more ",
159 "'--encoding' parameters.\n")
160 # It's better to fall back to the original (unknown encoding) data
161 # than to either 1) quit or 2) record nothing at all.
162 return { 'svn:author' : self
._author
,
163 'svn:log' : self
.get_log_msg(),
166 def add_revision(self
, cvs_rev
):
167 self
.cvs_revs
.append(cvs_rev
)
170 Log().write(Log
.NORMAL
, "Creating Subversion r%d (%s)"
171 % (self
.revnum
, self
._description
))
172 Ctx()._persistence
_manager
.put_svn_commit(self
.revnum
,
176 self
.motivating_revnum
)
179 """ Print a human-readable description of this SVNCommit. This
180 description is not intended to be machine-parseable (although
181 we're not going to stop you if you try!)"""
183 ret
= "SVNCommit #: " + str(self
.revnum
) + "\n"
184 if self
.symbolic_name
:
185 ret
+= (" symbolic name: "
186 + common
.clean_symbolic_name(self
.symbolic_name
)
189 ret
+= " NO symbolic name\n"
190 ret
+= " debug description: " + self
._description
+ "\n"
191 ret
+= " cvs_revs:\n"
192 for c_rev
in self
.cvs_revs
:
193 ret
+= " " + c_rev
.unique_key() + "\n"
196 def get_log_msg(self
):
197 """Returns the actual log message for a primary commit, and the
198 appropriate manufactured log message for a secondary commit."""
200 if self
.symbolic_name
is not None:
201 return self
._log
_msg
_for
_symbolic
_name
_commit
()
202 elif self
.motivating_revnum
is not None:
203 return self
._log
_msg
_for
_default
_branch
_commit
()
207 def _log_msg_for_symbolic_name_commit(self
):
208 """Creates a log message for a manufactured commit that fills
209 self.symbolic_name. If self.is_tag is true, write the log message
210 as though for a tag, else write it as though for a branch."""
216 # In Python 2.2.3, we could use textwrap.fill(). Oh well :-).
217 space_or_newline
= ' '
218 cleaned_symbolic_name
= common
.clean_symbolic_name(self
.symbolic_name
)
219 if len(cleaned_symbolic_name
) >= 13:
220 space_or_newline
= '\n'
222 return "This commit was manufactured by cvs2svn to create %s%s'%s'." \
223 % (type, space_or_newline
, cleaned_symbolic_name
)
225 def _log_msg_for_default_branch_commit(self
):
226 """Creates a log message for a manufactured commit that
227 synchronizes a non-trunk default branch with trunk."""
229 msg
= 'This commit was generated by cvs2svn to compensate for ' \
230 'changes in r%d,\n' \
231 'which included commits to RCS files with non-trunk default ' \
232 'branches.\n' % self
.motivating_revnum