bk2hg

Vadim Lebedev vadim at mbdsys.com
Fri Jul 1 14:02:24 CDT 2005


Hello,

Finally i  made bk to hg convertor work...

You launch it with:

'convert-repo-bk  bkdir  hgdir  -'

It will create the hgdir and hgdir.map file and will convert from bk to 
mercurial format...

There is one misterious problem...  After the conversion process i go to 
hgdir
and do 'hg status', and it shows me all files with '?'...  the files are 
present in the manifest...
and the working directory  contents is  identical to the original bk 
directory...
so i have to do bk commit -A -t"Import from bk" 
to normalize the situation.

I suspect there is a problem with rawcommit



Vadim

-------------- next part --------------
#!/usr/bin/env python
#
# This is a generalized framework for converting between SCM
# repository formats.
#
# In its current form, it's hardcoded to convert incrementally between
# git and Mercurial.
#
# To use, you must first import the first git version into Mercurial,
# and establish a mapping between the git commit hash and the hash in
# Mercurial for that version. This mapping is kept in a simple text
# file with lines like so:
#
# <git hash> <mercurial hash>
#
# To convert the rest of the repo, run:
#
# convert-repo <git-dir> <hg-dir> <mapfile>
#
# This updates the mapfile on each commit copied, so it can be
# interrupted and can be run repeatedly to copy new commits.

import sys, os, zlib, sha, time
from mercurial import hg, ui, util, commands

class convert_bk:
    def __init__(self, path):
        self.path = os.path.abspath(path)

    def getheads(self):
        path = os.getcwd()
        os.chdir(self.path)
        h = os.popen("bk repogca " + self.path).read()
#        k = os.popen("bk changes -r%s -d':MD5KEY:'" % h.strip()).read()
        os.chdir(path)
        print "heads:", h.strip()
        return [h.strip()]

    def getfile(self, name, rev):
        path = os.getcwd()
        os.chdir(self.path)

        text = os.popen("bk get -pq -r%s %s" % (rev,name) ).read()
        os.chdir(path)

        return text

    def getchanges(self, version):
        path = os.getcwd()
        os.chdir(self.path)
        v = os.popen("bk changes -r%s -d':MD5KEY:'" % version).read().strip()        
        fh = os.popen("bk changes -v -r%s -d':DPN:|:I:\\n'" % version)
        os.chdir(path)
        changes = []
        seen = {}
        for l in fh:
          s = l.strip().split('|')
          if not "ChangeSet" == s[0] and not "BitKeeper/" in s[0] and not s[0] in seen:
             changes.append((s[0], s[1], 0))
             seen[s[0]] = True

        return changes

    def getcommit(self, version):
#        print "Rev: ", version
        path = os.getcwd()
        os.chdir(self.path)
#        print "pipe: bk changes -f -e -r%s -d':PARENT:|:MPARENT:|:USER:@:HOST:|:D: :T::TZ:\\n:COMMENTS:'" % version 
        fh = os.popen("bk changes -f -e -r%s -d':PARENT:|:MPARENT:|:USER:@:HOST:|:D: :T: :TZ:\\n:COMMENTS:'" % version )


        info  = fh.readline().split('|')
#        print "Info", info
        
	comments = []
	for l in fh:
	    comments.append(l[2:])

	message = "\n".join(comments)
            
        parents = []
        if info[0]: parents += [info[0]]
        if info[1]: parents += [info[1]]
        author = info[2]
        if ".(none)" in author:
            author = author[:-7]

#       we have YYYY/MM/DD H:M:S TZH:TZM
        dateinfo = info[3].split()
        dtime = " ".join(dateinfo[0:2])

        tzinfo = dateinfo[2].split(':')
        tzsecs = 3600*int(tzinfo[0])
        tzmins = int(tzinfo[1])
        if tzsecs < 0:
            tzmins *= -1
            
        tzsecs += tzmins*60
        tm = time.mktime(time.strptime(dtime, "%Y/%m/%d %H:%M:%S"))
	date = "%d %d" % (tm, tzsecs)

#        kparents = [ os.popen("bk changes -r%s -d':MD5KEY:'" % p).read().strip() for p in parents]  

        os.chdir(path)
#        print (kparents, author, date, message)
        return (parents, author, date, message)

    def fixparents(self, parents):
        path = os.getcwd()
        os.chdir(self.path)
        kparents = [ os.popen("bk changes -r%s -d':MD5KEY:'" % p).read().strip() for p in parents]  
        os.chdir(path)
        return kparents

