* www/faq.html: Update link to SourceForge info about converting CVS -> SVN.
[cvs2svn.git] / cvs2svn_lib / svn_commit.py
blob25dc38ef60441cb2539fa9775c7adbe6ff8ca275
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2008 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 SVNCommit classes.
19 There are five types of SVNCommits:
21 SVNInitialProjectCommit -- Initializes a project (creates its trunk,
22 branches, and tags directories).
24 SVNPrimaryCommit -- Commits one or more CVSRevisions on one or more
25 lines of development.
27 SVNBranchCommit -- Creates or fills a branch; that is, copies files
28 from a source line of development to a target branch.
30 SVNTagCommit -- Creates or fills a tag; that is, copies files from a
31 source line of development to a target tag.
33 SVNPostCommit -- Updates trunk to reflect changes on a non-trunk
34 default branch.
36 """
39 import textwrap
41 from cvs2svn_lib.common import InternalError
42 from cvs2svn_lib.context import Ctx
43 from cvs2svn_lib.symbol import Branch
44 from cvs2svn_lib.symbol import Tag
47 class SVNCommit:
48 """This represents one commit to the Subversion Repository."""
50 # textwrap.TextWrapper instance to be used for wrapping log messages:
51 text_wrapper = textwrap.TextWrapper(width=76)
53 def __init__(self, date, revnum):
54 """Instantiate an SVNCommit.
56 REVNUM is the SVN revision number of this commit."""
58 # The date of the commit, as an integer. While the SVNCommit is
59 # being built up, this contains the latest date seen so far. This
60 # member is set externally.
61 self.date = date
63 # The SVN revision number of this commit, as an integer.
64 self.revnum = revnum
66 def __getstate__(self):
67 return (self.date, self.revnum,)
69 def __setstate__(self, state):
70 (self.date, self.revnum,) = state
72 def get_cvs_items(self):
73 """Return a list containing the CVSItems in this commit."""
75 raise NotImplementedError()
77 def get_author(self):
78 """Return the author or this commit, or None if none is to be used.
80 The return value is exactly as the author appeared in the RCS
81 file, with undefined character encoding."""
83 raise NotImplementedError()
85 def get_log_msg(self):
86 """Return a log message for this commit.
88 The return value is exactly as the log message appeared in the RCS
89 file, with undefined character encoding."""
91 raise NotImplementedError()
93 def get_warning_summary(self):
94 """Return a summary of this commit that can be used in warnings."""
96 return '(subversion rev %s)' % (self.revnum,)
98 def get_description(self):
99 """Return a partial description of this SVNCommit, for logging."""
101 raise NotImplementedError()
103 def output(self, output_option):
104 """Cause this commit to be output to OUTPUT_OPTION.
106 This method is used for double-dispatch. Derived classes should
107 call the OutputOption.process_*_commit() method appropriate for
108 the type of SVNCommit."""
110 raise NotImplementedError()
112 def __str__(self):
113 """ Print a human-readable description of this SVNCommit.
115 This description is not intended to be machine-parseable."""
117 ret = "SVNCommit #: " + str(self.revnum) + "\n"
118 ret += " debug description: " + self.get_description() + "\n"
119 return ret
122 class SVNInitialProjectCommit(SVNCommit):
123 def __init__(self, date, projects, revnum):
124 SVNCommit.__init__(self, date, revnum)
125 self.projects = list(projects)
127 def __getstate__(self):
128 return (
129 SVNCommit.__getstate__(self),
130 [project.id for project in self.projects],
133 def __setstate__(self, state):
134 (svn_commit_state, project_ids,) = state
135 SVNCommit.__setstate__(self, svn_commit_state)
136 self.projects = [
137 Ctx()._projects[project_id] for project_id in project_ids
140 def get_cvs_items(self):
141 return []
143 def get_author(self):
144 return Ctx().username
146 def get_log_msg(self):
147 return self.text_wrapper.fill(
148 Ctx().initial_project_commit_message % {}
151 def get_description(self):
152 return 'Project initialization'
154 def output(self, output_option):
155 output_option.process_initial_project_commit(self)
158 class SVNRevisionCommit(SVNCommit):
159 """A SVNCommit that includes actual CVS revisions."""
161 def __init__(self, cvs_revs, date, revnum):
162 SVNCommit.__init__(self, date, revnum)
164 self.cvs_revs = list(cvs_revs)
166 # This value is set lazily by _get_metadata():
167 self._metadata = None
169 def __getstate__(self):
170 """Return the part of the state represented by this mixin."""
172 return (
173 SVNCommit.__getstate__(self),
174 [cvs_rev.id for cvs_rev in self.cvs_revs],
177 def __setstate__(self, state):
178 """Restore the part of the state represented by this mixin."""
180 (svn_commit_state, cvs_rev_ids) = state
181 SVNCommit.__setstate__(self, svn_commit_state)
183 self.cvs_revs = [
184 cvs_rev
185 for (id, cvs_rev) in Ctx()._cvs_items_db.get_many(cvs_rev_ids)
187 self._metadata = None
189 def get_cvs_items(self):
190 return self.cvs_revs
192 def _get_metadata(self):
193 """Return the Metadata instance for this commit."""
195 if self._metadata is None:
196 # Set self._metadata for this commit from that of the first cvs
197 # revision.
198 if not self.cvs_revs:
199 raise InternalError('SVNPrimaryCommit contains no CVS revisions')
201 metadata_id = self.cvs_revs[0].metadata_id
202 self._metadata = Ctx()._metadata_db[metadata_id]
204 return self._metadata
206 def get_author(self):
207 return self._get_metadata().author
209 def get_warning_summary(self):
210 retval = []
211 retval.append(SVNCommit.get_warning_summary(self) + ' Related files:')
212 for cvs_rev in self.cvs_revs:
213 retval.append(' ' + cvs_rev.cvs_file.filename)
214 return '\n'.join(retval)
216 def __str__(self):
217 """Return the revision part of a description of this SVNCommit.
219 Derived classes should append the output of this method to the
220 output of SVNCommit.__str__()."""
222 ret = []
223 ret.append(SVNCommit.__str__(self))
224 ret.append(' cvs_revs:\n')
225 for cvs_rev in self.cvs_revs:
226 ret.append(' %x\n' % (cvs_rev.id,))
227 return ''.join(ret)
230 class SVNPrimaryCommit(SVNRevisionCommit):
231 def __init__(self, cvs_revs, date, revnum):
232 SVNRevisionCommit.__init__(self, cvs_revs, date, revnum)
234 def get_log_msg(self):
235 """Return the actual log message for this commit."""
237 return self._get_metadata().log_msg
239 def get_description(self):
240 return 'commit'
242 def output(self, output_option):
243 output_option.process_primary_commit(self)
246 class SVNPostCommit(SVNRevisionCommit):
247 def __init__(self, motivating_revnum, cvs_revs, date, revnum):
248 SVNRevisionCommit.__init__(self, cvs_revs, date, revnum)
250 # The subversion revision number of the *primary* commit where the
251 # default branch changes actually happened. (NOTE: Secondary
252 # commits that fill branches and tags also have a motivating
253 # commit, but we do not record it because it is (currently) not
254 # needed for anything.) motivating_revnum is used when generating
255 # the log message for the commit that synchronizes the default
256 # branch with trunk.
258 # It is possible for multiple synchronization commits to refer to
259 # the same motivating commit revision number, and it is possible
260 # for a single synchronization commit to contain CVSRevisions on
261 # multiple different default branches.
262 self.motivating_revnum = motivating_revnum
264 def __getstate__(self):
265 return (
266 SVNRevisionCommit.__getstate__(self),
267 self.motivating_revnum,
270 def __setstate__(self, state):
271 (rev_state, self.motivating_revnum,) = state
272 SVNRevisionCommit.__setstate__(self, rev_state)
274 def get_cvs_items(self):
275 # It might seem that we should return
276 # SVNRevisionCommit.get_cvs_items(self) here, but this commit
277 # doesn't really include those CVSItems, but rather followup
278 # commits to those.
279 return []
281 def get_log_msg(self):
282 """Return a manufactured log message for this commit."""
284 return self.text_wrapper.fill(
285 Ctx().post_commit_message % {'revnum' : self.motivating_revnum}
288 def get_description(self):
289 return 'post-commit default branch(es)'
291 def output(self, output_option):
292 output_option.process_post_commit(self)
295 class SVNSymbolCommit(SVNCommit):
296 def __init__(self, symbol, cvs_symbol_ids, date, revnum):
297 SVNCommit.__init__(self, date, revnum)
299 # The TypedSymbol that is filled in this SVNCommit.
300 self.symbol = symbol
302 self.cvs_symbol_ids = cvs_symbol_ids
304 def __getstate__(self):
305 return (
306 SVNCommit.__getstate__(self),
307 self.symbol.id, self.cvs_symbol_ids,
310 def __setstate__(self, state):
311 (svn_commit_state, symbol_id, self.cvs_symbol_ids) = state
312 SVNCommit.__setstate__(self, svn_commit_state)
313 self.symbol = Ctx()._symbol_db.get_symbol(symbol_id)
315 def get_cvs_items(self):
316 return [
317 cvs_symbol
318 for (id, cvs_symbol)
319 in Ctx()._cvs_items_db.get_many(self.cvs_symbol_ids)
322 def _get_symbol_type(self):
323 """Return the type of the self.symbol ('branch' or 'tag')."""
325 raise NotImplementedError()
327 def get_author(self):
328 return Ctx().username
330 def get_log_msg(self):
331 """Return a manufactured log message for this commit."""
333 return self.text_wrapper.fill(
334 Ctx().symbol_commit_message % {
335 'symbol_type' : self._get_symbol_type(),
336 'symbol_name' : self.symbol.name,
340 def get_description(self):
341 return 'copying to %s %r' % (self._get_symbol_type(), self.symbol.name,)
343 def __str__(self):
344 """ Print a human-readable description of this SVNCommit.
346 This description is not intended to be machine-parseable."""
348 return (
349 SVNCommit.__str__(self)
350 + " symbolic name: %s\n" % (self.symbol.name,)
354 class SVNBranchCommit(SVNSymbolCommit):
355 def __init__(self, symbol, cvs_symbol_ids, date, revnum):
356 if not isinstance(symbol, Branch):
357 raise InternalError('Incorrect symbol type %r' % (symbol,))
359 SVNSymbolCommit.__init__(self, symbol, cvs_symbol_ids, date, revnum)
361 def _get_symbol_type(self):
362 return 'branch'
364 def output(self, output_option):
365 output_option.process_branch_commit(self)
368 class SVNTagCommit(SVNSymbolCommit):
369 def __init__(self, symbol, cvs_symbol_ids, date, revnum):
370 if not isinstance(symbol, Tag):
371 raise InternalError('Incorrect symbol type %r' % (symbol,))
373 SVNSymbolCommit.__init__(self, symbol, cvs_symbol_ids, date, revnum)
375 def _get_symbol_type(self):
376 return 'tag'
378 def output(self, output_option):
379 output_option.process_tag_commit(self)