# Copyright 2005, 2006 by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
#
# This program is free software under the GNU GPL (>=v2)
# Read the file COPYING coming with the software for details.

import sys
import time
import os
import binascii
import sha
#import cPickle

import CVStools
import mercurial.ui
import mercurial.localrepo
import chast


def mkhash(author, date, msg):
    return sha.sha(author + str(date) + msg).digest()

class CvsToHg:
    def __init__(self, cvsroot, module, hgroot, progress):
        self.hgui = mercurial.ui.ui(interactive=False)
        self.hgroot = hgroot
        self.hgrepo = mercurial.localrepo.localrepository(self.hgui, hgroot)
        self.cvsroot = cvsroot
        self.cvsrepo = CVStools.CVSrepo(cvsroot)
        self.module = module
        self.chast = None
        self.progress = progress
        self.currevs = {}

    def _init(self):
        try:
            f = file(self.hgrepo.join("cvs20hg.log"))
            for l in f.readlines():
                h, d = l.split(" ")
                self.currevs[binascii.unhexlify(h)] = int(d)
            #self.currevs = cPickle.load(f)
        except IOError:
            pass
        # find last "real" rev
        tip = r = self.hgrepo.changelog.tip()
        while 1:
            rd = self.hgrepo.changelog.read(r)
            self.record(rd, False)
            if rd[1] != "repo surgery":
                break
            r = self.hgrepo.changelog.parents(r)[0]
        if type(rd[2]) in (type([]), type(())):
            date = int(rd[2][0])
        else:
            date = int(rd[2].split(' ')[0])

        self.skiprevs = self.currevs.copy()

        if date != 0:
            self.progress("Checking out working dir")
            self.hgrepo.recover()
            self.hgrepo.update(tip, force=True)
            self.hgrepo.manifest.mapcache = None    # XXX fix up bug

        files = [os.path.join(self.module, f[1]) for f in self.hgrepo.walk(tip)]
        self.chast = chast.ChastAggregator(self.cvsroot, self.module,
                                           self.progress, files=files,
                                           since=date)

    # Keeps a record of "running" changesets
    def record(self, c, remove=True):
        if c.__class__ != chast.changeset:
            if type(c[2]) in (type([]), type(())):
                date = int(c[2][0])
            else:
                date = int(c[2].split(' ')[0])
            c = chast.changeset(author=c[1], date=date, msg=c[4])

        hash = mkhash(author=c.author, date=c.enddate, msg=c.msg)
        if remove:
            dl = []
            for k, v in self.currevs.iteritems():
                if v < c.date:
                    dl.append(k)
            for k in dl:
                del self.currevs[k]
        self.currevs[hash] = c.enddate

        # write out the data
        fn = self.hgrepo.join("cvs20hg.log.new")
        f = file(fn, "w")
        f.writelines(map(lambda t: "%s %d\n" % (binascii.hexlify(t[0]), t[1]),
                         self.currevs.iteritems()))
        #cPickle.dump(self.currevs, f, -1)
        f.close()
        os.rename(fn, self.hgrepo.join("cvs20hg.log"))

    def commitset(self, c):
        files = []
        for cf, r, t, a, m in c.revs:
            if not cf.startswith(os.path.join(self.module, "")):
                raise "file '%s' not in module '%s'!" % (cf, self.module)
            f = cf[len(self.module) + len(os.path.sep):]

            files.append(f)
            if m == chast.CH_DEL:
                self.hgrepo.remove([f], unlink=True)
            else:
                rawdata, mode = self.cvsrepo.checkout(cf, r)
                self.hgrepo.wfile(f, "w").write(rawdata)
                os.chmod(self.hgrepo.wjoin(f), mode | 0200)
                if m == chast.CH_ADD:
                    self.hgrepo.add([f])

        self.hgrepo.commit(files, c.msg, c.author, "%d %d" % (c.date, 0))
        self.record(c)

    def sync(self, tag=None):
        if not self.chast:
            self._init()

        changes = self.chast.changes(tag)
        if changes and changes[0].author == "repo surgery":
            c = changes.pop(0)
            self.progress("Fixing up repo surgery, %d files" % len(c.revs))
            self.commitset(c)

        cnt = len(changes)
        for c in changes:
            cnt -= 1
            # We might have already committed this revision,
            # so just skip it if it already exists.
            # As changesets are strongly ordered, we can throw all
            # "previous" ones away as well.
            if self.skiprevs:
                hash = mkhash(author=c.author, date=c.enddate, msg=c.msg)
                if hash in self.skiprevs:
                    del self.skiprevs[hash]
                    continue

            self.progress("Committing changeset by %s at %s, %d files"
                          " (%d to go)"
                          % (c.author, time.asctime(time.gmtime(c.date)),
                             len(c.revs), cnt))
            self.commitset(c)


if __name__ == "__main__":
    def printfn(s):
        print s
    if len(sys.argv) == 5:
        cvstohg = CvsToHg(sys.argv[1], sys.argv[2], sys.argv[4], printfn)
        cvstohg.sync(tag=sys.argv[3])
    else:
        cvstohg = CvsToHg(sys.argv[1], sys.argv[2], sys.argv[3], printfn)
        cvstohg.sync(tag=None)
