* cvs2svn: Use gnu_getopt when available (Python >= 2.3) for more flexible
[cvs2svn.git] / cvs2svn_lib / openings_closings.py
blobb9c37e521a902189d65661d1f165aa9bd4966fcf
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 database facilities used by cvs2svn."""
20 import fileinput
22 from boolean import *
23 import common
24 import config
25 from context import Ctx
26 from artifact_manager import artifact_manager
27 import database
28 from cvs_file_database import CVSFileDatabase
29 from cvs_revision_database import CVSRevisionDatabase
30 from svn_revision_range import SVNRevisionRange
33 # Constants used in SYMBOL_OPENINGS_CLOSINGS
34 OPENING = 'O'
35 CLOSING = 'C'
38 class SymbolingsLogger:
39 """Manage the file that contains lines for symbol openings and
40 closings.
42 This data will later be used to determine valid SVNRevision ranges
43 from which a file can be copied when creating a branch or tag in
44 Subversion. Do this by finding "Openings" and "Closings" for each
45 file copied onto a branch or tag.
47 An "Opening" is the CVSRevision from which a given branch/tag
48 sprouts on a path.
50 The "Closing" for that branch/tag and path is the next CVSRevision
51 on the same line of development as the opening.
53 For example, on file 'foo.c', branch BEE has branch number 1.2.2 and
54 obviously sprouts from revision 1.2. Therefore, 1.2 is the opening
55 for BEE on path 'foo.c', and 1.3 is the closing for BEE on path
56 'foo.c'. Note that there may be many revisions chronologically
57 between 1.2 and 1.3, for example, revisions on branches of 'foo.c',
58 perhaps even including on branch BEE itself. But 1.3 is the next
59 revision *on the same line* as 1.2, that is why it is the closing
60 revision for those symbolic names of which 1.2 is the opening.
62 The reason for doing all this hullabaloo is to make branch and tag
63 creation as efficient as possible by minimizing the number of copies
64 and deletes per creation. For example, revisions 1.2 and 1.3 of
65 foo.c might correspond to revisions 17 and 30 in Subversion. That
66 means that when creating branch BEE, there is some motivation to do
67 the copy from one of 17-30. Now if there were another file,
68 'bar.c', whose opening and closing CVSRevisions for BEE corresponded
69 to revisions 24 and 39 in Subversion, we would know that the ideal
70 thing would be to copy the branch from somewhere between 24 and 29,
71 inclusive.
72 """
74 def __init__(self):
75 self.symbolings = open(
76 artifact_manager.get_temp_file(config.SYMBOL_OPENINGS_CLOSINGS), 'w')
77 self.closings = open(
78 artifact_manager.get_temp_file(config.SYMBOL_CLOSINGS_TMP), 'w')
80 # This keys of this dictionary are *source* cvs_paths for which
81 # we've encountered an 'opening' on the default branch. The
82 # values are the (uncleaned) symbolic names that this path has
83 # opened.
84 self.open_paths_with_default_branches = { }
86 def log_revision(self, c_rev, svn_revnum):
87 """Log any openings found in C_REV, and if C_REV.next_rev is not
88 None, a closing. The opening uses SVN_REVNUM, but the closing (if
89 any) will have its revnum determined later."""
91 for name in c_rev.tags + c_rev.branches:
92 self._note_default_branch_opening(c_rev, name)
93 if c_rev.op != common.OP_DELETE:
94 self._log(name, svn_revnum,
95 c_rev.cvs_path, c_rev.branch_name, OPENING)
97 # If our c_rev has a next_rev, then that's the closing rev for
98 # this source revision. Log it to closings for later processing
99 # since we don't know the svn_revnum yet.
100 if c_rev.next_rev is not None:
101 self.closings.write('%s %s\n' % (name, c_rev.next_rev.unique_key()))
103 def _log(self, name, svn_revnum, cvs_path, branch_name, type):
104 """Write out a single line to the symbol_openings_closings file
105 representing that SVN_REVNUM of SVN_PATH on BRANCH_NAME is either the
106 opening or closing (TYPE) of NAME (a symbolic name).
108 TYPE should only be one of the following constants: OPENING or
109 CLOSING."""
111 # 8 places gives us 999,999,999 SVN revs. That *should* be enough.
112 self.symbolings.write(
113 '%s %.8d %s %s %s\n'
114 % (name, svn_revnum, type, branch_name or '*', cvs_path))
116 def close(self):
117 """Iterate through the closings file, lookup the svn_revnum for
118 each closing CVSRevision, and write a proper line out to the
119 symbolings file."""
121 # Use this to get the c_rev of our rev_key
122 cvs_file_db = CVSFileDatabase(
123 artifact_manager.get_temp_file(config.CVS_FILES_DB),
124 database.DB_OPEN_READ)
125 cvs_revs_db = CVSRevisionDatabase(
126 cvs_file_db,
127 artifact_manager.get_temp_file(config.CVS_REVS_RESYNC_DB),
128 database.DB_OPEN_READ)
130 self.closings.close()
131 for line in fileinput.FileInput(
132 artifact_manager.get_temp_file(config.SYMBOL_CLOSINGS_TMP)):
133 (name, rev_key) = line.rstrip().split(" ", 1)
134 svn_revnum = Ctx()._persistence_manager.get_svn_revnum(rev_key)
136 c_rev = cvs_revs_db.get_revision(rev_key)
137 self._log(name, svn_revnum, c_rev.cvs_path, c_rev.branch_name, CLOSING)
139 self.symbolings.close()
141 def _note_default_branch_opening(self, c_rev, symbolic_name):
142 """If C_REV is a default branch revision, log C_REV.cvs_path as an
143 opening for SYMBOLIC_NAME."""
145 self.open_paths_with_default_branches.setdefault(
146 c_rev.cvs_path, []).append(symbolic_name)
148 def log_default_branch_closing(self, c_rev, svn_revnum):
149 """If self.open_paths_with_default_branches contains
150 C_REV.cvs_path, then call log each name in
151 self.open_paths_with_default_branches[C_REV.cvs_path] as a closing
152 with SVN_REVNUM as the closing revision number."""
154 path = c_rev.cvs_path
155 if self.open_paths_with_default_branches.has_key(path):
156 # log each symbol as a closing
157 for name in self.open_paths_with_default_branches[path]:
158 self._log(name, svn_revnum, path, None, CLOSING)
159 # Remove them from the openings list as we're done with them.
160 del self.open_paths_with_default_branches[path]
163 class OpeningsClosingsMap:
164 """A dictionary of openings and closings for a symbolic name in the
165 current SVNCommit.
167 The user should call self.register() for the openings and closings,
168 then self.get_node_tree() to retrieve the information as a
169 SymbolicNameFillingGuide."""
171 def __init__(self, symbolic_name):
172 """Initialize OpeningsClosingsMap and prepare it for receiving
173 openings and closings."""
175 self.name = symbolic_name
177 # A dictionary of SVN_PATHS to SVNRevisionRange objects.
178 self.things = { }
180 def register(self, svn_path, svn_revnum, type):
181 """Register an opening or closing revision for this symbolic name.
182 SVN_PATH is the source path that needs to be copied into
183 self.symbolic_name, and SVN_REVNUM is either the first svn
184 revision number that we can copy from (our opening), or the last
185 (not inclusive) svn revision number that we can copy from (our
186 closing). TYPE indicates whether this path is an opening or a a
187 closing.
189 The opening for a given SVN_PATH must be passed before the closing
190 for it to have any effect... any closing encountered before a
191 corresponding opening will be discarded.
193 It is not necessary to pass a corresponding closing for every
194 opening."""
196 # Always log an OPENING
197 if type == OPENING:
198 self.things[svn_path] = SVNRevisionRange(svn_revnum)
199 # Only log a closing if we've already registered the opening for that
200 # path.
201 elif type == CLOSING and self.things.has_key(svn_path):
202 self.things[svn_path].add_closing(svn_revnum)
204 def is_empty(self):
205 """Return true if we haven't accumulated any openings or closings,
206 false otherwise."""
208 return not len(self.things)
210 def get_things(self):
211 """Return a list of (svn_path, SVNRevisionRange) tuples for all
212 svn_paths with registered openings or closings."""
214 return self.things.items()