#!/usr/bin/python

import sys
import os
import os.path
import rcsparse
import re
import time
import getopt

files = []
versions = []
commits = {}

scratch_dir = "$sd/workspace"
co_scratchfile = "%s.%d" % ("/tmp/cvscvt-coscratch", os.getpid())
msg_scratchfile = "$sd/cvscvt-msgscratch.$$"
startup_dir = os.getcwd()
verbose = 0

keep_versions = re.compile("^1\.[0-9]*$")
version_no = re.compile("[0-9]*$")
version_tail = re.compile("\.[0-9]*$")
re_comma_v = re.compile(",v$")
re_attic1 = re.compile("/Attic/")
re_attic2 = re.compile("^Attic/")
re_cvsignore1 = re.compile("^.cvsignore$")
re_cvsignore2 = re.compile("/.cvsignore$")
re_squote = re.compile("'")

class commitment:
  def __init__(self, tag):
    self.tag = tag
    self.revisions = []
  
  def descrip(self):
    return "%s %s %s" % (self.tag, self.revisions[0].author, time.asctime(time.gmtime(self.revisions[0].timestamp)))

class rcsversion:

  """

  Encode the information about each revision that we will need later
  in order to (a) reconstruct what the configurations were from the
  checkin messages, and (b) have enough information to reconstruct the
  content when we are ready to put it into OpenCM

  """

  def __init__(self, name, revision, timestamp, author, state, branches, next):
    self.rcsname = name
    self.clearname = re_comma_v.sub("", self.rcsname)
    self.clearname = re_attic1.sub("/", self.clearname)
    self.clearname = re_attic2.sub("", self.clearname)
    self.basename = os.path.basename(self.clearname)
    self.opencmname = re_cvsignore1.sub(".opencm-rules", self.clearname)
    self.opencmname = re_cvsignore2.sub("/.opencm-rules", self.opencmname)
    self.revision = revision
    self.timestamp = timestamp
    self.author = author
    self.state = state
    self.branches = branches
    self.next = next
    self.tag = 0
    self.text = ""
    self.log = "\n"
        
  def __repr__(self):
    return "(%d %s %s %s)" % (self.tag, self.rcsname, self.revision, self.timestamp)
    
  def set_log(self, logmsg):
    self.log = logmsg
    return

  def set_text(self, text):
    #self.text = text
    return

  def opdescrip(self):
    return "%s %s %s" % (self.operation, self.revision, self.clearname)
  
  def error(s):
    print "%s: %s" % (sys.argv[0], s)
    sys.exit(1)
    
##################################################################
#
# Logic for parsing the RCS files:
#
##################################################################

