9
9
# For more information, visit http://viewvc.org/
11
11
# -----------------------------------------------------------------------
14
This is a Version Control library driver for locally accessible cvs-repositories.
27
### The functionality shared with bincvs should probably be moved to a
29
from vclib.bincvs import CVSRepository, Revision, Tag, \
32
class CCVSRepository(CVSRepository):
33
def dirlogs(self, path_parts, rev, entries, options):
34
"""see vclib.Repository.dirlogs docstring
36
rev can be a tag name or None. if set only information from revisions
37
matching the tag will be retrieved
39
Option values recognized by this implementation:
42
boolean. true to fetch logs of the most recently modified file in each
45
Option values returned by this implementation:
47
cvs_tags, cvs_branches
48
lists of tag and branch names encountered in the directory
50
subdirs = options.get('cvs_subdirs', 0)
52
dirpath = self._getpath(path_parts)
53
alltags = { # all the tags seen in the files of this dir
59
entry.rev = entry.date = entry.author = entry.dead = entry.log = None
60
path = _log_path(entry, dirpath, subdirs)
64
rcsparse.Parser().parse(open(path, 'rb'), InfoSink(entry, rev, alltags))
66
entry.errors.append("rcsparse error: %s" % e)
67
except RuntimeError, e:
68
entry.errors.append("rcsparse error: %s" % e)
69
except rcsparse.RCSStopParser:
72
branches = options['cvs_branches'] = []
73
tags = options['cvs_tags'] = []
74
for name, rev in alltags.items():
75
if Tag(None, rev).is_branch:
80
def itemlog(self, path_parts, rev, options):
81
"""see vclib.Repository.itemlog docstring
83
rev parameter can be a revision number, a branch number, a tag name,
84
or None. If None, will return information about all revisions, otherwise,
85
will only return information about the specified revision or branch.
87
Option values returned by this implementation:
90
dictionary of Tag objects for all tags encountered
92
path = self.rcsfile(path_parts, 1)
94
rcsparse.Parser().parse(open(path, 'rb'), sink)
95
filtered_revs = _file_log(sink.revs.values(), sink.tags,
96
sink.default_branch, rev)
97
for rev in filtered_revs:
98
if rev.prev and len(rev.number) == 2:
99
rev.changed = rev.prev.next_changed
100
options['cvs_tags'] = sink.tags
104
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
105
temp1 = tempfile.mktemp()
106
open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
107
temp2 = tempfile.mktemp()
108
open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
110
r1 = self.itemlog(path_parts1, rev1, {})[-1]
111
r2 = self.itemlog(path_parts2, rev2, {})[-1]
113
info1 = (self.rcsfile(path_parts1, root=1, v=0), r1.date, r1.string)
114
info2 = (self.rcsfile(path_parts2, root=1, v=0), r2.date, r2.string)
116
diff_args = vclib._diff_args(type, options)
118
return vclib._diff_fp(temp1, temp2, info1, info2, diff_args)
120
def annotate(self, path_parts, rev=None):
121
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
122
return source, source.revision
124
def openfile(self, path_parts, rev=None):
125
path = self.rcsfile(path_parts, 1)
127
rcsparse.Parser().parse(open(path, 'rb'), sink)
128
revision = sink.last and sink.last.string
129
return cStringIO.StringIO(string.join(sink.sstext.text, "\n")), revision
131
class MatchingSink(rcsparse.Sink):
132
"""Superclass for sinks that search for revisions based on tag or number"""
134
def __init__(self, find):
135
"""Initialize with tag name or revision number string to match against"""
136
if not find or find == 'MAIN' or find == 'HEAD':
143
def set_principal_branch(self, branch_number):
144
if self.find is None:
145
self.find_tag = Tag(None, branch_number)
147
def define_tag(self, name, revision):
148
if name == self.find:
149
self.find_tag = Tag(None, revision)
151
def admin_completed(self):
152
if self.find_tag is None:
153
if self.find is None:
154
self.find_tag = Tag(None, '')
157
self.find_tag = Tag(None, self.find)
161
class InfoSink(MatchingSink):
162
def __init__(self, entry, tag, alltags):
163
MatchingSink.__init__(self, tag)
165
self.alltags = alltags
166
self.matching_rev = None
167
self.perfect_match = 0
169
def define_tag(self, name, revision):
170
MatchingSink.define_tag(self, name, revision)
171
self.alltags[name] = revision
173
def admin_completed(self):
174
MatchingSink.admin_completed(self)
175
if self.find_tag is None:
176
# tag we're looking for doesn't exist
177
raise rcsparse.RCSStopParser
179
def define_revision(self, revision, date, author, state, branches, next):
180
if self.perfect_match:
184
rev = Revision(revision, date, author, state == "dead")
186
# perfect match if revision number matches tag number or if revision is on
187
# trunk and tag points to trunk. imperfect match if tag refers to a branch
188
# and this revision is the highest revision so far found on that branch
189
perfect = ((rev.number == tag.number) or
190
(not tag.number and len(rev.number) == 2))
191
if perfect or (tag.is_branch and tag.number == rev.number[:-1] and
192
(not self.matching_rev or
193
rev.number > self.matching_rev.number)):
194
self.matching_rev = rev
195
self.perfect_match = perfect
197
def set_revision_info(self, revision, log, text):
198
if self.matching_rev:
199
if revision == self.matching_rev.string:
200
self.entry.rev = self.matching_rev.string
201
self.entry.date = self.matching_rev.date
202
self.entry.author = self.matching_rev.author
203
self.entry.dead = self.matching_rev.dead
205
raise rcsparse.RCSStopParser
207
raise rcsparse.RCSStopParser
209
class TreeSink(rcsparse.Sink):
210
d_command = re.compile('^d(\d+)\\s(\\d+)')
211
a_command = re.compile('^a(\d+)\\s(\\d+)')
217
self.default_branch = None
219
def set_head_revision(self, revision):
222
def set_principal_branch(self, branch_number):
223
self.default_branch = branch_number
225
def define_tag(self, name, revision):
226
# check !tags.has_key(tag_name)
227
self.tags[name] = revision
229
def define_revision(self, revision, date, author, state, branches, next):
230
# check !revs.has_key(revision)
231
self.revs[revision] = Revision(revision, date, author, state == "dead")
233
def set_revision_info(self, revision, log, text):
234
# check revs.has_key(revision)
235
rev = self.revs[revision]
241
if self.head != revision:
243
lines = string.split(text, '\n')
245
while idx < len(lines):
247
dmatch = self.d_command.match(command)
250
deled = deled + string.atoi(dmatch.group(2))
252
amatch = self.a_command.match(command)
254
count = string.atoi(amatch.group(2))
255
added = added + count
258
raise "error while parsing deltatext: %s" % command
260
if len(rev.number) == 2:
261
rev.next_changed = changed and "+%i -%i" % (deled, added)
263
rev.changed = changed and "+%i -%i" % (added, deled)
266
d_command = re.compile('^d(\d+)\\s(\\d+)')
267
a_command = re.compile('^a(\d+)\\s(\\d+)')
269
def __init__(self, text):
270
self.text = string.split(text, "\n")
272
def command(self, cmd):
274
add_lines_remaining = 0
275
diffs = string.split(cmd, "\n")
282
for command in diffs:
283
if add_lines_remaining > 0:
284
# Insertion lines from a prior "a" command
285
self.text.insert(start_line + adjust, command)
286
add_lines_remaining = add_lines_remaining - 1
289
dmatch = self.d_command.match(command)
290
amatch = self.a_command.match(command)
292
# "d" - Delete command
293
start_line = string.atoi(dmatch.group(1))
294
count = string.atoi(dmatch.group(2))
295
begin = start_line + adjust - 1
296
del self.text[begin:begin + count]
297
adjust = adjust - count
300
start_line = string.atoi(amatch.group(1))
301
count = string.atoi(amatch.group(2))
302
add_lines_remaining = count
304
raise RuntimeError, 'Error parsing diff commands'
306
def secondnextdot(s, start):
307
# find the position the second dot after the start index.
308
return string.find(s, '.', string.find(s, '.', start) + 1)
311
class COSink(MatchingSink):
312
def __init__(self, rev):
313
MatchingSink.__init__(self, rev)
315
def set_head_revision(self, revision):
316
self.head = Revision(revision)
320
def admin_completed(self):
321
MatchingSink.admin_completed(self)
322
if self.find_tag is None:
323
raise vclib.InvalidRevision(self.find)
325
def set_revision_info(self, revision, log, text):
327
rev = Revision(revision)
329
if rev.number == tag.number:
332
depth = len(rev.number)
334
if rev.number == self.head.number:
335
assert self.sstext is None
336
self.sstext = StreamText(text)
337
elif (depth == 2 and tag.number and rev.number >= tag.number[:depth]):
338
assert len(self.last.number) == 2
339
assert rev.number < self.last.number
340
self.sstext.command(text)
341
elif (depth > 2 and rev.number[:depth-1] == tag.number[:depth-1] and
342
(rev.number <= tag.number or len(tag.number) == depth-1)):
343
assert len(rev.number) - len(self.last.number) in (0, 2)
344
assert rev.number > self.last.number
345
self.sstext.command(text)
350
#print "tag =", tag.number, "rev =", rev.number, "<br>"
16
def canonicalize_rootpath(rootpath):
17
return os.path.normpath(rootpath)
20
def expand_root_parent(parent_path):
21
# Each subdirectory of PARENT_PATH that contains a child
22
# "CVSROOT/config" is added the set of returned roots. Or, if the
23
# PARENT_PATH itself contains a child "CVSROOT/config", then all its
24
# subdirectories are returned as roots.
26
subpaths = os.listdir(parent_path)
27
cvsroot = os.path.exists(os.path.join(parent_path, "CVSROOT", "config"))
28
for rootname in subpaths:
29
rootpath = os.path.join(parent_path, rootname)
31
or (os.path.exists(os.path.join(rootpath, "CVSROOT", "config"))):
32
roots[rootname] = canonicalize_rootpath(rootpath)
36
def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse):
37
rootpath = canonicalize_rootpath(rootpath)
40
return ccvs.CCVSRepository(name, rootpath, authorizer, utilities)
43
return bincvs.BinCVSRepository(name, rootpath, authorizer, utilities)