#!/usr/bin/env python

######################################################################
# rcsjoin: attach the new revisions of one rcs file to another
#          (the result is stored in a new rcs file)
# 
# Copyright (C) 2011 Klaus Aehlig
# This file may freely be distributed under terms and conditions of
# the 3-clause BSD licence
######################################################################


import md5
import subprocess

separator='----------------------------' 

printrevf    = "%10s --> %s   %s"
foundnewrevf =   "               ...%s %s @@%s@@"
predf        =     "               ...predecessor %s with hash %s is %s in base"
newrevf      =     "               --> stored as %s"

def getrevisions(filename):
    rlog = subprocess.check_output(["rlog","-q",filename])
    lines = rlog.splitlines()
    revisions = []
    for i in range(len(lines)-1):
        if lines[i] == separator and lines[i+1].split()[0] == 'revision':
            revisions.append(lines[i+1].split()[1])
    revisions=[[int(n) for n in x.split('.')] for x in revisions]
    revisions.sort()
    revisions=['.'.join(["%d" % n for n in x]) for x in revisions]
    return revisions

def dec_revision(revision):
    if revision == '':
        return ''
    numbers = revision.split('.')
    if numbers[-1] == '1':
        return '.'.join(numbers[0:-2])
    numbers[-1] = "%d" % (int(numbers[-1])-1)
    return '.'.join(numbers)

def predecessor(revision,revisions):
    revision = dec_revision(revision)
    while revision not in revisions and revision != '':
        revision = dec_revision(revision)
    return revision

def describe_version(revision, filename):
    answer = {}
    rlog = subprocess.check_output(["rlog","-q","-r%s" % revision,filename])
    lines = rlog.splitlines()
    while lines[0] != separator or lines[1].split()[0] != 'revision':
        lines.pop(0)
    lines.pop(0)
    lines.pop(0)
    stateline = lines.pop(0)
    while lines[0].split(':')[0] == 'branches':
        lines.pop(0)
    lines.pop()
    lines.pop()
    lines.append('')
    answer['message'] = '\n'.join(lines)
    params = stateline.split(';')
    for param in params:
        keyvalue = param.split(': ',1)
        if len(keyvalue) > 1:
            answer[keyvalue[0].lstrip()]=keyvalue[1]
    answer['BODY'] = subprocess.check_output(["co", "-r%s" % revision, "-q", "-p", filename])
    return answer

def do_hash(string):
    m = md5.new()
    m.update(string)
    return m.hexdigest()


def inventory(filename):
    inv = {'' : do_hash('') }
    revisions = getrevisions(filename) # Note: properly sorted
    for r in revisions:
        desc = describe_version(r,filename)
        pred = predecessor(r,revisions)
        state = '%s %d\n%s\n%s' % (inv[pred],len(desc['message']),desc['message'],desc['BODY'])
        inv[r] = do_hash(state)
    return inv

def printinv(revisions,inv):
    """
    pretty print the given revisions of an inventory
    """
    for r in revisions:
        print printrevf  % (r,inv[r],'')

def attach(rcs, predrev, desc):
    """
    attach a new revision to an rcs file

    @param rcs: the rcsfile (basename)
    @param predrev: the revision that should be the predecessor of the new one
    @param desc: a dictionary describing the version to be attached

    @returns: the newly created revision
    """
    assert predrev != ''
    oldrevisions = getrevisions(rcs)
    subprocess.check_call(["co","-q","-l", "-r%s" % predrev, rcs])
    objfile = file(rcs, mode="w")
    objfile.write(desc['BODY'])
    objfile.close()
    subprocess.check_call(["ci","-q",
                           "-m%s" % desc['message'],
                           "-w%s" % desc['author'],
                           "-d%s" % desc['date'],
                           rcs])
    newrevisions = getrevisions(rcs)
    for r in newrevisions:
        if r not in oldrevisions:
            return r
    assert False


def join(base,branch,result):
    """
    create a new rcsfile result, by attaching the revisions of branch not
    in the inventory of base to it.

    @param base: the rcs-file to start with
    @param branch: the branched of rcs-file
    @param result: the basename of the to create rcs-file
    """
    baserev = getrevisions(base)
    baseinv = inventory(base)
    print base
    printinv(baserev, baseinv)

    branchrev = getrevisions(branch)
    branchinv = inventory(branch)

    resulthash={}
    subprocess.check_call(["cp", base, "%s,v" % result]) 
    for k,v in baseinv.items():
        resulthash[v] = k

    print branch
    for r in branchrev:
        isnew = branchinv[r] not in resulthash
        print printrevf % (r,branchinv[r], "ok (%s)" % resulthash[branchinv[r]] if not isnew else "NEW")
        if isnew:
            desc = describe_version(r,branch)
            print foundnewrevf % (desc['author'],desc['date'], desc['message'].rstrip())
            pred = predecessor(r,branchrev)
            predhash = branchinv[pred]
            predr = resulthash[predhash]
            print predf % (pred, predhash, predr)
            newrev = attach(result, predr, desc)
            print newrevf % newrev
            resulthash[branchinv[r]] = newrev

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 4:
        print "Usage: %s base,v branch,v join" % sys.argv[0]
    join(sys.argv[1], sys.argv[2], sys.argv[3])