class CvtSink:
  """Decode all of the various versions so that we can reconstitute
  them into configurations that then be can be checked into OpenCM.

  The rcsparse logic proceeds in three phases:

  1. It walks the RCS version tree and announces to us each version that
     it discovers.

  2. It parses the RCS description information and gives us that -- I
     am unclear (at the moment) what exactly this is.

  3. It parses the rcs delta text for each version, extracting both
     the log message and the text of that version. The 'text', in this
     context, is the text of the *delta*, not the text of the version
     -- we will separately need to do a bunch of crud to reconstruct
     the actual content for any given version.

  In the current logic I am preserving all of this, but the 'text'
  field clearly needs to be dropped in the first pass. As an example,
  the size of the EROS tree is 136+ Mbytes, which is pushing the
  limits of the swap area on most systems. We will therefore need to
  do a separate pass to reconstruct the content of each version of
  each file as appropriate.

  The logic of operation here, then, is to build a dictionary
  containing each version of each RCS file. We will subsequently
  manipulate this dictionary.

  """
  def __init__(self, nm):
    global sha
    import sha
    self.rcsname = nm
    self.myrevs = {}

  def set_head_revision(self, revision):
    #print revision
    return

  def set_principal_branch(self, branch_name):
    #print branch_name
    return

  def define_tag(self, name, revision):
    #print name, revision
    return

  def set_comment(self, comment):
    #print comment
    return

  def set_description(self, description):
    #print description
    return

  def define_revision(self, revision, timestamp, author, state,
                      branches, next):

    entity = rcsversion(self.rcsname, revision, timestamp, author, state, branches, next)

    if keep_versions.match(revision):
      self.myrevs[revision] = entity
      versions.append(entity)

    #print revision, timestamp, author, state, branches, next
    return

  def set_revision_info(self, revision, log, text):
    if self.myrevs.has_key(revision):
      self.myrevs[revision].set_log(log)
      self.myrevs[revision].set_text(text)
    #print revision, sha.new(log).hexdigest(), sha.new(text).hexdigest()
    return

  def tree_completed(self):
    # Wander through the resulting versions in proper numeric order,
    # figuring out what operations were what:
    brstate = {}

    keys = self.myrevs.keys()
    
    keys.sort(lambda x, y: cmp(int(version_no.search(x).group()), int(version_no.search(y).group())))

    #print "%s: %s" % (self.rcsname, keys)
    
    for k in keys:
      br = version_tail.sub("",k)

      if not brstate.has_key(br):
        brstate[br] = "dead"
      
      v = self.myrevs[k]

      if (self.rcsname != v.rcsname):
        raise "RCSname mismatch: %s vs %s" % (self.rcsname, v.rcsname)
      
      if (v.state == "Exp" and brstate[br] == "dead"):
        v.operation = "A"
        brstate[br] = "live"
      elif (v.state == "Exp" and brstate[br] == "live"):
        v.operation = "M"
      elif (v.state == "Exp"):
        raise "%s:%s: Unknown object state transition (%s -> %s)!" % (self.rcsname, v.revision, brstate[br], v.state)
      elif (v.state == "dead" and brstate[br] == "live"):
        brstate[br] = "dead"
        v.operation = "D"
      elif (v.state == "dead" and brstate[br] == "dead" and version_no.search(v.revision).group() == "1"):
        # This is a weird special case -- file was added on a branch, and CVS
        # inserted and initially dead version 1.1 on the main trunk, or
        # a branch was created and the file was immediately deleted on the
        # branch. 
        v.operation = "I"
      elif (v.state == "dead" and brstate[br] == "dead"):
	# I dont know why this happened, but it exists in OpenBSD cvs.
	v.operation = "D"
      else:
        raise "%s:%s: Unknown object state transition (%s -> %s)!" % (self.rcsname, v.revision, brstate[br], v.state)
    
    #print 'tree_completed'
    return

  def parse_completed(self):
    #print 'parse_completed'
    return


##################################################################
#
# Logic for expanding the directory hierarchy:
#
##################################################################
def addfile(path):
    global files
    normpath = os.path.normpath(path)
    if not normpath in files:
        files = files + [normpath]
        if verbose:
          print "Added file %s" % normpath
    
def adddir(path):
  contents = os.listdir(path)
  for name in contents:
    if name == "CVS":
      continue

    fullname = os.path.join(path, name)
    addpath(fullname)
    
def addpath(path):
    if not os.path.exists(path):
        error("File not found: %s" % path)
    elif os.path.isfile(path):
        addfile(path)
    elif os.path.isdir(path):
        adddir(path)
    else:
        error("Unknown file type for: %s" % path)


##################################################################
#
# Debugging visualization
#
##################################################################

from Tkinter import *
from tkFileDialog import *
from ScrolledText import ScrolledText