class convert_mercurial:
    def __init__(self, path):
        self.path = os.path.abspath(path)
        u = ui.ui()
        self.repo = hg.repository(u, path)

    def getheads(self):
        h = self.repo.changelog.heads()
        h = [ hg.hex(x) for x in h ]
        return h
        
    def putfile(self, f, e, data):
        self.repo.wfile(f, "w").write(data)
        util.set_exec(self.repo.wjoin(f), e)

    def delfile(self, f):
        try:
            print "Removing1: ", f
            os.unlink(self.repo.wjoin(f))
            print "Removing2: ", f
            self.repo.remove([f])
        except:
            pass

    def putcommit(self, files, parents, author, dest, text):
        if not parents: parents = ["0" * 40]
        if len(parents) < 2: parents.append("0" * 40)

        seen = {}
        pl = []
        for p in parents:
            if p not in seen:
                pl.append(p)
                seen[p] = 1
        parents = pl

        p2 = parents.pop(0)
        c = self.repo.changelog.count()

#	print "count=",c
        while parents:
            p1 = p2
            p2 = parents.pop(0)
#            print "rawcommmit", p1, len(p1), p2, len(p2)
            self.repo.rawcommit(files, text, author, dest, 
                                hg.bin(p1), hg.bin(p2))
            text = "(octopus merge fixup)\n"

            
        return hg.hex(self.repo.changelog.node(c))

class convert:
    def __init__(self, source, dest, mapfile):
        self.source = source
        self.dest = dest
        self.mapfile = mapfile
        self.commitcache = {}

        self.map = {}
        for l in file(self.mapfile):
            l = l.strip()
            if l:
                sv, dv = l.split()
            self.map[sv] = dv

    def walktree(self, heads):
        visit = heads
        known = {}
        parents = {}
        while visit:
            n = visit.pop(0)
            if n in known or n in self.map: continue
            known[n] = 1
            self.commitcache[n] = self.source.getcommit(n)
            cp = self.commitcache[n][0]
            for p in cp:
                parents.setdefault(n, []).append(p)
                visit.append(p)

        return parents

    def toposort(self, parents):
        print "Sorting... 1"
        visit = parents.keys()
        seen = {}
        children = {}
        while visit:
            n = visit.pop(0)
            if n in seen: continue
            seen[n] = 1
            pc = 0
            if n in parents:
                for p in parents[n]:
                    if p not in self.map: pc += 1
                    visit.append(p)
                    children.setdefault(p, []).append(n)
            if not pc: root = n

        s = []
        removed = {}
        visit = parents.keys()
        print "Sorting... 2"        
        while visit:
            n = visit.pop(0)
            if n in removed: continue
            dep = 0
            if n in parents:
                for p in parents[n]:
                    if p in self.map: continue
                    if p not in removed:
                        # we're still dependent
                        visit.append(n)
                        dep = 1
                        break

            if not dep:
                # all n's parents are in the list
                removed[n] = 1
                s.append(n)
                if n in children:
                    for c in children[n]:
                        visit.insert(0, c)

        print "Sort done"

        return s

    def copy(self, rev):
        print "Copy rev:", rev
        p, a, d, t = self.commitcache[rev]
        files = self.source.getchanges(rev)

        for f,v,e in files:
#            print "Importing ", f,v, e
            try:
                data = self.source.getfile(f, v)
#                print "Got: ", f, v
            except IOError, inst:
                self.dest.delfile(f)
            else:
                self.dest.putfile(f, e, data)

#        print "copy: p=", p        
        r = [self.map[v] for v in p]
        f = [f for f,v,e in files]
#        print "calling putcommit", f, r, a, d, t
        self.map[rev] = self.dest.putcommit(f, r, a, d, t)
        file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))

    def convert(self):
        heads = self.source.getheads()
        parents = self.walktree(heads)
        t = self.toposort(parents)
        num = len(t)

        for c in t:
            num -= 1
            if c in self.map: continue
            desc = self.commitcache[c][3].splitlines()[0]
            print num, desc
            self.copy(c)

bkpath, hgpath, mapfile = sys.argv[1:]

if mapfile == '-':
    mapfile = ".".join([hgpath, "map"])
    try:
        file(mapfile, "r")
    except:
        cd = os.getcwd()
        util.system("bk export -r1.2 %s %s" % (bkpath,hgpath))
        util.system("chmod -R +w %s" % hgpath)
        u = ui.ui()
        os.chdir(hgpath)
        print "creating repo"
        repo = hg.repository(u, ".", create=1)
        print "committing"
        commands.commit(u, repo, addremove=1, text="Initial Import from bk", user=None, date=None)
        os.chdir(cd)
        print "creating map file"
        f = file(mapfile, "a")
        f.write("1.0 %s\n" % ("0" * 40) )
        f.write("1.1 %s\n" % ("0" * 40) )
        f.write("1.2 %s\n" % hg.hex(repo.changelog.tip()))
        f.close()
        del repo

c = convert(convert_bk(bkpath), convert_mercurial(hgpath), mapfile)
c.convert()



More information about the Mercurial mailing list