bk2hg

Vadim Lebedev vadim at mbdsys.com
Fri Jul 1 17:29:53 CDT 2005


Skipped content of type multipart/alternative-------------- 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 = path

    def getheads(self):
        path = os.getcwd()
        os.chdir(self.path)
        h = os.popen("bk repogca " + os.path.abspath(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):
        if version == "1.0" or version == "1.1":
            return []
        
        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


repo = None
u = None
class convert_mercurial:
    def __init__(self, path):
        self.path = 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)
#            sys.exit(0)

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)
        os.chdir(hgpath)
        print "creating repo"
        util.system("hg init")
        util.system('hg commit -A -t"initial commit"');
        tip = os.popen("hg tip").readline().split(":")[2]
        print "creating map file"
        os.chdir(cd)
        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" % tip)
        f.close()


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



More information about the Mercurial mailing list