class VersionFrame(Frame):
  def handleSelect(self, event):
    items = self.listbox.curselection()
    try:
      items = map(int, items)
    except ValueError: pass

    self.doSelect(items[0])

  def doSelect(self, ndx):
    self.listbox.see(ndx)
    self.listbox.selection_set(ndx)

    v = self.listmap[ndx]

    self.textbox.config(state=NORMAL)
    self.textbox.delete('0.0',END)

    self.textbox.insert(END, "Tag: ", "LABEL")
    self.textbox.insert(END, v.tag)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "RCS File: ", "LABEL")
    self.textbox.insert(END, v.rcsname)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Clear Name: ", "LABEL")
    self.textbox.insert(END, v.clearname)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "OpenCM Name: ", "LABEL")
    self.textbox.insert(END, v.opencmname)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Revision: ", "LABEL")
    self.textbox.insert(END, v.revision)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Timestamp: ", "LABEL")
    self.textbox.insert(END, v.timestamp)
    self.textbox.insert(END, " (")
    self.textbox.insert(END, time.asctime(time.gmtime(v.timestamp)))
    self.textbox.insert(END, ")\n")

    self.textbox.insert(END, "Author: ", "LABEL")
    self.textbox.insert(END, v.author)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "State: ", "LABEL")
    self.textbox.insert(END, v.state)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Operation: ", "LABEL")
    self.textbox.insert(END, v.operation)
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Log: ", "LABEL")
    self.textbox.insert(END, v.log, "LOG")
    self.textbox.insert(END, "\n")

    self.textbox.insert(END, "Text:\n", "LABEL")

    os.system("co -q -p%s %s > %s" % (v.revision, v.rcsname, co_scratchfile))
    f = open(co_scratchfile)
    s = f.read()
    f.close()
    os.remove(co_scratchfile)
    self.textbox.insert(END, s, "CONTENT")
    self.textbox.insert(END, "\n")

    #self.textbox.insert(END, v.details())
    self.textbox.config(state=DISABLED)

  def clear(self):
    self.listbox.delete(0,END)
    self.textbox.delete('0.0',END)
    self.listmap = []
    
  def append(self, v, labelfn = str):
    self.listbox.insert(END, labelfn(v))
    self.listmap.append(v)
    
  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.pack(side=BOTTOM, expand=YES, fill=BOTH)

    Label(self, text='Raw Version List').pack(side=TOP, expand=NO, fill=X)

    text = ScrolledText(self, height=8)
    text.pack(side=BOTTOM, expand=YES, fill=BOTH)

    text.tag_config("LABEL", font=("Helvetica", 10, "bold"))
    text.tag_config("LOG", foreground="blue")
    text.tag_config("CONTENT", foreground="purple")

    Label(self, text='Version Description').pack(side=BOTTOM, expand=NO, fill=X)
  
    scroll = Scrollbar(self)
    list = Listbox(self, height=10)

    list.config(font=("Helvetica", 10), exportselection=0, yscrollcommand=scroll.set, relief=SUNKEN)
    list.pack(side=LEFT, expand=YES, fill=BOTH)

    scroll.config(command=list.yview, relief=SUNKEN)
    scroll.pack(side=RIGHT, fill=BOTH)
  
    list.config(selectmode=SINGLE, setgrid=1)

    self.listbox = list
    self.textbox = text
    self.listmap = []

    #list.selection_handle(command=self.handleSelect)
    list.bind('<ButtonRelease-1>', self.handleSelect)
    
class CommitFrame(Frame):
  def doSelect(self, ndx):
    self.listbox.see(ndx)
    self.listbox.selection_set(ndx)
    cmt = self.listmap[ndx]

    self.vg.clear()

    pos = 0
    
    for v in cmt.revisions:
      self.vg.append(v, lambda x: x.opdescrip())
      pos = pos + 1

    self.vg.doSelect(0)
    
  def handleSelect(self, event):
    items = self.listbox.curselection()
    try:
      items = map(int, items)
    except ValueError: pass

    self.doSelect(items[0])


  def clear(self):
    self.listbox.delete('0.0',END)
    self.listmap = []
    
  def append(self, v, labelfn = str):
    self.listbox.insert(END, labelfn(v))
    self.listmap.append(v)
    
  def __init__(self, vg, master=None):
    Frame.__init__(self, master)
    self.pack(side=BOTTOM, expand=YES, fill=BOTH)

    Label(self, text='Commit List').pack(side=TOP, expand=NO, fill=X)
  
    scroll = Scrollbar(self)
    list = Listbox(self,height=5)

    list.config(exportselection=0, yscrollcommand=scroll.set, relief=SUNKEN)
    list.pack(side=LEFT, expand=YES, fill=BOTH)

    scroll.config(command=list.yview, relief=SUNKEN)
    scroll.pack(side=LEFT, fill=BOTH)
  
    list.config(selectmode=SINGLE, setgrid=1)

    self.listbox = list
    self.listmap = []

    self.vg = vg
    
    list.bind('<ButtonRelease-1>', self.handleSelect)
    
