#!/bin/sh
## -*- tcl -*- \
exec tclsh "$0" ${1+"$@"}

# # ## ### ##### ######## ############# #####################
## Copyright (c) 2007 Andreas Kupries.
#
# This software is licensed as described in the file LICENSE, which
# you should have received as part of this distribution.
#
# This software consists of voluntary contributions made by many
# individuals.  For exact contribution history, see the revision
# history and logs, available at http://fossil-scm.hwaci.com/fossil
# # ## ### ##### ######## ############# #####################

## Helper application, debugging of cvs2fossil. This application
## extracts the tree of revisions for a file of interest, specified
## either directly through its id, or indirectly through the id of a
## revision it contains, and generates a nice graphical representation
## of it (png image). It uses GraphiViz's 'dot' tool to do all the
## layouting.

# # ## ### ##### ######## ############# #####################
## Requirements, extended package management for local packages.

lappend auto_path [file join [file dirname [info script]] lib]

package require Tcl 8.4                               ; # Required runtime.
package require struct::graph                         ; # Graph handling.
package require struct::list                          ; # Higher order list ops.
package require vc::fossil::import::cvs::project::rev ; # Changesets
package require vc::fossil::import::cvs::state        ; # State storage.
package require vc::tools::misc                       ; # Min/max.
package require vc::tools::dot                        ; # Graph export to DOT.
package require vc::tools::trouble                    ; # Error reporting
package require vc::tools::log                        ; # User feedback

namespace import ::vc::fossil::import::cvs::state
namespace import ::vc::fossil::import::cvs::project::rev
namespace import ::vc::tools::dot
namespace import ::vc::tools::trouble
namespace import ::vc::tools::log
namespace import ::vc::tools::misc::*

log verbosity 0

# Process the command line, i.e. get the database to access, and file
# of interest. The latter can be specified by name, id, or indirectly
# through the id of one of the revisions it contains.

state use [lindex $argv 0]
state reading project
state reading file
state reading revision
state reading revisionbranchchildren
state reading changeset
state reading csitem
state reading csorder

set what [lindex $argv 1]
set centralrid -1

switch -exact -- $what {
    rid {
	# Get the revision of interest, identified by the internal
	# numeric id used by cvs2fossil.
	set centralrid [lindex $argv 2]

	puts "Revision : [state one { SELECT rev FROM revision WHERE rid = $centralrid }] ($centralrid)"

	# Map it to the file containing the revision of interest.
	set fid [state one { SELECT fid FROM revision WHERE rid = $centralrid }]
    }
    fid {
	# Get the file of interest, identified by internal numeric id
	# used by cvs2fossil.
	set fid [lindex $argv 2]
    }
    fname {
	# Get the file of interest, identified by its name.

	set fname [lindex $argv 2]
	set fid [state one { SELECT fid FROM file WHERE name == $fname }]
    }
    default {
	trouble fatal \
	    "Unknown spec \"$what\", expected one of \"fid\", \"fname\", or \"rid\""
    }
}
trouble abort?

set pid [state one { SELECT pid FROM file WHERE fid == $fid }]

puts "File     : [state one { SELECT name FROM file    WHERE fid = $fid }] ($fid)"
puts "Project  : [state one { SELECT name FROM project WHERE pid = $pid }] ($pid)"

# Get the data of all revisions in the file of interest, as a list for
# iteration, and as array for random access of neighbouring revisions.

array set rev {}
foreach {rid revnr lod date isdefault} [set revisions [state run {
    SELECT R.rid, R.rev, S.name, R.date, R.isdefault
    FROM revision R, symbol S
    WHERE R.fid = $fid
    AND   R.lod = S.sid
}]] {
    set cs [state run {
	SELECT CR.cid, CO.pos, CT.name
	FROM csitem CR, csorder CO, cstype CT, changeset C
	WHERE CR.iid = $rid
	AND   CR.cid = CO.cid
	AND   CR.cid = C.cid
	AND   CT.tid = C.type
    }]

    set rev($rid) [list $revnr $lod $date $isdefault $cs]
}

puts "#Revs    : [array size rev]"

# Start the graph

struct::graph dg

# Convert the revisions into nodes of the graph, and use node
# attributes to highlight various pieces of interest for the dot
# conversion. Label => Revnr, Symbol (LOD), Changeset id (if
# available), formatted date. Background fill colors to show the
# different branches ?.

foreach {rid revnr lod date isdefault} $revisions {
    set label  "$rid = <$revnr> @ $lod / [clock format $date]"
    set cs [lindex $rev($rid) 4]
    if {[llength $cs]} {
	foreach {cs ord cstype} $cs {
	    append label "\\nCS/${cstype}($cs) @$ord"
	}
    }
    set key [list rev $rid]
    dg node insert $key
    dg node set    $key label $label
    dg node set    $key shape [expr {$isdefault ? "diamond" : "box"}]
}

# Go through the revisions a second time and set up the arcs based on
# the stored successor information.

::vc::fossil::import::cvs::project::rev::rev successors dep [array names rev]

proc Ord {cmd rid} {return 0
    global rev
    set ords {}
    foreach {cs ord cstype} [lindex $rev($rid) 4] { lappend ords $ord }
    return [$cmd $ords]
}

foreach {rid children} [array get dep] {
    set max [Ord max $rid]

    foreach child $children {
	if {[catch {
	    set a [dg arc insert $rid $child]
	}]} continue

	if {$max > [Ord min $child]} {
	    puts "Backward : $rid -> $child"
	    dg arc set $a color red
	}
    }
}

# Convert the graph to dot, then run the layouter and convert to png,
# at last show the image.

vc::tools::dot layout png dg SymbolTree st.png
exec display st.png
file delete st.png
exit
