changeset 6690:127e8c3466d1

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.
author Frank Kingswood <frank@kingswood-consulting.co.uk>
date Sun, 15 Jun 2008 16:05:46 +0100
parents d2ac53fe216e
children 0dba955c2636
files hgext/convert/cvs.py tests/test-convert-cvs-branch tests/test-convert-cvs-branch.out tests/test-convert-cvs-builtincvsps tests/test-convert-cvs-builtincvsps.out
diffstat 5 files changed, 467 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/cvs.py	Sun Jun 15 15:59:53 2008 +0100
+++ b/hgext/convert/cvs.py	Sun Jun 15 16:05:46 2008 +0100
@@ -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 @@
         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 @@
         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 @@
             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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-cvs-branch	Sun Jun 15 16:05:46 2008 +0100
@@ -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'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-cvs-branch.out	Sun Jun 15 16:05:46 2008 +0100
@@ -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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-cvs-builtincvsps	Sun Jun 15 16:05:46 2008 +0100
@@ -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'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-cvs-builtincvsps.out	Sun Jun 15 16:05:46 2008 +0100
@@ -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
+