class AppGui(Frame):
  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.pack(side=BOTTOM, expand=YES, fill=BOTH)

    self.master.title("cvsconvert")
    self.master.iconname("cvsconvert")

    subframe = Frame(self)
    Button(subframe, text="Save and Exit", command=self.savescript).pack(side=LEFT, expand=NO, fill=X)
    Button(subframe, text="Exit", command=self.quit).pack(side=LEFT, expand=NO, fill=X)
    subframe.pack(side=BOTTOM, expand=YES, fill=BOTH)

    subframe = Frame(self)
    self.repositoryName = StringVar()
    Label(subframe, text = "Repository:").pack(side=LEFT, expand=NO, fill=NONE)
    Entry(subframe, textvariable = self.repositoryName).pack(side=LEFT, expand=YES, fill=X)
    subframe.pack(side=TOP, expand=YES, fill=BOTH)
    
    subframe = Frame(self)
    self.petName = StringVar()
    Label(subframe, text = "Pet Name:").pack(side=LEFT, expand=NO, fill=NONE)
    Entry(subframe, textvariable = self.petName).pack(side=LEFT, expand=YES, fill=X)
    subframe.pack(side=TOP, expand=YES, fill=BOTH)
    
    subframe = Frame(self)
    self.projectName = StringVar()
    Label(subframe, text = "Project Name:").pack(side=LEFT, expand=NO, fill=NONE)
    Entry(subframe, textvariable = self.projectName).pack(side=LEFT, expand=YES, fill=X)
    subframe.pack(side=TOP, expand=YES, fill=BOTH)
    
    subframe = Frame(self)
    self.authorName = StringVar()
    Label(subframe, text = "Your Name:").pack(side=LEFT, expand=NO, fill=NONE)
    Entry(subframe, textvariable = self.authorName).pack(side=LEFT, expand=YES, fill=X)
    subframe.pack(side=TOP, expand=YES, fill=BOTH)
    
    subframe = Frame(self)
    self.userName = StringVar()
    Label(subframe, text = "OpenCM User (Key) Name:").pack(side=LEFT, expand=NO, fill=NONE)
    Entry(subframe, textvariable = self.userName).pack(side=LEFT, expand=YES, fill=X)
    subframe.pack(side=TOP, expand=YES, fill=BOTH)
    
    self.text = ScrolledText(self,height=10)
    self.text.pack(side=BOTTOM, expand=YES, fill=BOTH)

    self.text.tag_config("BOLD", underline=1, foreground="blue", font=("Helvetica", 10, "bold italic"))
    self.text.tag_config("CMD", foreground="black", font=("Helvetica", 10, "bold"))
    self.text.tag_config("COMMENT", foreground="red", font=("Helvetica", 10, "bold"))
    self.text.tag_config("INPUT", foreground="blue", font=("Helvetica", 10))

    self.text.tag_config("DEBUG", foreground="green", font=("Helvetica", 10))

    Button(self, text="Update Conversion Script", command=self.genscript).pack(side=BOTTOM, expand=NO, fill=X)

    self.vf = VersionFrame(self)
    self.vf.pack(side=RIGHT, expand=YES, fill=BOTH)
    self.cf = CommitFrame(self.vf, self)
    self.cf.pack(side=LEFT, expand=YES, fill=BOTH)

  def genCheckOut(self, v):
    dirname = os.path.join(scratch_dir, os.path.dirname(v.clearname))
    cvs_outputname = os.path.join(scratch_dir, v.clearname)
    opencm_outputname = os.path.join(scratch_dir, v.opencmname)
    rcs_file = os.path.join(startup_dir, v.rcsname);
      
    self.text.insert(END, "if [ ! -d %s ]; then mkdir -p %s; fi\n" % (dirname, dirname), "CMD")
    self.text.insert(END, "(cd %s; co -f -q -r%s '%s')\n" % (dirname, v.revision, rcs_file), "CMD")
    self.text.insert(END, "checkstatus $? \"checkout failed\"\n", "CMD")
    if v.basename == ".cvsignore":
      self.text.insert(END, "# DANGER: CVSIGNORE FILE!!\n", "DEBUG")
      self.text.insert(END, "process_cvsignore \"%s\" \"%s\"\n" % (cvs_outputname, opencm_outputname), "CMD");
    
  def genCommit(self, c):
    adds = []
    deletes = []
    mods = []
    active = []

    # sort by name within commit for ease of reading
    c.revisions.sort(lambda x, y: cmp(x.clearname, y.clearname))

    for v in c.revisions:
      if v.operation == "A":
        adds.append(v)
        active.append(v)
      elif v.operation == "D":
        deletes.append(v)
        active.append(v)
      elif v.operation == "M":
        mods.append(v)
        active.append(v)
        
    cmtmsg = "CVS author %s\nCVS timestamp %s (%s)\n\n%s\n\n" % (v.author, v.timestamp, time.asctime(time.gmtime(v.timestamp)), v.log)
    
    for v in active:
      cmtmsg = "%sCVS: %c %s %s\n" % (cmtmsg, v.operation, v.revision, v.clearname)

    # We eyeballed it broken down by type this way and decided it looked
    # better (was easier to understand) when it was purely alphabetical.
    #
    #for v in adds:
    #  cmtmsg = "%sCVS: A %s %s\n" % (cmtmsg, v.revision, v.clearname)
    #for v in deletes:
    #  cmtmsg = "%sCVS: D %s %s\n" % (cmtmsg, v.revision, v.clearname)
    #for v in mods:
    #  cmtmsg = "%sCVS: M %s %s\n" % (cmtmsg, v.revision, v.clearname)
                         
    if len(active) == 0:
      self.text.insert(END, "# NOTHING in commit %s\n" % c.tag, "COMMENT")
      return
    
    self.text.insert(END, "# BEGIN replaying commit %s\n" % c.tag, "COMMENT")
    self.text.insert(END, "\n")

    self.text.insert(END, "echo BEGIN replaying commit %s\n" % c.tag, "CMD")
    self.text.insert(END, "\n")

    for v in active:
      self.genCheckOut(v)
      
    self.text.insert(END, "\n")

    if len(adds) > 0:
      avec = adds
      while len(avec) > 0:
        self.text.insert(END, "cd %s && ${OPENCM} --flush-io add" % scratch_dir, "CMD")
        map (lambda x, self=self: self.text.insert(END, " '%s'" % x.opencmname, "INPUT"), avec[0:10])
        self.text.insert(END, "\n", "CMD")
        self.text.insert(END, "checkstatus $? \"add failed\"\n", "CMD")
        avec = avec[10:]

    if len(deletes) > 0:
      dvec = deletes
      while len(dvec) > 0:
        self.text.insert(END, "cd %s && ${OPENCM} --flush-io rm" % scratch_dir, "CMD")
        map (lambda x, self=self: self.text.insert(END, " '%s'" % x.opencmname, "INPUT"), dvec[0:10])
        self.text.insert(END, "\n", "CMD")
        self.text.insert(END, "checkstatus $? \"remove failed\"\n", "CMD")
        dvec = dvec[10:]

    # Run a forced status check on the files we know have
    # changed. This is for the purpose of a side effect: forcing a
    # hash recomputation on the files that we know have changed to
    # avoid a timestamp collision.  Scripts type faster than people
    # do.
    
    # Note: Following activity is illegal in 17 states.
    avec = active
    while len(avec) > 0:
      self.text.insert(END, "cd %s && ${OPENCM} --flush-io --force-hash status" % scratch_dir, "CMD")
      map (lambda x, self=self: self.text.insert(END, " %s" % x.opencmname, "INPUT"), avec[0:10])
      self.text.insert(END, "\n", "CMD")
      self.text.insert(END, "checkstatus $? \"status check failed\"\n", "CMD")
      avec = avec[10:]


    self.text.insert(END, "\n")

    self.text.insert(END, "cat > %s << 'THIS_IS_A_COMMIT_MESSAGE_FROM_CVS'\n" % msg_scratchfile, "CMD")
    self.text.insert(END, cmtmsg, "INPUT");
    self.text.insert(END, "THIS_IS_A_COMMIT_MESSAGE_FROM_CVS\n", "CMD")
    
    # re_squote.sub("'\"'\"'", cmtmsg))

    self.text.insert(END, "cd %s && ${OPENCM} --flush-io commit --messagefile %s\n" %
                     (scratch_dir, msg_scratchfile), "CMD")
    self.text.insert(END, "checkstatus $? \"commit failed\"\n", "CMD")
    self.text.insert(END, "rm %s\n" % msg_scratchfile, "CMD")

    self.text.insert(END, "# END replaying commit %s\n" % c.tag, "COMMENT")

    self.text.insert(END, "\n")

  def savescript(self):
    self.genscript()
    f = asksaveasfile()
    s = self.text.get(0.0,END)
    f.write(s)
    f.close

    self.quit()

    return
  
  def genscript(self):
    self.text.delete('0.0',END)
    self.text.insert(END, "#/bin/sh\n", "CMD")
    self.text.insert(END, "\n")
    #self.text.insert(END, "echo When prompted, fill in project description information...\n", "COMMENT")
    #self.text.insert(END, "\n")
    self.text.insert(END, "if [ \"x${EDITOR}\" = \"x\" ]\n", "CMD")
    self.text.insert(END, "then\n", "CMD")
    self.text.insert(END, "  export EDITOR=vi\n", "CMD")
    self.text.insert(END, "fi\n", "CMD")
    self.text.insert(END, "\n")
    self.text.insert(END, "OPENCM_REPOSITORY=${OPENCM_REPOSITORY:-'%s'}\n" % self.repositoryName.get(), "CMD")
    self.text.insert(END, "export OPENCM_REPOSITORY\n", "CMD")
    if self.authorName.get() != "":
      self.text.insert(END, "OPENCM_AUTHOR=${OPENCM_AUTHOR:-'%s'}\n" % self.authorName.get(), "CMD")
      self.text.insert(END, "export OPENCM_AUTHOR\n", "CMD")
    if self.userName.get() != "":
      self.text.insert(END, "OPENCM_USER=${OPENCM_USER:-'%s'}\n" % self.userName.get(), "CMD")
      self.text.insert(END, "export OPENCM_USER\n", "CMD")
    self.text.insert(END, "\n")

    self.text.insert(END, "OPENCM=${OPENCM:-cm}\n", "CMD")
    self.text.insert(END, "export OPENCM\n", "CMD")
    self.text.insert(END, "TMPDIR=${TMPDIR:-/tmp}\n", "CMD")
    self.text.insert(END, "sd=$(mktemp -d ${TMPDIR}/scratchme.XXXXXX)\n", "CMD")
    self.text.insert(END, "\n")

    self.text.insert(END, "checkstatus()\n");
    self.text.insert(END, "{\n");
    self.text.insert(END, "  if [ $1 -ne 0 ]\n");
    self.text.insert(END, "  then\n");
    self.text.insert(END, "    echo $2: status $1\n");
    self.text.insert(END, "    exit $1\n");
    self.text.insert(END, "  fi\n");
    self.text.insert(END, "}\n");
    self.text.insert(END, "\n")
    self.text.insert(END, "process_cvsignore()\n");
    self.text.insert(END, "{\n");
    self.text.insert(END, "  echo CONVERTING .cvsignore: $1 '->' $2\n");
    self.text.insert(END, "  sed 's/^/exclude /' $1 > $2\n");
    self.text.insert(END, "  rm -f $1\n");
    self.text.insert(END, "}\n");
    self.text.insert(END, "\n")

    self.text.insert(END, "${OPENCM} create project %s --name '%s' -m 'New project created by cvsconvert.py'\n" % (self.petName.get(), self.projectName.get()), "CMD")
    
    self.text.insert(END, "checkstatus $? \"create project failed\"\n", "CMD");

    self.text.insert(END, "rm -rf %s\n" % scratch_dir, "CMD")
    self.text.insert(END, "mkdir -p %s\n" % scratch_dir, "CMD")
    self.text.insert(END, "\n")
    self.text.insert(END, "# Check out empty project so we have something to work with\n", "COMMENT")
    self.text.insert(END, "(cd %s;${OPENCM} checkout %s)\n" % (scratch_dir, self.petName.get()), "CMD")
    self.text.insert(END, "checkstatus $? \"opencm checkout failed\"\n", "CMD");
    self.text.insert(END, "\n")

    cmts = commits.values()
    cmts.sort(lambda x, y: cmp(int(x.tag), int(y.tag)))
    
    for c in cmts:
      self.genCommit(c)
      
    self.text.insert(END, "\n")
    self.text.insert(END, "echo CONVERSION COMPLETE. Cleaning up...\n", "CMD");
    self.text.insert(END, "rm -rf %s\n" % scratch_dir, "CMD")

    #self.text.insert(END, "Someday I will build a script here. ")
    #self.text.insert(END, "Watch this space!", "BOLD")
    return


