##// END OF EJS Templates
transplant: wrap a transaction around the whole command
transplant: wrap a transaction around the whole command

File last commit:

r14945:11aad09a default
r15204:3ce9b1a7 default
Show More
cvsps.py
848 lines | 29.9 KiB | text/x-python | PythonLexer
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # Mercurial built-in replacement for cvsps.
#
# Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
import os
import re
import cPickle as pickle
from mercurial import util
from mercurial.i18n import _
Frank Kingswood
convert: implement two hooks in builtin cvsps
r10095 from mercurial import hook
Augie Fackler
hgext: replace uses of hasattr with util.safehasattr
r14945 from mercurial import util
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
class logentry(object):
'''Class logentry has the following attributes:
.author - author name as CVS knows it
.branch - name of branch this revision is on
.branches - revision tuple of branches starting at this revision
.comment - commit message
.date - the commit date as a (time, tz) tuple
.dead - true if file revision is dead
.file - Name of file
.lines - a tuple (+lines, -lines) or None
.parent - Previous revision of this entry
.rcs - name of file as returned from CVS
.revision - revision number as tuple
.tags - list of tags on the file
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 .synthetic - is this a synthetic "file ... added on ..." revision?
Martin Geisler
convert/cvsps: wrap long lines
r8661 .mergepoint- the branch that has been merged from
(if present in rlog output)
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 .branchpoints- the branches that start at the current entry
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 '''
def __init__(self, **entries):
Benoit Boissinot
cvsps: fix traceback involving 'synthetic'...
r10701 self.synthetic = False
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 self.__dict__.update(entries)
Greg Ward
cvsps: make debugging easier by adding __repr__() methods.
r8080 def __repr__(self):
return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
id(self),
self.file,
".".join(map(str, self.revision)))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 class logerror(Exception):
pass
Patrick Mezard
convert: fix builtin cvsps under Windows...
r7097 def getrepopath(cvspath):
"""Return the repository path from a CVS path.
>>> getrepopath('/foo/bar')
'/foo/bar'
>>> getrepopath('c:/foo/bar')
'c:/foo/bar'
>>> getrepopath(':pserver:10/foo/bar')
'/foo/bar'
>>> getrepopath(':pserver:10c:/foo/bar')
'/foo/bar'
>>> getrepopath(':pserver:/foo/bar')
'/foo/bar'
>>> getrepopath(':pserver:c:/foo/bar')
'c:/foo/bar'
>>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
'/foo/bar'
>>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
'c:/foo/bar'
"""
# According to CVS manual, CVS paths are expressed like:
# [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
#
# Unfortunately, Windows absolute paths start with a drive letter
# like 'c:' making it harder to parse. Here we assume that drive
# letters are only one character long and any CVS component before
# the repository path is at least 2 characters long, and use this
# to disambiguate.
parts = cvspath.split(':')
if len(parts) == 1:
return parts[0]
# Here there is an ambiguous case if we have a port number
# immediately followed by a Windows driver letter. We assume this
# never happens and decide it must be CVS path component,
# therefore ignoring it.
if len(parts[-2]) > 1:
return parts[-1].lstrip('0123456789')
return parts[-2] + ':' + parts[-1]
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 def createlog(ui, directory=None, root="", rlog=True, cache=None):
'''Collect the CVS rlog'''
# Because we store many duplicate commit log messages, reusing strings
# saves a lot of memory and pickle storage space.
_scache = {}
def scache(s):
"return a shared version of a string"
return _scache.setdefault(s, s)
ui.status(_('collecting CVS rlog\n'))
log = [] # list of logentry objects containing the CVS state
# patterns to match in CVS (r)log output, by state of use
re_00 = re.compile('RCS file: (.+)$')
re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
re_02 = re.compile('cvs (r?log|server): (.+)\n$')
Martin Geisler
convert/cvsps: wrap long lines
r8661 re_03 = re.compile("(Cannot access.+CVSROOT)|"
"(can't create temporary directory.+)$")
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 re_10 = re.compile('Working file: (.+)$')
re_20 = re.compile('symbolic names:')
re_30 = re.compile('\t(.+): ([\\d.]+)$')
re_31 = re.compile('----------------------------$')
Martin Geisler
convert/cvsps: wrap long lines
r8661 re_32 = re.compile('======================================='
'======================================$')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
Martin Geisler
convert/cvsps: wrap long lines
r8661 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
r'(.*mergepoint:\s+([^;]+);)?')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 re_70 = re.compile('branches: (.+);$')
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 prefix = '' # leading path to strip of what we get from CVS
if directory is None:
# Current working directory
# Get the real directory in the repository
try:
Alejandro Santos
compat: use open() instead of file() everywhere
r9031 prefix = open(os.path.join('CVS','Repository')).read().strip()
Mathieu Clabaut
convert: teach cvsps to handle . repository (issue1649)...
r10695 directory = prefix
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if prefix == ".":
Matt Mackall
cvsps: fix up some whitespace
r6688 prefix = ""
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 except IOError:
Martin Geisler
convert: mark strings for translation
r10939 raise logerror(_('not a CVS sandbox'))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
Patrick Mezard
convert: fix builtin cvsps under Windows...
r7097 if prefix and not prefix.endswith(os.sep):
prefix += os.sep
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
# Use the Root file in the sandbox, if it exists
try:
Alejandro Santos
compat: use open() instead of file() everywhere
r9031 root = open(os.path.join('CVS','Root')).read().strip()
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 except IOError:
pass
if not root:
root = os.environ.get('CVSROOT', '')
# read log cache if one exists
oldlog = []
date = None
if cache:
cachedir = os.path.expanduser('~/.hg.cvsps')
if not os.path.exists(cachedir):
os.mkdir(cachedir)
# The cvsps cache pickle needs a uniquified name, based on the
# repository location. The address may have all sort of nasties
# in it, slashes, colons and such. So here we take just the
# alphanumerics, concatenated in a way that does not mix up the
Dirkjan Ochtman
cvsps: fix a final whitespace nit
r6696 # various components, so that
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # :pserver:user@server:/path
# and
# /pserver/user/server/path
# are mapped to different cache file names.
Matt Mackall
cvsps: fix up some whitespace
r6688 cachefile = root.split(":") + [directory, "cache"]
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
Matt Mackall
cvsps: fix up some whitespace
r6688 cachefile = os.path.join(cachedir,
'.'.join([s for s in cachefile if s]))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
if cache == 'update':
try:
ui.note(_('reading cvs log cache %s\n') % cachefile)
Alejandro Santos
compat: use open() instead of file() everywhere
r9031 oldlog = pickle.load(open(cachefile))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 ui.note(_('cache has %d log entries\n') % len(oldlog))
except Exception, e:
ui.note(_('error reading cache: %r\n') % e)
if oldlog:
date = oldlog[-1].date # last commit date as a (time,tz) tuple
date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
# build the CVS commandline
cmd = ['cvs', '-q']
if root:
cmd.append('-d%s' % root)
Patrick Mezard
convert: fix builtin cvsps under Windows...
r7097 p = util.normpath(getrepopath(root))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if not p.endswith('/'):
Matt Mackall
cvsps: fix up some whitespace
r6688 p += '/'
Mathieu Clabaut
convert: teach cvsps to handle . repository (issue1649)...
r10695 if prefix:
# looks like normpath replaces "" by "."
prefix = p + util.normpath(prefix)
else:
prefix = p
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 cmd.append(['log', 'rlog'][rlog])
if date:
# no space between option and date string
cmd.append('-d>%s' % date)
cmd.append(directory)
# state machine begins here
tags = {} # dictionary of revisions on current file with their tags
Henrik Stuart
convert: added cvsnt mergepoint support
r7956 branchmap = {} # mapping between branch names and revision numbers
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 state = 0
store = False # set when a new record can be appended
cmd = [util.shellquote(arg) for arg in cmd]
Martin Geisler
i18n: mark strings for translation in convert extension
r6956 ui.note(_("running %s\n") % (' '.join(cmd)))
Martin Geisler
do not attempt to translate ui.debug output
r9467 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
David Champion
cvsps: cvs log loop uses lookahead to avoid misleading text...
r7593 pfp = util.popen(' '.join(cmd))
peek = pfp.readline()
while True:
line = peek
if line == '':
break
peek = pfp.readline()
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if line.endswith('\n'):
line = line[:-1]
#ui.debug('state=%d line=%r\n' % (state, line))
if state == 0:
# initial state, consume input until we see 'RCS file'
match = re_00.match(line)
if match:
rcs = match.group(1)
tags = {}
if rlog:
Patrick Mezard
convert: fix builtin cvsps under Windows...
r7097 filename = util.normpath(rcs[:-2])
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if filename.startswith(prefix):
filename = filename[len(prefix):]
if filename.startswith('/'):
filename = filename[1:]
if filename.startswith('Attic/'):
filename = filename[6:]
else:
filename = filename.replace('/Attic/', '/')
state = 2
continue
state = 1
continue
match = re_01.match(line)
if match:
Matt Mackall
clean up remaining generic exceptions
r11122 raise logerror(match.group(1))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 match = re_02.match(line)
if match:
Matt Mackall
clean up remaining generic exceptions
r11122 raise logerror(match.group(2))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if re_03.match(line):
Matt Mackall
clean up remaining generic exceptions
r11122 raise logerror(line)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
elif state == 1:
# expect 'Working file' (only when using log instead of rlog)
match = re_10.match(line)
assert match, _('RCS file must be followed by working file')
Patrick Mezard
convert: fix builtin cvsps under Windows...
r7097 filename = util.normpath(match.group(1))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 state = 2
elif state == 2:
# expect 'symbolic names'
if re_20.match(line):
Henrik Stuart
convert: added cvsnt mergepoint support
r7956 branchmap = {}
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 state = 3
elif state == 3:
# read the symbolic names and store as tags
match = re_30.match(line)
if match:
rev = [int(x) for x in match.group(2).split('.')]
# Convert magic branch number to an odd-numbered one
revn = len(rev)
Matt Mackall
cvsps: fix up some whitespace
r6688 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
rev = rev[:-2] + rev[-1:]
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 rev = tuple(rev)
if rev not in tags:
tags[rev] = []
tags[rev].append(match.group(1))
Henrik Stuart
convert: added cvsnt mergepoint support
r7956 branchmap[match.group(1)] = match.group(2)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
elif re_31.match(line):
state = 5
elif re_32.match(line):
state = 0
elif state == 4:
# expecting '------' separator before first revision
if re_31.match(line):
state = 5
else:
Martin Geisler
convert/cvsps: wrap long lines
r8661 assert not re_32.match(line), _('must have at least '
'some revisions')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
elif state == 5:
# expecting revision number and possibly (ignored) lock indication
# we create the logentry here from values stored in states 0 to 4,
# as this state is re-entered for subsequent revisions of a file.
match = re_50.match(line)
assert match, _('expected revision number')
e = logentry(rcs=scache(rcs), file=scache(filename),
revision=tuple([int(x) for x in match.group(1).split('.')]),
Benoit Boissinot
cvsps: fix traceback involving 'synthetic'...
r10701 branches=[], parent=None)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 state = 6
elif state == 6:
# expecting date, author, state, lines changed
match = re_60.match(line)
assert match, _('revision must be followed by date line')
d = match.group(1)
if d[2] == '/':
# Y2K
Matt Mackall
cvsps: fix up some whitespace
r6688 d = '19' + d
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
if len(d.split()) != 3:
# cvs log dates always in GMT
Matt Mackall
cvsps: fix up some whitespace
r6688 d = d + ' UTC'
Martin Geisler
convert/cvsps: wrap long lines
r8661 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
'%Y/%m/%d %H:%M:%S',
'%Y-%m-%d %H:%M:%S'])
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 e.author = scache(match.group(2))
e.dead = match.group(3).lower() == 'dead'
if match.group(5):
if match.group(6):
e.lines = (int(match.group(5)), int(match.group(6)))
else:
e.lines = (int(match.group(5)), 0)
elif match.group(6):
e.lines = (0, int(match.group(6)))
else:
e.lines = None
Henrik Stuart
convert: added cvsnt mergepoint support
r7956
if match.group(7): # cvsnt mergepoint
myrev = match.group(8).split('.')
if len(myrev) == 2: # head
e.mergepoint = 'HEAD'
else:
myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
branches = [b for b in branchmap if branchmap[b] == myrev]
assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
e.mergepoint = branches[0]
else:
e.mergepoint = None
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 e.comment = []
state = 7
elif state == 7:
Matt Mackall
cvsps: fix up some whitespace
r6688 # read the revision numbers of branches that start at this revision
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # or store the commit log message otherwise
m = re_70.match(line)
if m:
e.branches = [tuple([int(y) for y in x.strip().split('.')])
for x in m.group(1).split(';')]
state = 8
David Champion
cvsps: cvs log loop uses lookahead to avoid misleading text...
r7593 elif re_31.match(line) and re_50.match(peek):
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 state = 5
store = True
elif re_32.match(line):
state = 0
store = True
else:
e.comment.append(line)
elif state == 8:
# store commit log message
if re_31.match(line):
state = 5
store = True
elif re_32.match(line):
state = 0
store = True
else:
e.comment.append(line)
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 # When a file is added on a branch B1, CVS creates a synthetic
# dead trunk revision 1.1 so that the branch has a root.
# Likewise, if you merge such a file to a later branch B2 (one
# that already existed when the file was added on B1), CVS
# creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
# these revisions now, but mark them synthetic so
# createchangeset() can take care of them.
if (store and
e.dead and
e.revision[-1] == 1 and # 1.1 or 1.1.x.1
len(e.comment) == 1 and
file_added_re.match(e.comment[0])):
Martin Geisler
do not attempt to translate ui.debug output
r9467 ui.debug('found synthetic revision in %s: %r\n'
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 % (e.rcs, e.comment[0]))
e.synthetic = True
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if store:
# clean up the results and save in the log.
store = False
Matt Mackall
replace util.sort with sorted built-in...
r8209 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 e.comment = scache('\n'.join(e.comment))
revn = len(e.revision)
Matt Mackall
cvsps: fix up some whitespace
r6688 if revn > 3 and (revn % 2) == 0:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 e.branch = tags.get(e.revision[:-1], [None])[0]
else:
e.branch = None
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 # find the branches starting from this revision
branchpoints = set()
for branch, revision in branchmap.iteritems():
revparts = tuple([int(i) for i in revision.split('.')])
Brandon Parsons
cvs: skip bad tags...
r10950 if len(revparts) < 2: # bad tags
continue
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
# normal branch
if revparts[:-2] == e.revision:
branchpoints.add(branch)
Matt Mackall
many, many trivial check-code fixups
r10282 elif revparts == (1, 1, 1): # vendor branch
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 if revparts in e.branches:
branchpoints.add(branch)
e.branchpoints = branchpoints
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 log.append(e)
Matt Mackall
cvsps: fix up some whitespace
r6688 if len(log) % 100 == 0:
ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
Alejandro Santos
compat: use 'key' argument instead of 'cmp' when sorting a list
r9032 log.sort(key=lambda x: (x.rcs, x.revision))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
# find parent revisions of individual files
versions = {}
for e in log:
branch = e.revision[:-1]
p = versions.get((e.rcs, branch), None)
if p is None:
p = e.revision[:-2]
e.parent = p
versions[(e.rcs, branch)] = e.revision
# update the log cache
if cache:
if log:
# join up the old and new logs
Alejandro Santos
compat: use 'key' argument instead of 'cmp' when sorting a list
r9032 log.sort(key=lambda x: x.date)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
if oldlog and oldlog[-1].date >= log[0].date:
Martin Geisler
convert: mark strings for translation
r10939 raise logerror(_('log cache overlaps with new log entries,'
' re-run without cache.'))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
Matt Mackall
cvsps: fix up some whitespace
r6688 log = oldlog + log
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
# write the new cachefile
ui.note(_('writing cvs log cache %s\n') % cachefile)
Alejandro Santos
compat: use open() instead of file() everywhere
r9031 pickle.dump(log, open(cachefile, 'w'))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 else:
log = oldlog
ui.status(_('%d log entries\n') % len(log))
Frank Kingswood
convert: implement two hooks in builtin cvsps
r10095 hook.hook(ui, None, "cvslog", True, log=log)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return log
class changeset(object):
'''Class changeset has the following attributes:
Greg Ward
cvsps: update docstring for changeset class.
r8079 .id - integer identifying this changeset (list index)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 .author - author name as CVS knows it
.branch - name of branch this changeset is on, or None
.comment - commit message
.date - the commit date as a (time,tz) tuple
.entries - list of logentry objects in this changeset
.parents - list of one or two parent changesets
.tags - list of tags on this changeset
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 .synthetic - from synthetic revision "file ... added on branch ..."
Martin Geisler
convert/cvsps: wrap long lines
r8661 .mergepoint- the branch that has been merged from
(if present in rlog output)
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 .branchpoints- the branches that start at the current entry
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 '''
def __init__(self, **entries):
Benoit Boissinot
cvsps: fix traceback involving 'synthetic'...
r10701 self.synthetic = False
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 self.__dict__.update(entries)
Greg Ward
cvsps: make debugging easier by adding __repr__() methods.
r8080 def __repr__(self):
return "<%s at 0x%x: %s>" % (self.__class__.__name__,
id(self),
getattr(self, 'id', "(no id)"))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
'''Convert log into changesets.'''
ui.status(_('creating changesets\n'))
# Merge changesets
Alejandro Santos
compat: use 'key' argument instead of 'cmp' when sorting a list
r9032 log.sort(key=lambda x: (x.comment, x.author, x.branch, x.date))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
changesets = []
Benoit Boissinot
convert: use set instead of dict
r8456 files = set()
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 c = None
for i, e in enumerate(log):
# Check if log entry belongs to the current changeset or not.
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756
# Since CVS is file centric, two different file revisions with
# different branchpoints should be treated as belonging to two
# different changesets (and the ordering is important and not
# honoured by cvsps at this point).
#
# Consider the following case:
# foo 1.1 branchpoints: [MYBRANCH]
# bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
#
# Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
# later version of foo may be in MYBRANCH2, so foo should be the
# first changeset and bar the next and MYBRANCH and MYBRANCH2
# should both start off of the bar changeset. No provisions are
# made to ensure that this is, in fact, what happens.
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if not (c and
e.comment == c.comment and
e.author == c.author and
e.branch == c.branch and
Augie Fackler
hgext: replace uses of hasattr with util.safehasattr
r14945 (not util.safehasattr(e, 'branchpoints') or
not util.safehasattr (c, 'branchpoints') or
Rocco Rutte
convert: fix builtin cvsps when no branchpoints are present
r8890 e.branchpoints == c.branchpoints) and
Matt Mackall
cvsps: fix up some whitespace
r6688 ((c.date[0] + c.date[1]) <=
(e.date[0] + e.date[1]) <=
(c.date[0] + c.date[1]) + fuzz) and
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 e.file not in files):
c = changeset(comment=e.comment, author=e.author,
Henrik Stuart
convert: added cvsnt mergepoint support
r7956 branch=e.branch, date=e.date, entries=[],
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 mergepoint=getattr(e, 'mergepoint', None),
branchpoints=getattr(e, 'branchpoints', set()))
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 changesets.append(c)
Benoit Boissinot
convert: use set instead of dict
r8456 files = set()
Matt Mackall
cvsps: fix up some whitespace
r6688 if len(changesets) % 100 == 0:
t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
ui.status(util.ellipsis(t, 80) + '\n')
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
c.entries.append(e)
Benoit Boissinot
convert: use set instead of dict
r8456 files.add(e.file)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 c.date = e.date # changeset date is date of latest commit in it
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 # Mark synthetic changesets
for c in changesets:
# Synthetic revisions always get their own changeset, because
# the log message includes the filename. E.g. if you add file3
# and file4 on a branch, you get four log entries and three
# changesets:
# "File file3 was added on branch ..." (synthetic, 1 entry)
# "File file4 was added on branch ..." (synthetic, 1 entry)
# "Add file3 and file4 to fix ..." (real, 2 entries)
# Hence the check for 1 entry here.
Benoit Boissinot
cvsps: fix traceback involving 'synthetic'...
r10701 c.synthetic = len(c.entries) == 1 and c.entries[0].synthetic
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # Sort files in each changeset
for c in changesets:
def pathcompare(l, r):
'Mimic cvsps sorting order'
l = l.split('/')
r = r.split('/')
nl = len(l)
nr = len(r)
n = min(nl, nr)
for i in range(n):
Matt Mackall
cvsps: fix up some whitespace
r6688 if i + 1 == nl and nl < nr:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return -1
Matt Mackall
cvsps: fix up some whitespace
r6688 elif i + 1 == nr and nl > nr:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return +1
Matt Mackall
cvsps: fix up some whitespace
r6688 elif l[i] < r[i]:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return -1
Matt Mackall
cvsps: fix up some whitespace
r6688 elif l[i] > r[i]:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return +1
return 0
def entitycompare(l, r):
return pathcompare(l.file, r.file)
c.entries.sort(entitycompare)
# Sort changesets by date
def cscmp(l, r):
Matt Mackall
cvsps: fix up some whitespace
r6688 d = sum(l.date) - sum(r.date)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if d:
return d
# detect vendor branches and initial commits on a branch
le = {}
for e in l.entries:
le[e.rcs] = e.revision
re = {}
for e in r.entries:
re[e.rcs] = e.revision
d = 0
for e in l.entries:
if re.get(e.rcs, None) == e.parent:
assert not d
d = 1
break
for e in r.entries:
if le.get(e.rcs, None) == e.parent:
assert not d
d = -1
break
return d
changesets.sort(cscmp)
# Collect tags
globaltags = {}
for c in changesets:
for e in c.entries:
for tag in e.tags:
# remember which is the latest changeset to have this tag
globaltags[tag] = c
for c in changesets:
Benoit Boissinot
convert: use set instead of dict
r8456 tags = set()
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 for e in c.entries:
Martin Geisler
convert/cvsps: use set.update for bulk update
r8483 tags.update(e.tags)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # remember tags only if this is the latest changeset to have it
Benoit Boissinot
convert: use set instead of dict
r8456 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
# Find parent changesets, handle {{mergetobranch BRANCHNAME}}
# by inserting dummy changesets with two parents, and handle
# {{mergefrombranch BRANCHNAME}} by setting two parents.
if mergeto is None:
mergeto = r'{{mergetobranch ([-\w]+)}}'
if mergeto:
mergeto = re.compile(mergeto)
if mergefrom is None:
mergefrom = r'{{mergefrombranch ([-\w]+)}}'
if mergefrom:
mergefrom = re.compile(mergefrom)
versions = {} # changeset index where we saw any particular file version
branches = {} # changeset index where we saw a branch
n = len(changesets)
i = 0
Matt Mackall
many, many trivial check-code fixups
r10282 while i < n:
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 c = changesets[i]
for f in c.entries:
versions[(f.rcs, f.revision)] = i
p = None
if c.branch in branches:
p = branches[c.branch]
else:
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 # first changeset on a new branch
# the parent is a changeset with the branch in its
# branchpoints such that it is the latest possible
# commit without any intervening, unrelated commits.
for candidate in xrange(i):
if c.branch not in changesets[candidate].branchpoints:
if p is not None:
break
continue
p = candidate
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
c.parents = []
if p is not None:
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 p = changesets[p]
# Ensure no changeset has a synthetic changeset as a parent.
while p.synthetic:
assert len(p.parents) <= 1, \
_('synthetic changeset cannot have multiple parents')
if p.parents:
p = p.parents[0]
else:
p = None
break
if p is not None:
c.parents.append(p)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
Henrik Stuart
convert: added cvsnt mergepoint support
r7956 if c.mergepoint:
if c.mergepoint == 'HEAD':
c.mergepoint = None
c.parents.append(changesets[branches[c.mergepoint]])
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 if mergefrom:
m = mergefrom.search(c.comment)
if m:
m = m.group(1)
if m == 'HEAD':
m = None
Greg Ward
cvsps: fix crash when log message refers to non-existent branch (issue1615).
r8171 try:
candidate = changesets[branches[m]]
except KeyError:
ui.warn(_("warning: CVS commit message references "
"non-existent branch %r:\n%s\n")
% (m, c.comment))
Greg Ward
issue1578: fix crash: do not use synthetic changesets as merge parents.
r7950 if m in branches and c.branch != m and not candidate.synthetic:
c.parents.append(candidate)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
if mergeto:
m = mergeto.search(c.comment)
if m:
try:
m = m.group(1)
if m == 'HEAD':
m = None
except:
m = None # if no group found then merge to HEAD
if m in branches and c.branch != m:
# insert empty changeset for merge
Matt Mackall
many, many trivial check-code fixups
r10282 cc = changeset(
author=c.author, branch=m, date=c.date,
comment='convert-repo: CVS merge from branch %s'
% c.branch,
entries=[], tags=[],
parents=[changesets[branches[m]], c])
Matt Mackall
cvsps: fix up some whitespace
r6688 changesets.insert(i + 1, cc)
branches[m] = i + 1
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
# adjust our loop counters now we have inserted a new entry
n += 1
i += 2
continue
branches[c.branch] = i
i += 1
Greg Ward
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
r7862 # Drop synthetic changesets (safe now that we have ensured no other
# changesets can have them as parents).
i = 0
while i < len(changesets):
if changesets[i].synthetic:
del changesets[i]
else:
i += 1
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 # Number changesets
for i, c in enumerate(changesets):
Matt Mackall
cvsps: fix up some whitespace
r6688 c.id = i + 1
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687
ui.status(_('%d changeset entries\n') % len(changesets))
Frank Kingswood
convert: implement two hooks in builtin cvsps
r10095 hook.hook(ui, None, "cvschangesets", True, changesets=changesets)
Frank Kingswood
convert: cvsps.py - code to generate changesets from a CVS repository
r6687 return changesets
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502
def debugcvsps(ui, *args, **opts):
Martin Geisler
convert/cvsps: wrap long lines
r8661 '''Read CVS rlog for current directory or named path in
repository, and convert the log to changesets based on matching
commit log entries and dates.
'''
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 if opts["new_cache"]:
cache = "write"
elif opts["update_cache"]:
cache = "update"
else:
cache = None
revisions = opts["revisions"]
try:
if args:
log = []
for d in args:
log += createlog(ui, d, root=opts["root"], cache=cache)
else:
log = createlog(ui, root=opts["root"], cache=cache)
except logerror, e:
ui.write("%r\n"%e)
return
changesets = createchangeset(ui, log, opts["fuzz"])
del log
# Print changesets (optionally filtered)
off = len(revisions)
branches = {} # latest version number in each branch
ancestors = {} # parent branch
for cs in changesets:
if opts["ancestors"]:
if cs.branch not in branches and cs.parents and cs.parents[0].id:
Matt Mackall
many, many trivial check-code fixups
r10282 ancestors[cs.branch] = (changesets[cs.parents[0].id - 1].branch,
Martin Geisler
convert/cvsps: wrap long lines
r8661 cs.parents[0].id)
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 branches[cs.branch] = cs.id
# limit by branches
if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
continue
if not off:
# Note: trailing spaces on several lines here are needed to have
# bug-for-bug compatibility with cvsps.
ui.write('---------------------\n')
ui.write('PatchSet %d \n' % cs.id)
Martin Geisler
convert/cvsps: wrap long lines
r8661 ui.write('Date: %s\n' % util.datestr(cs.date,
'%Y/%m/%d %H:%M:%S %1%2'))
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 ui.write('Author: %s\n' % cs.author)
ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
Matt Mackall
many, many trivial check-code fixups
r10282 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1],
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 ','.join(cs.tags) or '(none)'))
Henrik Stuart
convert: better support for CVS branchpoints (issue1447)...
r8756 branchpoints = getattr(cs, 'branchpoints', None)
if branchpoints:
ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 if opts["parents"] and cs.parents:
Matt Mackall
many, many trivial check-code fixups
r10282 if len(cs.parents) > 1:
ui.write('Parents: %s\n' %
(','.join([str(p.id) for p in cs.parents])))
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 else:
ui.write('Parent: %d\n' % cs.parents[0].id)
if opts["ancestors"]:
b = cs.branch
r = []
while b:
b, c = ancestors[b]
r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
if r:
ui.write('Ancestors: %s\n' % (','.join(r)))
ui.write('Log:\n')
ui.write('%s\n\n' % cs.comment)
ui.write('Members: \n')
for f in cs.entries:
fn = f.file
if fn.startswith(opts["prefix"]):
fn = fn[len(opts["prefix"]):]
Matt Mackall
many, many trivial check-code fixups
r10282 ui.write('\t%s:%s->%s%s \n' % (
fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
'.'.join([str(x) for x in f.revision]),
['', '(DEAD)'][f.dead]))
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 ui.write('\n')
# have we seen the start tag?
if revisions and off:
if revisions[0] == str(cs.id) or \
revisions[0] in cs.tags:
off = False
# see if we reached the end tag
Matt Mackall
many, many trivial check-code fixups
r10282 if len(revisions) > 1 and not off:
Frank Kingswood
Add debugcvsps command, replacing cvsps script
r7502 if revisions[1] == str(cs.id) or \
revisions[1] in cs.tags:
break