Make minimal changes to get HTML files to be valid XHTML, dropping from Strict
[cvs2svn.git] / cvs2svn_lib / cvs_revision.py
blobfc522b2285b78a6c86b46b08d013b30f5dd5db5d
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 classes to store CVS revisions."""
20 import os
22 from boolean import *
23 import common
26 class CVSRevisionID(object):
27 """An object that identifies a CVS revision of a file."""
29 def __init__(self, fname, rev):
30 self.fname = fname
31 self.rev = rev
33 def unique_key(self):
34 """Return a string that can be used as a unique key for this revision.
36 The 'primary key' of a CVS Revision is (revision number,
37 filename). To make a key (say, for a dict), we just glom them
38 together into a string."""
40 return self.rev + '/' + self.fname
43 class CVSRevision(CVSRevisionID):
44 """Information about a single CVS revision.
46 A CVSRevision holds the information known about a single version of
47 a single file.
49 ctx is the context to use for instances of CVSRevision, or None. If
50 ctx is None, the following properties of instantiated CVSRevision
51 class objects will be unavailable (or simply will not work
52 correctly, if at all):
54 cvs_path
55 svn_path
56 is_default_branch_revision()
58 (Note that this class treats ctx as const, because the caller
59 likely passed in a Borg instance of a Ctx. The reason this class
60 stores a Ctx instance, instead of just instantiating a Ctx itself,
61 is that this class should be usable outside cvs2svn.)
62 """
64 ctx = None
66 def __init__(self,
67 timestamp, digest, prev_timestamp, next_timestamp,
68 op, prev_rev, rev, next_rev,
69 file_in_attic, file_executable,
70 file_size, deltatext_exists,
71 fname, mode, branch_name, tags, branches):
72 """Initialize a new CVSRevision object.
74 Arguments:
75 TIMESTAMP --> (int) date stamp for this cvs revision
76 DIGEST --> (string) digest of author+logmsg
77 PREV_TIMESTAMP --> (int) date stamp for the previous cvs revision
78 NEXT_TIMESTAMP --> (int) date stamp for the next cvs revision
79 OP --> (char) OP_ADD, OP_CHANGE, or OP_DELETE
80 PREV_REV --> (string or None) previous CVS rev, e.g., '1.2'.
81 This is converted to a CVSRevisionID instance.
82 REV --> (string) this CVS rev, e.g., '1.3'
83 NEXT_REV --> (string or None) next CVS rev, e.g., '1.4'.
84 This is converted to a CVSRevisionID instance.
85 FILE_IN_ATTIC --> (bool) true iff RCS file is in Attic
86 FILE_EXECUTABLE --> (bool) true iff RCS file has exec bit set.
87 FILE_SIZE --> (int) size of the RCS file
88 DELTATEXT_EXISTS--> (bool) true iff non-empty deltatext
89 FNAME --> (string) relative path of file in CVS repos
90 MODE --> (string or None) 'kkv', 'kb', etc.
91 BRANCH_NAME --> (string or None) branch on which this rev occurred
92 TAGS --> (list of strings) all tags on this revision
93 BRANCHES --> (list of strings) all branches rooted in this rev
95 WARNING: Due to the resync process in pass2, prev_timestamp or
96 next_timestamp may be incorrect in the c-revs or s-revs files."""
98 CVSRevisionID.__init__(self, fname, rev)
100 self.timestamp = timestamp
101 self.digest = digest
102 self.prev_timestamp = prev_timestamp
103 self.next_timestamp = next_timestamp
104 self.op = op
105 self.prev_rev = prev_rev and CVSRevisionID(self.fname, prev_rev)
106 self.next_rev = next_rev and CVSRevisionID(self.fname, next_rev)
107 self.file_in_attic = file_in_attic
108 self.file_executable = file_executable
109 self.file_size = file_size
110 self.deltatext_exists = deltatext_exists
111 self.mode = mode
112 self.branch_name = branch_name
113 self.tags = tags
114 self.branches = branches
116 def get_cvs_path(self):
117 return self.ctx.cvs_repository.get_cvs_path(self.fname)
119 cvs_path = property(get_cvs_path)
121 def get_svn_path(self):
122 if self.branch_name:
123 return self.ctx.project.make_branch_path(
124 self.branch_name, self.cvs_path)
125 else:
126 return self.ctx.project.make_trunk_path(self.cvs_path)
128 svn_path = property(get_svn_path)
130 def __getstate__(self):
131 """Return the contents of this instance, encoded as a string.
133 The format of the output (a single string, without newlines, with
134 particular field order) is important for the correct behavior of
135 the various REVS_DATAFILEs."""
137 def timestamp_to_string(timestamp):
138 if timestamp:
139 return '%08lx' % timestamp
140 else:
141 return '*'
143 def rev_to_string(rev):
144 if rev is None:
145 return '*'
146 else:
147 return rev.rev
149 def bool_to_string(v):
150 return { True : 'T', False : 'F' }[bool(v)]
152 def list_to_string(l):
153 """Format list L as a count of elements followed by the elements
154 themselves, each separated by spaces."""
156 return ' '.join([str(len(l))] + l)
158 return ('%s %s %s %s %s %s %s %s %s %s %d %s %s %s %s %s %s'
159 % (timestamp_to_string(self.timestamp),
160 self.digest,
161 timestamp_to_string(self.prev_timestamp),
162 timestamp_to_string(self.next_timestamp),
163 self.op,
164 rev_to_string(self.prev_rev),
165 self.rev,
166 rev_to_string(self.next_rev),
167 bool_to_string(self.file_in_attic),
168 bool_to_string(self.file_executable),
169 self.file_size,
170 bool_to_string(self.deltatext_exists),
171 self.mode or '*',
172 self.branch_name or '*',
173 list_to_string(self.tags),
174 list_to_string(self.branches),
175 self.fname,))
177 def __setstate__(self, state):
178 self.__init__(*_parse_cvs_revision_state(state))
180 def opens_symbolic_name(self, name):
181 """Return True iff this CVSRevision is the opening CVSRevision for
182 NAME (for this RCS file)."""
184 if name in self.tags:
185 return True
186 if name in self.branches:
187 # If this c_rev opens a branch and our op is OP_DELETE, then
188 # that means that the file that this c_rev belongs to was
189 # created on the branch, so for all intents and purposes, this
190 # c_rev is *technically* not an opening. See Issue #62 for more
191 # information.
192 if self.op != common.OP_DELETE:
193 return True
194 return False
196 def is_default_branch_revision(self):
197 """Return True iff SELF.rev of SELF.cvs_path is a default branch
198 revision according to DEFAULT_BRANCHES_DB (see the conditions
199 documented there)."""
201 val = self.ctx._default_branches_db.get(self.cvs_path, None)
202 if val is not None:
203 val_last_dot = val.rindex(".")
204 our_last_dot = self.rev.rindex(".")
205 default_branch = val[:val_last_dot]
206 our_branch = self.rev[:our_last_dot]
207 default_rev_component = int(val[val_last_dot + 1:])
208 our_rev_component = int(self.rev[our_last_dot + 1:])
209 if (default_branch == our_branch
210 and our_rev_component <= default_rev_component):
211 return True
213 return False
215 def rcs_path(self):
216 """Returns the actual filesystem path to the RCS file of this
217 CVSRevision."""
219 if self.file_in_attic:
220 basepath, filename = os.path.split(self.fname)
221 return os.path.join(basepath, 'Attic', filename)
222 else:
223 return self.fname
225 def filename(self):
226 """Return the last path component of self.fname, minus the ',v'."""
228 return os.path.split(self.fname)[-1][:-2]
231 def _parse_cvs_revision_state(line):
232 """Parse LINE into the constructor arguments needed to create a
233 CVSRevision object and return the arguments as a tuple. LINE is a
234 string in the format of a line from a revs file. It should *not*
235 include a trailing newline."""
237 def string_to_timestamp(s):
238 if s == '*':
239 return 0
240 else:
241 return int(s, 16)
243 def string_to_rev(s):
244 if s == '*':
245 return None
246 else:
247 return s
249 def string_to_bool(s):
250 return { 'T' : True, 'F' : False }[s]
252 (timestamp, digest, prev_timestamp, next_timestamp,
253 op, prev_rev, rev, next_rev, file_in_attic,
254 file_executable, file_size, deltatext_exists,
255 mode, branch_name, numtags, remainder) = line.split(' ', 15)
257 # Patch up items which are not simple strings:
258 timestamp = string_to_timestamp(timestamp)
259 prev_timestamp = string_to_timestamp(prev_timestamp)
260 next_timestamp = string_to_timestamp(next_timestamp)
262 prev_rev = string_to_rev(prev_rev)
263 next_rev = string_to_rev(next_rev)
265 file_in_attic = string_to_bool(file_in_attic)
266 file_executable = string_to_bool(file_executable)
268 file_size = long(file_size)
270 deltatext_exists = string_to_bool(deltatext_exists)
272 if mode == "*":
273 mode = None
275 if branch_name == "*":
276 branch_name = None
278 numtags = int(numtags)
279 tags_and_numbranches_and_remainder = remainder.split(' ', numtags + 1)
280 tags = tags_and_numbranches_and_remainder[:-2]
281 numbranches = int(tags_and_numbranches_and_remainder[-2])
282 remainder = tags_and_numbranches_and_remainder[-1]
283 branches_and_fname = remainder.split(' ', numbranches)
284 branches = branches_and_fname[:-1]
285 fname = branches_and_fname[-1]
287 return (timestamp, digest, prev_timestamp, next_timestamp,
288 op, prev_rev, rev, next_rev,
289 file_in_attic, file_executable, file_size, deltatext_exists,
290 fname, mode, branch_name, tags, branches,)
293 def parse_cvs_revision(line):
294 """Parse LINE into a CVSRevision instance and return the instance.
295 LINE is a string in the format of a line from a revs file. It
296 should *not* include a trailing newline."""
298 return CVSRevision(*_parse_cvs_revision_state(line))