A working version of vclib.ccvs. Need a lot of debugging. Writing a driver
(called bincvs) for vclib that uses rlog and co binary rcs commands. Added a few tests scripts. git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@500 8cb11bc2-c004-0410-86c3-e597b4017df7remotes/tags/1.0.0-rc1
parent
6777801f27
commit
25b5885a6b
|
@ -23,7 +23,9 @@ such as CVS.
|
|||
|
||||
|
||||
# ======================================================================
|
||||
|
||||
#
|
||||
# TODO: Add a last modified property
|
||||
#
|
||||
class Repository:
|
||||
"""
|
||||
Abstract class representing a repository.
|
||||
|
@ -91,10 +93,10 @@ class Repository:
|
|||
gets the properties.
|
||||
"""
|
||||
|
||||
def _getvf_co(self, target, path):
|
||||
def _getvf_cofile(self, target, path):
|
||||
"""
|
||||
should return a file object representing the checked out revision.
|
||||
Notice that _getvf_co can also adds the properties in <target> the
|
||||
Notice that _getvf_co can also add the properties in <target> the
|
||||
way _getvf_properties does.
|
||||
|
||||
Developers: method to be overloaded.
|
||||
|
@ -118,9 +120,8 @@ class Versfile:
|
|||
if not isinstance(repository,Repository):
|
||||
raise TypeError(repository)
|
||||
self.repository=repository
|
||||
# path is assumed to be of correct type.
|
||||
# TODO: do the actual checking.
|
||||
self.path=path
|
||||
|
||||
if tree!=None:
|
||||
self.tree=tree
|
||||
|
||||
|
@ -166,7 +167,7 @@ class Revision:
|
|||
raise AttributeError()
|
||||
|
||||
def checkout(self):
|
||||
return self.versfile._getvf_co(self)
|
||||
return self.versfile._getvf_cofile(self)
|
||||
|
||||
# Here are the shortcuts methods.
|
||||
def getprevious(self):
|
||||
|
|
|
@ -0,0 +1,467 @@
|
|||
# -*-python-*-
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1999-2002 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"""Version Control lib driver for locally accessible cvs-repositories.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# ======================================================================
|
||||
from vclib import Repository, Versfile, Revision
|
||||
import os
|
||||
import os.path
|
||||
import string
|
||||
import re
|
||||
import exceptions
|
||||
import popen
|
||||
|
||||
class LogHeader:
|
||||
"Hold state from the header portion of an 'rlog' output."
|
||||
def __init__(self, filename, head=None, branch=None, taginfo=None):
|
||||
self.filename = filename
|
||||
self.head = head
|
||||
self.branch = branch
|
||||
self.taginfo = taginfo
|
||||
|
||||
|
||||
class LogEntry:
|
||||
"Hold state for each revision entry in an 'rlog' output."
|
||||
def __init__(self, rev, date, author, state, changed, log):
|
||||
self.rev = rev
|
||||
self.date = date
|
||||
self.author = author
|
||||
self.state = state
|
||||
self.changed = changed
|
||||
self.log = log
|
||||
|
||||
def parse_log_header(fp):
|
||||
"""Parse and RCS/CVS log header.
|
||||
|
||||
fp is a file (pipe) opened for reading the log information.
|
||||
|
||||
On entry, fp should point to the start of a log entry.
|
||||
On exit, fp will have consumed the separator line between the header and
|
||||
the first revision log.
|
||||
|
||||
If there is no revision information (e.g. the "-h" switch was passed to
|
||||
rlog), then fp will consumed the file separator line on exit.
|
||||
"""
|
||||
filename = head = branch = None
|
||||
taginfo = { } # tag name => revision
|
||||
|
||||
parsing_tags = 0
|
||||
eof = None
|
||||
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
# the true end-of-file
|
||||
eof = _EOF_LOG
|
||||
break
|
||||
|
||||
if parsing_tags:
|
||||
if line[0] == '\t':
|
||||
[ tag, rev ] = map(string.strip, string.split(line, ':'))
|
||||
taginfo[tag] = rev
|
||||
else:
|
||||
# oops. this line isn't tag info. stop parsing tags.
|
||||
parsing_tags = 0
|
||||
|
||||
if not parsing_tags:
|
||||
if line[:9] == 'RCS file:':
|
||||
# remove the trailing ,v
|
||||
filename = line[10:-3]
|
||||
elif line[:5] == 'head:':
|
||||
head = line[6:-1]
|
||||
elif line[:7] == 'branch:':
|
||||
branch = line[8:-1]
|
||||
elif line[:14] == 'symbolic names':
|
||||
# start parsing the tag information
|
||||
parsing_tags = 1
|
||||
elif line == ENTRY_END_MARKER:
|
||||
# end of the headers
|
||||
break
|
||||
elif line == LOG_END_MARKER:
|
||||
# end of this file's log information
|
||||
eof = _EOF_FILE
|
||||
break
|
||||
elif line[:6] == 'rlog: ':
|
||||
# rlog: filename/goes/here,v: error message
|
||||
idx = string.find(line, ':', 6)
|
||||
if idx != -1:
|
||||
if line[idx:idx+32] == ': warning: Unknown phrases like ':
|
||||
# don't worry about this warning. it can happen with some RCS
|
||||
# files that have unknown fields in them (e.g. "permissions 644;"
|
||||
continue
|
||||
|
||||
# looks like a filename
|
||||
filename = line[6:idx]
|
||||
if filename[-2:] == ',v':
|
||||
filename = filename[:-2]
|
||||
return LogHeader(filename), _EOF_ERROR
|
||||
# dunno what this is
|
||||
|
||||
return LogHeader(filename, head, branch, taginfo), eof
|
||||
|
||||
_re_log_info = re.compile(r'^date:\s+([^;]+);'
|
||||
r'\s+author:\s+([^;]+);'
|
||||
r'\s+state:\s+([^;]+);'
|
||||
r'(\s+lines:\s+([0-9\s+-]+))?\n$')
|
||||
### _re_rev should be updated to extract the "locked" flag
|
||||
_re_rev = re.compile(r'^revision\s+([0-9.]+).*')
|
||||
def parse_log_entry(fp):
|
||||
"""Parse a single log entry.
|
||||
|
||||
On entry, fp should point to the first line of the entry (the "revision"
|
||||
line).
|
||||
On exit, fp will have consumed the log separator line (dashes) or the
|
||||
end-of-file marker (equals).
|
||||
|
||||
Returns: revision, date (time_t secs), author, state, lines changed,
|
||||
the log text, and eof flag (see _EOF_*)
|
||||
"""
|
||||
rev = None
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
return None, _EOF_LOG
|
||||
if line[:8] == 'revision':
|
||||
match = _re_rev.match(line)
|
||||
if not match:
|
||||
return None, _EOF_LOG
|
||||
rev = match.group(1)
|
||||
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
return None, _EOF_LOG
|
||||
match = _re_log_info.match(line)
|
||||
|
||||
eof = None
|
||||
log = ''
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
# true end-of-file
|
||||
eof = _EOF_LOG
|
||||
break
|
||||
if line[:9] == 'branches:':
|
||||
continue
|
||||
if line == ENTRY_END_MARKER:
|
||||
break
|
||||
if line == LOG_END_MARKER:
|
||||
# end of this file's log information
|
||||
eof = _EOF_FILE
|
||||
break
|
||||
|
||||
log = log + line
|
||||
|
||||
if not rev or not match:
|
||||
# there was a parsing error
|
||||
return None, eof
|
||||
|
||||
# parse out a time tuple for the local time
|
||||
tm = compat.cvs_strptime(match.group(1))
|
||||
try:
|
||||
date = int(time.mktime(tm)) - time.timezone
|
||||
except OverflowError:
|
||||
# it is possible that CVS recorded an "illegal" time, such as those
|
||||
# which occur during a Daylight Savings Time switchover (there is a
|
||||
# gap in the time continuum). Let's advance one hour and try again.
|
||||
# While the time isn't necessarily "correct", recall that the gap means
|
||||
# that times *should* be an hour forward. This is certainly close enough
|
||||
# for our needs.
|
||||
#
|
||||
# Note: a true overflow will simply raise an error again, which we won't
|
||||
# try to catch a second time.
|
||||
tm = tm[:3] + (tm[3] + 1,) + tm[4:]
|
||||
date = int(time.mktime(tm)) - time.timezone
|
||||
|
||||
return LogEntry(rev, date,
|
||||
# author, state, lines changed
|
||||
match.group(2), match.group(3), match.group(5),
|
||||
log), eof
|
||||
|
||||
def skip_file(fp):
|
||||
"Skip the rest of a file's log information."
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
if line == LOG_END_MARKER:
|
||||
break
|
||||
|
||||
def process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags):
|
||||
"Fill in fileinfo and alltags with info from the rlog output."
|
||||
|
||||
# consume each file found in the resulting log
|
||||
while 1:
|
||||
|
||||
revwanted = None
|
||||
branch = None
|
||||
branchpoint = None
|
||||
|
||||
header, eof = parse_log_header(rlog)
|
||||
filename = header.filename
|
||||
head = header.head
|
||||
branch = header.branch
|
||||
symrev = header.taginfo
|
||||
|
||||
# the rlog output is done
|
||||
if eof == _EOF_LOG:
|
||||
break
|
||||
|
||||
if filename:
|
||||
# convert from absolute to relative
|
||||
if filename[:len(full_name)] == full_name:
|
||||
filename = filename[len(full_name)+1:]
|
||||
|
||||
# for a subdir (not Attic files!), use the subdir for a key
|
||||
idx = string.find(filename, '/')
|
||||
if idx != -1 and filename[:6] != 'Attic/':
|
||||
info_key = filename[:idx]
|
||||
else:
|
||||
info_key = filename
|
||||
|
||||
# an error was found regarding this file
|
||||
if eof == _EOF_ERROR:
|
||||
fileinfo[info_key] = _FILE_HAD_ERROR
|
||||
continue
|
||||
|
||||
# if we hit the end of the log information (already!), then there is
|
||||
# nothing we can do with this file
|
||||
if eof:
|
||||
continue
|
||||
|
||||
if not filename or not head:
|
||||
# parsing error. skip the rest of this file.
|
||||
skip_file(rlog)
|
||||
continue
|
||||
|
||||
if not branch:
|
||||
idx = string.rfind(head, '.')
|
||||
branch = head[:idx]
|
||||
idx = string.rfind(branch, '.')
|
||||
if idx == -1:
|
||||
branch = '0.' + branch
|
||||
else:
|
||||
branch = branch[:idx] + '.0' + branch[idx:]
|
||||
|
||||
symrev['MAIN'] = symrev['HEAD'] = branch
|
||||
|
||||
if symrev.has_key(view_tag):
|
||||
revwanted = symrev[view_tag]
|
||||
if revwanted[:2] == '0.': ### possible?
|
||||
branch = revwanted[2:]
|
||||
else:
|
||||
idx = string.find(revwanted, '.0.')
|
||||
if idx == -1:
|
||||
branch = revwanted
|
||||
else:
|
||||
branch = revwanted[:idx] + revwanted[idx+2:]
|
||||
if revwanted != branch:
|
||||
revwanted = None
|
||||
|
||||
idx = string.rfind(branch, '.')
|
||||
if idx == -1:
|
||||
branchpoint = ''
|
||||
else:
|
||||
branchpoint = branch[:idx]
|
||||
|
||||
elif view_tag:
|
||||
# the tag wasn't found, so skip this file
|
||||
skip_file(rlog)
|
||||
continue
|
||||
|
||||
# we don't care about the values -- just the keys. this the fastest
|
||||
# way to merge the set of keys
|
||||
alltags.update(symrev)
|
||||
|
||||
# read all of the log entries until we find the revision we want
|
||||
while 1:
|
||||
|
||||
# fetch one of the log entries
|
||||
entry, eof = parse_log_entry(rlog)
|
||||
|
||||
if not entry:
|
||||
# parsing error
|
||||
if not eof:
|
||||
skip_file(rlog)
|
||||
break
|
||||
|
||||
rev = entry.rev
|
||||
|
||||
idx = string.rfind(rev, '.')
|
||||
revbranch = rev[:idx]
|
||||
|
||||
if not view_tag or (not revwanted and branch == revbranch):
|
||||
revwanted = rev
|
||||
|
||||
if rev == revwanted or rev == branchpoint:
|
||||
fileinfo[info_key] = (rev, entry.date, entry.log, entry.author,
|
||||
filename, entry.state)
|
||||
|
||||
if rev == revwanted:
|
||||
# done with this file now
|
||||
if not eof:
|
||||
skip_file(rlog)
|
||||
break
|
||||
|
||||
# if we hit the true EOF, or just this file's end-of-info, then we are
|
||||
# done collecting log entries.
|
||||
if eof:
|
||||
break
|
||||
|
||||
def get_logs(full_name, files, view_tag):
|
||||
|
||||
if len(files) == 0:
|
||||
return { }, { }
|
||||
|
||||
fileinfo = { }
|
||||
alltags = { # all the tags seen in the files of this dir
|
||||
'MAIN' : '1',
|
||||
'HEAD' : '1',
|
||||
}
|
||||
|
||||
chunk_size = 100
|
||||
while files:
|
||||
chunk = files[:chunk_size]
|
||||
del files[:chunk_size]
|
||||
|
||||
# prepend the full pathname for each file
|
||||
for i in range(len(chunk)):
|
||||
chunk[i] = full_name + '/' + chunk[i]
|
||||
|
||||
if not view_tag:
|
||||
# NOTE: can't pass tag on command line since a tag may contain "-"
|
||||
# we'll search the output for the appropriate revision
|
||||
# fetch the latest revision on the default branch
|
||||
chunk = ('-r',) + tuple(chunk)
|
||||
|
||||
rlog = popen.popen(os.path.normpath(os.path.join(cfg.general.rcs_path,'rlog')), chunk, 'r')
|
||||
|
||||
process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags)
|
||||
|
||||
### it would be nice to verify that we got SOMETHING from rlog about
|
||||
### each file. if we didn't, then it could be that the chunk is still
|
||||
### too large, so we want to cut the chunk_size in half and try again.
|
||||
###
|
||||
### BUT: if we didn't get feedback for some *other* reason, then halving
|
||||
### the chunk size could merely send us into a needless retry loop.
|
||||
###
|
||||
### more work for later...
|
||||
|
||||
status = rlog.close()
|
||||
if status:
|
||||
raise 'error during rlog: '+hex(status)
|
||||
|
||||
return fileinfo, alltags
|
||||
|
||||
|
||||
|
||||
class BinCVSRepository:
|
||||
def __init__(self,name,basepath):
|
||||
self.name=name
|
||||
self.basepath=basepath
|
||||
if self.basepath[-1:]!=os.sep:
|
||||
self.basepath=self.basepath+os.sep
|
||||
|
||||
def _getpath(self,pathname):
|
||||
return self.basepath+string.join(pathname,os.sep)
|
||||
|
||||
def _getrcsname(self,filename):
|
||||
if filename[-2:]==',v':
|
||||
return filename
|
||||
else:
|
||||
return filename+',v'
|
||||
|
||||
def getfile(self,pathname):
|
||||
if os.path.isfile(self._getrcsname(self._getpath(pathname))):
|
||||
return Versfile(self,self._getrcsname(self._getpath(pathname)) )
|
||||
raise exceptions.IOError("File not found %s in repository %s"% (self._getpath(pathname),self.name) )
|
||||
|
||||
def getsubdirs(self,path):
|
||||
h=os.listdir(self._getpath(path))
|
||||
g=[]
|
||||
for i in h:
|
||||
if os.path.isdir(self._getpath(path+[i])):
|
||||
g.append(i)
|
||||
return g
|
||||
|
||||
def getfiles(self,path):
|
||||
h=os.listdir(self._getpath(path))
|
||||
g={}
|
||||
for i in h:
|
||||
ci=self._getrcsname(self._getpath(path+[i]))
|
||||
if os.path.isfile(ci):
|
||||
g[i]=Versfile(self,ci)
|
||||
return g
|
||||
# Private methods ( accessed by Versfile and Revision )
|
||||
|
||||
def _getvf_info(self,target, path):
|
||||
"""
|
||||
This method will had to <target> (expect to be an instance of Versfile)
|
||||
a certain number of attributes:
|
||||
head (string)
|
||||
age (int timestamp)
|
||||
author (string)
|
||||
log
|
||||
branch
|
||||
... ( there can be other stuff here)
|
||||
|
||||
Developers: method to be overloaded.
|
||||
"""
|
||||
if not os.path.isfile(path):
|
||||
raise "Unknown file: %s " % path
|
||||
|
||||
def _getvf_tree(self,versfile):
|
||||
"""
|
||||
should return a dictionary of Revisions
|
||||
Developers: method to be overloaded.
|
||||
"""
|
||||
|
||||
def _getvf_properties(self,target,path,revisionnumber):
|
||||
"""
|
||||
Add/update into target's attributes (expected to be an instance of
|
||||
Revision) a certain number of attributes:
|
||||
rev
|
||||
date
|
||||
author
|
||||
state
|
||||
log
|
||||
previous revision number
|
||||
branches ( a list of revision numbers )
|
||||
changes ( string of the form: e.g. "+1 -0 lines" )
|
||||
tags
|
||||
... ( there can be other stuff here)
|
||||
|
||||
Developers: in the cvs implementation, the method will never be called.
|
||||
There is no point in developping this method as _getvf_tree already
|
||||
gets the properties.
|
||||
"""
|
||||
|
||||
def _getvf_cofile(self, target, path):
|
||||
"""
|
||||
should return a file object representing the checked out revision.
|
||||
Notice that _getvf_co can also add the properties in <target> the
|
||||
way _getvf_properties does.
|
||||
|
||||
Developers: method to be overloaded.
|
||||
"""
|
||||
fp = popen.popen('co',
|
||||
('-p'+rev, os.path.join(repo,file) ), 'r')
|
||||
fp.readline()
|
||||
fp.readline()
|
||||
return fp
|
||||
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# popen.py: a replacement for os.popen()
|
||||
#
|
||||
# This implementation of popen() provides a cmd + args calling sequence,
|
||||
# rather than a system() type of convention. The shell facilities are not
|
||||
# available, but that implies we can avoid worrying about shell hacks in
|
||||
# the arguments.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def popen(cmd, args, mode, capture_err=1):
|
||||
# flush the stdio buffers since we are about to change the FD under them
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
r, w = os.pipe()
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
# in the parent
|
||||
|
||||
# close the descriptor that we don't need and return the other one.
|
||||
if mode == 'r':
|
||||
os.close(w)
|
||||
return _pipe(os.fdopen(r, 'r'), pid)
|
||||
os.close(r)
|
||||
return _pipe(os.fdopen(w, 'w'), pid)
|
||||
|
||||
# in the child
|
||||
|
||||
# we'll need /dev/null for the discarded I/O
|
||||
null = os.open('/dev/null', os.O_RDWR)
|
||||
|
||||
if mode == 'r':
|
||||
# hook stdout/stderr to the "write" channel
|
||||
os.dup2(w, 1)
|
||||
# "close" stdin; the child shouldn't use it
|
||||
### this isn't quite right... we may want the child to read from stdin
|
||||
os.dup2(null, 0)
|
||||
# what to do with errors?
|
||||
if capture_err:
|
||||
os.dup2(w, 2)
|
||||
else:
|
||||
os.dup2(null, 2)
|
||||
else:
|
||||
# hook stdin to the "read" channel
|
||||
os.dup2(r, 0)
|
||||
# "close" stdout/stderr; the child shouldn't use them
|
||||
### this isn't quite right... we may want the child to write to these
|
||||
os.dup2(null, 1)
|
||||
os.dup2(null, 2)
|
||||
|
||||
# don't need these FDs any more
|
||||
os.close(null)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# the stdin/stdout/stderr are all set up. exec the target
|
||||
try:
|
||||
os.execvp(cmd, (cmd,) + tuple(args))
|
||||
except:
|
||||
# aid debugging, if the os.execvp above fails for some reason:
|
||||
import string
|
||||
print "<h2>exec failed:</h2><pre>", cmd, string.join(args), "</pre>"
|
||||
raise
|
||||
|
||||
# crap. shouldn't be here.
|
||||
sys.exit(127)
|
||||
|
||||
def pipe_cmds(cmds):
|
||||
# flush the stdio buffers since we are about to change the FD under them
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
prev_r, parent_w = os.pipe()
|
||||
|
||||
null = os.open('/dev/null', os.O_RDWR)
|
||||
|
||||
for cmd in cmds[:-1]:
|
||||
r, w = os.pipe()
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# hook up stdout to the output channel
|
||||
os.dup2(w, 1)
|
||||
|
||||
# toss errors
|
||||
os.dup2(null, 2)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
os.close(null)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# time to run the command
|
||||
try:
|
||||
os.execvp(cmd[0], cmd)
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
# in the parent
|
||||
|
||||
# we don't need these any more
|
||||
os.close(prev_r)
|
||||
os.close(w)
|
||||
|
||||
# the read channel of this pipe will feed into to the next command
|
||||
prev_r = r
|
||||
|
||||
# no longer needed
|
||||
os.close(null)
|
||||
|
||||
# done with most of the commands. set up the last command to write to stdout
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child (the last command)
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
|
||||
# run the last command
|
||||
try:
|
||||
os.execvp(cmds[-1][0], cmds[-1])
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
# not needed any more
|
||||
os.close(prev_r)
|
||||
|
||||
# write into the first pipe, wait on the final process
|
||||
return _pipe(os.fdopen(parent_w, 'w'), pid)
|
||||
|
||||
|
||||
class _pipe:
|
||||
"Wrapper for a file which can wait() on a child process at close time."
|
||||
|
||||
def __init__(self, file, child_pid):
|
||||
self.file = file
|
||||
self.child_pid = child_pid
|
||||
|
||||
def eof(self):
|
||||
pid, status = os.waitpid(self.child_pid, os.WNOHANG)
|
||||
if pid:
|
||||
self.file.close()
|
||||
self.file = None
|
||||
return status
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
if self.file:
|
||||
self.file.close()
|
||||
self.file = None
|
||||
return os.waitpid(self.child_pid, 0)[1]
|
||||
return None
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.file, name)
|
||||
|
||||
def __del__(self):
|
||||
if self.file:
|
||||
self.close()
|
|
@ -19,6 +19,7 @@
|
|||
"""
|
||||
This is a Version Control library driver for locally accessible cvs-repositories.
|
||||
"""
|
||||
|
||||
from vclib import Repository, Versfile, Revision
|
||||
import os
|
||||
import os.path
|
||||
|
@ -26,7 +27,7 @@ import string
|
|||
import re
|
||||
import exceptions
|
||||
import rcsparse
|
||||
|
||||
import cStringIO
|
||||
class InfoSink(rcsparse.Sink):
|
||||
|
||||
def __init__(self,target):
|
||||
|
@ -63,6 +64,7 @@ class TreeSink(rcsparse.Sink):
|
|||
def __init__(self,target):
|
||||
self.target=target
|
||||
self.tree={}
|
||||
self.tags={}
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.target.__dict__["head"]=revision
|
||||
|
@ -74,8 +76,12 @@ class TreeSink(rcsparse.Sink):
|
|||
if self.tree.has_key(revision):
|
||||
self.tree[revision].__dict__["tags"].append(name)
|
||||
else:
|
||||
self.tree[revision]=Revision(self.target,revision)
|
||||
self.tree[revision].__dict__["tags"]=[name]
|
||||
if revision in self.tags:
|
||||
self.tags[revision].append(name)
|
||||
else:
|
||||
self.tags[revision]=[name]
|
||||
# self.tree[revision]=Revision(self.target,revision)
|
||||
# self.tree[revision].__dict__["tags"]=[name]
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
|
@ -92,6 +98,11 @@ class TreeSink(rcsparse.Sink):
|
|||
rev.__dict__["branches"]=branches
|
||||
rev.__dict__["state"]=state
|
||||
rev.__dict__["previous"]=next
|
||||
if revision in self.tags:
|
||||
rev.__dict__["tags"]=self.tags[revision]
|
||||
del self.tags[revision]
|
||||
else:
|
||||
rev.__dict__["tags"]=[]
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
if self.tree.has_key(revision):
|
||||
|
@ -121,8 +132,66 @@ class TreeSink(rcsparse.Sink):
|
|||
idx = idx +count
|
||||
else:
|
||||
raise "error while parsing deltatext: %s" % command
|
||||
#rev.getprevious().__dict__["changes"]= "+%d -%d lines" %(deled,added)
|
||||
|
||||
rev.__dict__["changes"]= (deled,added)
|
||||
def gobranch(self,rev):
|
||||
rev.__dict__["changes"]="+%d -%d lines" % (rev.__dict__["changes"][1],rev.__dict__["changes"][0])
|
||||
if rev.__dict__["previous"]!=None:
|
||||
self.gobranch(self.tree[rev.__dict__["previous"]])
|
||||
for x in rev.__dict__["branches"]:
|
||||
self.gobranch(self.tree[x])
|
||||
def gotree(self,rev):
|
||||
if rev.__dict__["previous"]!=None:
|
||||
rev.__dict__["changes"]="+%d -%d lines" % self.tree[rev.__dict__["previous"]].__dict__["changes"]
|
||||
self.gotree(self.tree[rev.__dict__["previous"]])
|
||||
else:
|
||||
rev.__dict__["changes"]=""
|
||||
for x in rev.__dict__["branches"]:
|
||||
self.gobranch(self.tree[x])
|
||||
def parse_completed(self):
|
||||
self.gotree(self.tree[self.target.__dict__["head"]])
|
||||
class StreamText:
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
def __init__(self,text,head):
|
||||
self.next_revision(head)
|
||||
self.text=string.split(text,"\n")
|
||||
def command(self,cmd):
|
||||
adjust = 0
|
||||
add_lines_remaining = 0
|
||||
diffs = string.split(cmd,"\n")
|
||||
if diffs[-1]=="":
|
||||
del diffs[-1]
|
||||
if len(diffs)==0:
|
||||
return
|
||||
if diffs[0]=="":
|
||||
del diffs[0]
|
||||
for command in diffs:
|
||||
if add_lines_remaining > 0:
|
||||
# Insertion lines from a prior "a" command
|
||||
self.text.insert(start_line + adjust, command)
|
||||
add_lines_remaining = add_lines_remaining - 1
|
||||
adjust = adjust + 1
|
||||
continue
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if dmatch:
|
||||
# "d" - Delete command
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
begin = start_line + adjust - 1
|
||||
del self.text[begin:begin + count]
|
||||
adjust = adjust - count
|
||||
elif amatch:
|
||||
# "a" - Add command
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
add_lines_remaining = count
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
def next_revision(self,revision):
|
||||
#print "Revision: %s"% revision
|
||||
pass
|
||||
class COSink(rcsparse.Sink):
|
||||
|
||||
def __init__(self,target):
|
||||
|
@ -130,8 +199,10 @@ class COSink(rcsparse.Sink):
|
|||
|
||||
def set_head_revision(self, revision):
|
||||
self.head=revision
|
||||
self.position=0
|
||||
self.path=[revision]
|
||||
self.pathover=0
|
||||
self.buffer={}
|
||||
self.sstext=None
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
|
@ -141,11 +212,67 @@ class COSink(rcsparse.Sink):
|
|||
self.target.__dict__["branches"]=branches
|
||||
self.target.__dict__["state"]=state
|
||||
self.target.__dict__["previous"]=next
|
||||
#self.path.append
|
||||
if self.path[-1]==self.target.rev:
|
||||
return
|
||||
if revision==self.path[-1]:
|
||||
if self.target.rev[:len(revision)]==revision:
|
||||
# Branch ?
|
||||
for x in branches:
|
||||
if self.target.rev[:len(revision)+3]==x[:len(revision)+3]:
|
||||
self.path.append(x)
|
||||
if x in self.buffer:
|
||||
i=self.buffer[x]
|
||||
del self.buffer[x]
|
||||
self.define_revision(x,0,"","",i[0],i[1])
|
||||
return
|
||||
else:
|
||||
print revision
|
||||
print branches
|
||||
raise " %s revision doesn't exist " % self.target.rev
|
||||
else:
|
||||
# no => next
|
||||
self.path.append(next)
|
||||
if self.buffer.has_key(next):
|
||||
self.path.append(next)
|
||||
if x in self.buffer:
|
||||
i=self.buffer[next]
|
||||
del self.buffer[next]
|
||||
self.define_revision(next,0,"","",i[0],i[1])
|
||||
else:
|
||||
self.buffer[revision]=(branches,next)
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
pass
|
||||
|
||||
def tree_completed(self):
|
||||
if self.path[-1]!=self.target.rev:
|
||||
raise "Error Incomplete path"
|
||||
#print self.path
|
||||
self.buffer={}
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
if revision==self.target.rev:
|
||||
self.target.__dict__["log"]=log
|
||||
if revision in self.path:
|
||||
if self.path[self.position]==revision:
|
||||
if revision==self.head:
|
||||
self.sstext=StreamText(text,self.head)
|
||||
else:
|
||||
self.sstext.next_revision(revision)
|
||||
self.sstext.command(text)
|
||||
while self.position+1<len(self.path):
|
||||
self.position = self.position+1
|
||||
x= self.path[self.position]
|
||||
if x not in self.buffer:
|
||||
break
|
||||
self.sstext.next_revision(x)
|
||||
self.sstext.command(self.buffer[x])
|
||||
del self.buffer[x]
|
||||
else:
|
||||
self.buffer[revision]=text
|
||||
|
||||
def parse_completed(self):
|
||||
if self.buffer!={}:
|
||||
raise "Error buffer not emptied"
|
||||
|
||||
class CVSRepository(Repository):
|
||||
def __init__(self,name,basepath):
|
||||
self.name=name
|
||||
|
@ -166,6 +293,7 @@ class CVSRepository(Repository):
|
|||
if os.path.isfile(self._getrcsname(self._getpath(pathname))):
|
||||
return Versfile(self,self._getrcsname(self._getpath(pathname)) )
|
||||
raise exceptions.IOError("File not found %s in repository %s"% (self._getpath(pathname),self.name) )
|
||||
|
||||
def getsubdirs(self,path):
|
||||
h=os.listdir(self._getpath(path))
|
||||
g=[]
|
||||
|
@ -196,18 +324,12 @@ class CVSRepository(Repository):
|
|||
if not os.path.isfile(versfile.path):
|
||||
raise "Unknown file: %s " % versfile.path
|
||||
sink=TreeSink(versfile)
|
||||
try:
|
||||
rcsparse.Parser().parse(open(versfile.path),sink)
|
||||
except RCSStopParser:
|
||||
pass
|
||||
rcsparse.Parser().parse(open(versfile.path),sink)
|
||||
return sink.tree
|
||||
|
||||
def _getvf_co(self, target, path):
|
||||
def _getvf_cofile(self, target, path):
|
||||
if not os.path.isfile(path):
|
||||
raise "Unknown file: %s " % versfile.path
|
||||
raise "Unknown file: %s " % path
|
||||
sink=COSink(target)
|
||||
try:
|
||||
rcsparse.Parser().parse(open(versfile.path),sink)
|
||||
except RCSStopParser:
|
||||
pass
|
||||
return sink.tree
|
||||
rcsparse.Parser().parse(open(path),sink)
|
||||
return cStringIO.StringIO(string.join(sink.sstext.text,"\n"))
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/local/bin/python
|
||||
import sys, os.path
|
||||
sys.path.append( os.path.normpath(os.path.join(sys.path[0],"..","..","lib")) )
|
||||
import vclib.ccvs
|
||||
import popen
|
||||
def usage():
|
||||
print """
|
||||
co simulation using vclib!!!
|
||||
python co.py <Path to repository> <(relative) Path to file> <revision>
|
||||
"""
|
||||
sys.exit()
|
||||
def convertpath(s):
|
||||
a=(s,'')
|
||||
res=[]
|
||||
while (a[0]!=''):
|
||||
a=os.path.split(a[0])
|
||||
res= [a[1]]+res
|
||||
return res
|
||||
|
||||
def compareco(repo,file,rev):
|
||||
a=vclib.ccvs.CVSRepository("lucas",repo)
|
||||
f=a.getfile(convertpath(file)) # example: ["kdelibs","po","Attic","nl.po"]
|
||||
r=f.tree[rev]
|
||||
fp1 = r.checkout()
|
||||
fp2 = popen.popen('co',
|
||||
('-p'+rev, os.path.join(repo,file) ), 'r')
|
||||
l1 = fp1.readlines()
|
||||
l2 = fp2.readlines()
|
||||
ok=1
|
||||
for i in range(0,len(l1)-1):
|
||||
if l1[i] != l2[i+2]:
|
||||
print " Difference in line %d"% i
|
||||
print " line from CCVS %s" % l1[i]
|
||||
print " line from RCS %s" % l2[i+2]
|
||||
ok=0
|
||||
return ok
|
||||
|
||||
if len(sys.argv)==4:
|
||||
compareco(sys.argv[1],sys.argv[2],sys.argv[3])
|
||||
elif len(sys.argv)==3:
|
||||
a=vclib.ccvs.CVSRepository("lucas",sys.argv[1])
|
||||
f=a.getfile(convertpath(sys.argv[2])) # example: ["kdelibs","po","Attic","nl.po"]
|
||||
for rev in f.tree.keys():
|
||||
print ("revision: %s" % rev),
|
||||
if compareco(sys.argv[1],sys.argv[2],rev):
|
||||
print "ok"
|
||||
else:
|
||||
print "fail"
|
||||
|
||||
else:
|
||||
usage()
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/local/bin/python
|
||||
import sys, os.path
|
||||
sys.path.append( os.path.normpath(os.path.join(sys.path[0],"..","..","lib")) )
|
||||
import vclib.ccvs
|
||||
import popen
|
Loading…
Reference in New Issue