# HG changeset patch # User Frank Kingswood # Date 2008-06-15 15:05:46 # Node ID 127e8c3466d1969b495fd58eb114191d666a72df # Parent d2ac53fe216e70dac6b6e1251d0e6d3a5ea8d7f7 convert: cvs.py - Allow user to use built-in CVS changeset code. tests: add two testcases for CVS conversion with builtin CVS including a testcase for issue 1148. diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py --- a/hgext/convert/cvs.py +++ b/hgext/convert/cvs.py @@ -3,8 +3,10 @@ import os, locale, re, socket from cStringIO import StringIO from mercurial import util +from mercurial.i18n import _ from common import NoRepo, commit, converter_source, checktool +import cvsps class convert_cvs(converter_source): def __init__(self, ui, path, rev=None): @@ -14,10 +16,13 @@ class convert_cvs(converter_source): if not os.path.exists(cvs): raise NoRepo("%s does not look like a CVS checkout" % path) + checktool('cvs') self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q') cvspsexe = self.cmd.split(None, 1)[0] - for tool in (cvspsexe, 'cvs'): - checktool(tool) + self.builtin = cvspsexe == 'builtin' + + if not self.builtin: + checktool(cvspsexe) self.changeset = {} self.files = {} @@ -28,10 +33,11 @@ class convert_cvs(converter_source): self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1] self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1] self.encoding = locale.getpreferredencoding() - self._parse() + + self._parse(ui) self._connect() - def _parse(self): + def _parse(self, ui): if self.changeset: return @@ -56,80 +62,114 @@ class convert_cvs(converter_source): id = None state = 0 filerevids = {} - for l in util.popen(cmd): - if state == 0: # header - if l.startswith("PatchSet"): - id = l[9:-2] - if maxrev and int(id) > maxrev: - # ignore everything - state = 3 - elif l.startswith("Date"): - date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) - date = util.datestr(date) - elif l.startswith("Branch"): - branch = l[8:-1] - self.parent[id] = self.lastbranch.get(branch, 'bad') - self.lastbranch[branch] = id - elif l.startswith("Ancestor branch"): - ancestor = l[17:-1] - # figure out the parent later - self.parent[id] = self.lastbranch[ancestor] - elif l.startswith("Author"): - author = self.recode(l[8:-1]) - elif l.startswith("Tag:") or l.startswith("Tags:"): - t = l[l.index(':')+1:] - t = [ut.strip() for ut in t.split(',')] - if (len(t) > 1) or (t[0] and (t[0] != "(none)")): - self.tags.update(dict.fromkeys(t, id)) - elif l.startswith("Log:"): - # switch to gathering log - state = 1 - log = "" - elif state == 1: # log - if l == "Members: \n": - # switch to gathering members - files = {} - oldrevs = [] - log = self.recode(log[:-1]) - state = 2 - else: - # gather log - log += l - elif state == 2: # members - if l == "\n": # start of next entry - state = 0 - p = [self.parent[id]] - if id == "1": - p = [] - if branch == "HEAD": - branch = "" - if branch: - latest = None - # the last changeset that contains a base - # file is our parent - for r in oldrevs: - latest = max(filerevids.get(r, None), latest) - if latest: - p = [latest] + + if self.builtin: + # builtin cvsps code + ui.status(_('using builtin cvsps\n')) + + db = cvsps.createlog(ui, cache='update') + db = cvsps.createchangeset(ui, db, + fuzz=int(ui.config('convert', 'cvsps.fuzz', 60)), + mergeto=ui.config('convert', 'cvsps.mergeto', None), + mergefrom=ui.config('convert', 'cvsps.mergefrom', None)) + + for cs in db: + if maxrev and cs.id>maxrev: + break + id = str(cs.id) + cs.author = self.recode(cs.author) + self.lastbranch[cs.branch] = id + cs.comment = self.recode(cs.comment) + date = util.datestr(cs.date) + self.tags.update(dict.fromkeys(cs.tags, id)) + + files = {} + for f in cs.entries: + files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]), + ['', '(DEAD)'][f.dead]) - # add current commit to set - c = commit(author=author, date=date, parents=p, - desc=log, branch=branch) - self.changeset[id] = c - self.files[id] = files - else: - colon = l.rfind(':') - file = l[1:colon] - rev = l[colon+1:-2] - oldrev, rev = rev.split("->") - files[file] = rev + # add current commit to set + c = commit(author=cs.author, date=date, + parents=[str(p.id) for p in cs.parents], + desc=cs.comment, branch=cs.branch or '') + self.changeset[id] = c + self.files[id] = files + else: + # external cvsps + for l in util.popen(cmd): + if state == 0: # header + if l.startswith("PatchSet"): + id = l[9:-2] + if maxrev and int(id) > maxrev: + # ignore everything + state = 3 + elif l.startswith("Date"): + date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) + date = util.datestr(date) + elif l.startswith("Branch"): + branch = l[8:-1] + self.parent[id] = self.lastbranch.get(branch, 'bad') + self.lastbranch[branch] = id + elif l.startswith("Ancestor branch"): + ancestor = l[17:-1] + # figure out the parent later + self.parent[id] = self.lastbranch[ancestor] + elif l.startswith("Author"): + author = self.recode(l[8:-1]) + elif l.startswith("Tag:") or l.startswith("Tags:"): + t = l[l.index(':')+1:] + t = [ut.strip() for ut in t.split(',')] + if (len(t) > 1) or (t[0] and (t[0] != "(none)")): + self.tags.update(dict.fromkeys(t, id)) + elif l.startswith("Log:"): + # switch to gathering log + state = 1 + log = "" + elif state == 1: # log + if l == "Members: \n": + # switch to gathering members + files = {} + oldrevs = [] + log = self.recode(log[:-1]) + state = 2 + else: + # gather log + log += l + elif state == 2: # members + if l == "\n": # start of next entry + state = 0 + p = [self.parent[id]] + if id == "1": + p = [] + if branch == "HEAD": + branch = "" + if branch: + latest = None + # the last changeset that contains a base + # file is our parent + for r in oldrevs: + latest = max(filerevids.get(r, None), latest) + if latest: + p = [latest] - # save some information for identifying branch points - oldrevs.append("%s:%s" % (oldrev, file)) - filerevids["%s:%s" % (rev, file)] = id - elif state == 3: - # swallow all input - continue + # add current commit to set + c = commit(author=author, date=date, parents=p, + desc=log, branch=branch) + self.changeset[id] = c + self.files[id] = files + else: + colon = l.rfind(':') + file = l[1:colon] + rev = l[colon+1:-2] + oldrev, rev = rev.split("->") + files[file] = rev + + # save some information for identifying branch points + oldrevs.append("%s:%s" % (oldrev, file)) + filerevids["%s:%s" % (rev, file)] = id + elif state == 3: + # swallow all input + continue self.heads = self.lastbranch.values() finally: diff --git a/tests/test-convert-cvs-branch b/tests/test-convert-cvs-branch new file mode 100644 --- /dev/null +++ b/tests/test-convert-cvs-branch @@ -0,0 +1,60 @@ +#!/bin/sh + +# This is http://www.selenic.com/mercurial/bts/issue1148 + +"$TESTDIR/hghave" cvs || exit 80 + +cvscall() +{ + cvs -f "$@" +} + +echo "[extensions]" >> $HGRCPATH +echo "convert = " >> $HGRCPATH +echo "graphlog = " >> $HGRCPATH +echo "[convert]" >> $HGRCPATH +echo "cvsps=builtin" >> $HGRCPATH + +echo % create cvs repository +mkdir cvsrepo +cd cvsrepo +export CVSROOT=`pwd` +export CVS_OPTIONS=-f +cd .. + +cvscall -q -d "$CVSROOT" init + +echo % Create a new project + +mkdir src +cd src +echo "1" > a > b +cvscall import -m "init" src v0 r0 +cd .. +cvscall co src +cd src + +echo % Branch the project + +cvscall tag -b BRANCH +cvscall up -r BRANCH + +echo % Modify file a, then b, then a + +echo "2" > a +cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo "2" > b +cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo "3" > a +cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo % Convert + +cd .. +hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' + +echo % Check the result + +hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n' diff --git a/tests/test-convert-cvs-branch.out b/tests/test-convert-cvs-branch.out new file mode 100644 --- /dev/null +++ b/tests/test-convert-cvs-branch.out @@ -0,0 +1,53 @@ +% create cvs repository +% Create a new project +N src/a +N src/b + +No conflicts created by this import + +cvs checkout: Updating src +U src/a +U src/b +% Branch the project +cvs tag: Tagging . +T a +T b +cvs update: Updating . +% Modify file a, then b, then a +cvs commit: Examining . +checking in src/a,v +cvs commit: Examining . +checking in src/b,v +cvs commit: Examining . +checking in src/a,v +% Convert +assuming destination src-hg +initializing destination src-hg repository +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +4 Initial revision +3 init +2 mod a +1 mod b +0 mod a again +updating tags +% Check the result +o 5 () update tags files: .hgtags +| +| o 4 (BRANCH) mod a again files: a +| | +| o 3 (BRANCH) mod b files: b +| | +| o 2 (BRANCH) mod a files: a +| | +| o 1 (v0) init files: +|/ +o 0 () Initial revision files: a b + diff --git a/tests/test-convert-cvs-builtincvsps b/tests/test-convert-cvs-builtincvsps new file mode 100644 --- /dev/null +++ b/tests/test-convert-cvs-builtincvsps @@ -0,0 +1,99 @@ +#!/bin/sh + +"$TESTDIR/hghave" cvs || exit 80 + +cvscall() +{ + cvs -f "$@" +} + +echo "[extensions]" >> $HGRCPATH +echo "convert = " >> $HGRCPATH +echo "graphlog = " >> $HGRCPATH +echo "[convert]" >> $HGRCPATH +echo "cvsps=builtin" >> $HGRCPATH + +echo % create cvs repository +mkdir cvsrepo +cd cvsrepo +export CVSROOT=`pwd` +export CVS_OPTIONS=-f +cd .. + +cvscall -q -d "$CVSROOT" init + +echo % create source directory +mkdir src-temp +cd src-temp +echo a > a +mkdir b +cd b +echo c > c +cd .. + +echo % import source directory +cvscall -q import -m import src INITIAL start +cd .. + +echo % checkout source directory +cvscall -q checkout src + +echo % commit a new revision changing b/c +cd src +sleep 1 +echo c >> b/c +cvscall -q commit -mci0 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert fresh repo +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/a +cat src-hg/b/c + +echo % convert fresh repo with --filemap +echo include b/c > filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo % commit new file revisions +cd src +echo a >> a +echo c >> b/c +cvscall -q commit -mci1 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert again +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/a +cat src-hg/b/c + +echo % convert again with --filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo % commit branch +cd src +cvs -q update -r1.1 b/c +cvs -q tag -b branch +cvs -q update -r branch +echo d >> b/c +cvs -q commit -mci2 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert again +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/a +cat src-hg/b/c + +echo % convert again with --filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +cat src-hg/b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo "graphlog = " >> $HGRCPATH +hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n' diff --git a/tests/test-convert-cvs-builtincvsps.out b/tests/test-convert-cvs-builtincvsps.out new file mode 100644 --- /dev/null +++ b/tests/test-convert-cvs-builtincvsps.out @@ -0,0 +1,139 @@ +% create cvs repository +% create source directory +% import source directory +N src/a +N src/b/c + +No conflicts created by this import + +% checkout source directory +U src/a +U src/b/c +% commit a new revision changing b/c +checking in src/b/c,v +% convert fresh repo +initializing destination src-hg repository +using builtin cvsps +collecting CVS rlog +5 log entries +creating changesets +3 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +2 Initial revision +1 import +0 ci0 +updating tags +a +c +c +% convert fresh repo with --filemap +initializing destination src-filemap repository +using builtin cvsps +collecting CVS rlog +5 log entries +creating changesets +3 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +2 Initial revision +1 import +rolling back last transaction +0 ci0 +updating tags +c +c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +% commit new file revisions +checking in src/a,v +checking in src/b/c,v +% convert again +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +4 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci1 +a +a +c +c +c +% convert again with --filemap +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +4 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci1 +c +c +c +3 ci1 files: b/c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +% commit branch +U b/c +T a +T b/c +checking in src/b/c,v +% convert again +using builtin cvsps +collecting CVS rlog +8 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci2 +a +a +c +d +% convert again with --filemap +using builtin cvsps +collecting CVS rlog +8 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci2 +c +d +4 ci2 files: b/c +3 ci1 files: b/c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +o 5 (branch) ci2 files: b/c +| +| o 4 () ci1 files: a b/c +| | +| o 3 () update tags files: .hgtags +| | +| o 2 () ci0 files: b/c +|/ +| o 1 (INITIAL) import files: +|/ +o 0 () Initial revision files: a b/c +