Document changes made since last edit of CHANGES.
[cvs2svn.git] / cvs2svn_lib / svn_commit_creator.py
blobf5fed92fb9bd04109aa686df12e8c348d0126f6d
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 SVNCommitCreator class."""
20 import time
22 from cvs2svn_lib.common import InternalError
23 from cvs2svn_lib.log import logger
24 from cvs2svn_lib.context import Ctx
25 from cvs2svn_lib.cvs_item import CVSRevisionNoop
26 from cvs2svn_lib.cvs_item import CVSBranchNoop
27 from cvs2svn_lib.cvs_item import CVSTagNoop
28 from cvs2svn_lib.changeset import OrderedChangeset
29 from cvs2svn_lib.changeset import BranchChangeset
30 from cvs2svn_lib.changeset import TagChangeset
31 from cvs2svn_lib.svn_commit import SVNInitialProjectCommit
32 from cvs2svn_lib.svn_commit import SVNPrimaryCommit
33 from cvs2svn_lib.svn_commit import SVNPostCommit
34 from cvs2svn_lib.svn_commit import SVNBranchCommit
35 from cvs2svn_lib.svn_commit import SVNTagCommit
36 from cvs2svn_lib.key_generator import KeyGenerator
39 class SVNCommitCreator:
40 """This class creates and yields SVNCommits via process_changeset()."""
42 def __init__(self):
43 # The revision number to assign to the next new SVNCommit.
44 self.revnum_generator = KeyGenerator()
46 # A set containing the Projects that have already been
47 # initialized:
48 self._initialized_projects = set()
50 def _post_commit(self, cvs_revs, motivating_revnum, timestamp):
51 """Generate any SVNCommits needed to follow CVS_REVS.
53 That is, handle non-trunk default branches. A revision on a CVS
54 non-trunk default branch is visible in a default CVS checkout of
55 HEAD. So we copy such commits over to Subversion's trunk so that
56 checking out SVN trunk gives the same output as checking out of
57 CVS's default branch."""
59 cvs_revs = [
60 cvs_rev
61 for cvs_rev in cvs_revs
62 if cvs_rev.ntdbr and not isinstance(cvs_rev, CVSRevisionNoop)
65 if cvs_revs:
66 cvs_revs.sort(
67 lambda a, b: cmp(a.cvs_file.rcs_path, b.cvs_file.rcs_path)
69 # Generate an SVNCommit for all of our default branch cvs_revs.
70 yield SVNPostCommit(
71 motivating_revnum, cvs_revs, timestamp,
72 self.revnum_generator.gen_id(),
75 def _process_revision_changeset(self, changeset, timestamp):
76 """Process CHANGESET, using TIMESTAMP as the commit time.
78 Create and yield one or more SVNCommits in the process. CHANGESET
79 must be an OrderedChangeset. TIMESTAMP is used as the timestamp
80 for any resulting SVNCommits."""
82 if not changeset.cvs_item_ids:
83 logger.warn('Changeset has no items: %r' % changeset)
84 return
86 logger.verbose('-' * 60)
87 logger.verbose('CVS Revision grouping:')
88 logger.verbose(' Time: %s' % time.ctime(timestamp))
90 # Generate an SVNCommit unconditionally. Even if the only change in
91 # this group of CVSRevisions is a deletion of an already-deleted
92 # file (that is, a CVS revision in state 'dead' whose predecessor
93 # was also in state 'dead'), the conversion will still generate a
94 # Subversion revision containing the log message for the second dead
95 # revision, because we don't want to lose that information.
97 cvs_revs = list(changeset.iter_cvs_items())
98 if cvs_revs:
99 cvs_revs.sort(lambda a, b: cmp(a.cvs_file.rcs_path, b.cvs_file.rcs_path))
100 svn_commit = SVNPrimaryCommit(
101 cvs_revs, timestamp, self.revnum_generator.gen_id()
104 yield svn_commit
106 for cvs_rev in cvs_revs:
107 Ctx()._symbolings_logger.log_revision(cvs_rev, svn_commit.revnum)
109 # Generate an SVNPostCommit if we have default branch revs. If
110 # some of the revisions in this commit happened on a non-trunk
111 # default branch, then those files have to be copied into trunk
112 # manually after being changed on the branch (because the RCS
113 # "default branch" appears as head, i.e., trunk, in practice).
114 # Unfortunately, Subversion doesn't support copies with sources
115 # in the current txn. All copies must be based in committed
116 # revisions. Therefore, we generate the copies in a new
117 # revision.
118 for svn_post_commit in self._post_commit(
119 cvs_revs, svn_commit.revnum, timestamp
121 yield svn_post_commit
123 def _process_tag_changeset(self, changeset, timestamp):
124 """Process TagChangeset CHANGESET, producing a SVNTagCommit.
126 Filter out CVSTagNoops. If no CVSTags are left, don't generate a
127 SVNTagCommit."""
129 if Ctx().trunk_only:
130 raise InternalError(
131 'TagChangeset encountered during a --trunk-only conversion')
133 cvs_tag_ids = [
134 cvs_tag.id
135 for cvs_tag in changeset.iter_cvs_items()
136 if not isinstance(cvs_tag, CVSTagNoop)
138 if cvs_tag_ids:
139 yield SVNTagCommit(
140 changeset.symbol, cvs_tag_ids, timestamp,
141 self.revnum_generator.gen_id(),
143 else:
144 logger.debug(
145 'Omitting %r because it contains only CVSTagNoops' % (changeset,)
148 def _process_branch_changeset(self, changeset, timestamp):
149 """Process BranchChangeset CHANGESET, producing a SVNBranchCommit.
151 Filter out CVSBranchNoops. If no CVSBranches are left, don't
152 generate a SVNBranchCommit."""
154 if Ctx().trunk_only:
155 raise InternalError(
156 'BranchChangeset encountered during a --trunk-only conversion')
158 cvs_branches = [
159 cvs_branch
160 for cvs_branch in changeset.iter_cvs_items()
161 if not isinstance(cvs_branch, CVSBranchNoop)
163 if cvs_branches:
164 svn_commit = SVNBranchCommit(
165 changeset.symbol,
166 [cvs_branch.id for cvs_branch in cvs_branches],
167 timestamp,
168 self.revnum_generator.gen_id(),
170 yield svn_commit
171 for cvs_branch in cvs_branches:
172 Ctx()._symbolings_logger.log_branch_revision(
173 cvs_branch, svn_commit.revnum
175 else:
176 logger.debug(
177 'Omitting %r because it contains only CVSBranchNoops' % (changeset,)
180 def process_changeset(self, changeset, timestamp):
181 """Process CHANGESET, using TIMESTAMP for all of its entries.
183 Return a generator that generates the resulting SVNCommits.
185 The changesets must be fed to this function in proper dependency
186 order."""
188 # First create any new projects that might be opened by the
189 # changeset:
190 projects_opened = \
191 changeset.get_projects_opened() - self._initialized_projects
192 if projects_opened:
193 if Ctx().cross_project_commits:
194 yield SVNInitialProjectCommit(
195 timestamp, projects_opened, self.revnum_generator.gen_id()
197 else:
198 for project in projects_opened:
199 yield SVNInitialProjectCommit(
200 timestamp, [project], self.revnum_generator.gen_id()
202 self._initialized_projects.update(projects_opened)
204 if isinstance(changeset, OrderedChangeset):
205 for svn_commit \
206 in self._process_revision_changeset(changeset, timestamp):
207 yield svn_commit
208 elif isinstance(changeset, TagChangeset):
209 for svn_commit in self._process_tag_changeset(changeset, timestamp):
210 yield svn_commit
211 elif isinstance(changeset, BranchChangeset):
212 for svn_commit in self._process_branch_changeset(changeset, timestamp):
213 yield svn_commit
214 else:
215 raise TypeError('Illegal changeset %r' % changeset)