def show_commits():
  ag = AppGui()
  cmts = commits.values()
  cmts.sort(lambda x, y: cmp(int(x.tag), int(y.tag)))
  
  for c in cmts:
    ag.cf.append(c, lambda x: x.descrip())

  ag.cf.doSelect(0)
  ag.genscript()
  ag.mainloop()



##################################################################
#
# Main application driver
#
##################################################################

if __name__ == '__main__':
  opts, args = getopt.getopt(sys.argv[1:], "p:r:v")

  #print opts
  
  for (o, a) in opts:
    if o == "-v":
      verbose = 1
      
  for path in args:
    addpath(path)

  files.sort()

  for f in files:
    rcsparse.Parser().parse(open(f), CvtSink(f))

    if verbose:
      print "Parsed file %s" % f

    #print versions

  # I initially thought to first sort things by log message
  # and then by date, but it is likely that some junk log messages
  # will appear multiple times.
  #
  # Therefore, the revised strategy is to sort by date and tag
  # all sequences of identical log messages as being part of a single
  # commit. This may result in more commit than there were, but it at
  # least preserves the ordering.
  
  # Sort all versions by their date first:
  versions.sort(lambda x, y: cmp(x.timestamp, y.timestamp))

  # Iterate through, tagging by string equality. Anything with the same
  # tag ends up in the same commit.

  # Guard against the possibility that a single file got changed twice
  # with the same commit message by also bumping the tag number if a
  # file would otherwise end up appearing twice in the same commit. We
  # learned about this the hard way.
    
  cur_logmsg = versions[0].log
  cur_author = versions[0].author
  tag = 1
  filenames = []
  for v in versions:
    if cur_logmsg != v.log or cur_author != v.author or v.opencmname in filenames:
      filenames = []
      cur_logmsg = v.log
      cur_author = v.author
      tag = tag + 1
    v.tag = tag
    filenames.append(v.opencmname)
      
  #print versions
    
  # Iterate through again, partitioning the resulting tagged versions
  # by their commit sequence number:

  for v in versions:
    if not commits.has_key(v.tag):
      commits[v.tag] = commitment(v.tag)

    commits[v.tag].revisions.append(v)
    
  for c in commits.values():
    c.revisions.sort(lambda x, y: cmp(x.clearname, y.clearname))

  #print versions

  show_commits()
