Introduce proper XHTML boilerplate into the cvs2svn webpages.
[cvs2svn.git] / cvs2svn_lib / svn_commit.py
blob32cf4eaa8cc7adeb9866a511fa70707edbff4234
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."""
20 from boolean import *
21 import common
22 from common import warning_prefix
23 from context import Ctx
24 from log import Log
27 class SVNCommit:
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.
41 revnum = 2
43 class SVNCommitInternalInconsistencyError(Exception):
44 """Exception raised if we encounter an impossible state in the
45 SVNCommit Databases."""
47 pass
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
53 REVNUM.
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 []
75 if revnum:
76 self.revnum = revnum
77 else:
78 self.revnum = SVNCommit.revnum
79 SVNCommit.revnum += 1
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.
97 self.is_tag = None
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."""
119 self._log_msg = msg
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
130 def get_date(self):
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)
139 try:
140 utf8_author = None
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,
146 'svn:date' : date }
147 except UnicodeError:
148 Log().write(Log.WARN, '%s: problem encoding author or log message:'
149 % warning_prefix)
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(),
164 'svn:date' : date }
166 def add_revision(self, cvs_rev):
167 self.cvs_revs.append(cvs_rev)
169 def flush(self):
170 Log().write(Log.NORMAL, "Creating Subversion r%d (%s)"
171 % (self.revnum, self._description))
172 Ctx()._persistence_manager.put_svn_commit(self.revnum,
173 self.cvs_revs,
174 self._max_date,
175 self.symbolic_name,
176 self.motivating_revnum)
178 def __str__(self):
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)
187 + "\n")
188 else:
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"
194 return ret
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()
204 else:
205 return self._log_msg
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."""
212 type = 'branch'
213 if self.is_tag:
214 type = 'tag'
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
233 return msg