summaryrefslogtreecommitdiff
path: root/tools/cvs2fossil/lib/c2f_frev.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cvs2fossil/lib/c2f_frev.tcl')
-rw-r--r--tools/cvs2fossil/lib/c2f_frev.tcl552
1 files changed, 552 insertions, 0 deletions
diff --git a/tools/cvs2fossil/lib/c2f_frev.tcl b/tools/cvs2fossil/lib/c2f_frev.tcl
new file mode 100644
index 0000000..fa05513
--- /dev/null
+++ b/tools/cvs2fossil/lib/c2f_frev.tcl
@@ -0,0 +1,552 @@
+## -*- tcl -*-
+# # ## ### ##### ######## ############# #####################
+## Copyright (c) 2007-2008 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
+# # ## ### ##### ######## ############# #####################
+
+## Revisions per file.
+
+# # ## ### ##### ######## ############# #####################
+## Requirements
+
+package require Tcl 8.4 ; # Required runtime.
+package require snit ; # OO system.
+package require vc::tools::misc ; # Text formatting
+package require vc::fossil::import::cvs::state ; # State storage.
+package require vc::fossil::import::cvs::integrity ; # State integrity checks.
+
+# # ## ### ##### ######## ############# #####################
+##
+
+snit::type ::vc::fossil::import::cvs::file::rev {
+ # # ## ### ##### ######## #############
+ ## Public API
+
+ constructor {revnr date state thefile} {
+ set myrevnr $revnr
+ set mydate $date
+ set myorigdate $date
+ set mystate $state
+ set myfile $thefile
+ return
+ }
+
+ method defid {} {
+ set myid [incr myidcounter]
+ return
+ }
+
+ method id {} { return $myid }
+ method file {} { return $myfile }
+
+ # Basic pieces ________________________
+
+ method hasmeta {} { return [expr {$mymetaid ne ""}] }
+ method hastext {} {
+ return [expr {$mytextstart <= $mytextend}]
+ }
+
+ method setmeta {meta} { set mymetaid $meta ; return }
+ method settext {text} {
+ struct::list assign $text mytextstart mytextend
+ return
+ }
+ method setlod {lod} { set mylod $lod ; return }
+
+ method revnr {} { return $myrevnr }
+ method state {} { return $mystate }
+ method lod {} { return $mylod }
+ method date {} { return $mydate }
+
+ method isneeded {} {
+ if {$myoperation ne "nothing"} {return 1}
+ if {$myrevnr ne "1.1"} {return 1}
+ if {![$mylod istrunk]} {return 1}
+ if {![llength $mybranches]} {return 1}
+ set firstbranch [lindex $mybranches 0]
+ if {![$firstbranch haschild]} {return 1}
+ if {$myisondefaultbranch} {return 1}
+
+ # FIX: This message will not match if the RCS file was renamed
+ # manually after it was created.
+
+ set gen "file [file tail [$myfile usrpath]] was initially added on branch [$firstbranch name]."
+ set log [$myfile commitmessageof $mymetaid]
+
+ return [expr {$log ne $gen}]
+ }
+
+ method isneededbranchdel {} {
+ if {$myparentbranch eq ""} {return 1} ; # not first on a branch, needed
+ set base [$myparentbranch parent]
+ if {$base eq ""} {return 1} ; # branch has parent lod, needed
+ if {[$self LODLength] < 2} {return 1} ; # our lod contains only ourselves, needed.
+ if {$myoperation ne "delete"} {return 1} ; # Not a deletion, needed
+ if {[llength $mytags]} {return 1} ; # Have tags, needed
+ if {[llength $mybranches]} {return 1} ; # Have other branches, needed
+ if {abs($mydate - [$base date]) > 2} {return 1} ; # Next rev > 2 seconds apart, needed
+
+ # FIXME: This message will not match if the RCS file was
+ # renamed manually after it was created.
+
+ set qfile [string map {
+ . \\. ? \\? * \\* \\ \\\\ + \\+ ^ \\^ $ \\$
+ \[ \\\[ \] \\\] ( \\( ) \\) \{ \\\{ \} \\\}
+ } [file tail [$myfile usrpath]]]
+ set pattern "file $qfile was added on branch .* on \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}( \[+-\]\\d{4})?"
+ set log [$myfile commitmessageof $mymetaid]
+
+ # Not the special message, needed
+ if {![regexp -- $pattern $log]} {return 1}
+
+ # This is an unneeded initial branch delete.
+ return 0
+ }
+
+ method LODLength {} {
+ set n 1 ; # count self
+ set rev $mychild
+ while {$rev ne ""} {
+ incr n
+ set rev [$rev child]
+ }
+ return $n
+ }
+
+ # Basic parent/child linkage __________
+
+ method hasparent {} { return [expr {$myparent ne ""}] }
+ method haschild {} { return [expr {$mychild ne ""}] }
+
+ method setparent {parent} {
+ integrity assert {$myparent eq ""} {Parent already defined}
+ set myparent $parent
+ return
+ }
+
+ method cutfromparent {} { set myparent "" ; return }
+ method cutfromchild {} { set mychild "" ; return }
+
+ method setchild {child} {
+ integrity assert {$mychild eq ""} {Child already defined}
+ set mychild $child
+ return
+ }
+
+ method changeparent {parent} { set myparent $parent ; return }
+ method changechild {child} { set mychild $child ; return }
+
+ method parent {} { return $myparent }
+ method child {} { return $mychild }
+
+ # Branch linkage ______________________
+
+ method setparentbranch {branch} {
+ integrity assert {$myparentbranch eq ""} {Branch parent already defined}
+ set myparentbranch $branch
+ return
+ }
+
+ method hasparentbranch {} { return [expr {$myparentbranch ne ""}] }
+ method hasbranches {} { return [llength $mybranches] }
+
+ method parentbranch {} { return $myparentbranch }
+ method branches {} { return $mybranches }
+
+ method addbranch {branch} {
+ lappend mybranches $branch
+ return
+ }
+
+ method addchildonbranch {child} {
+ lappend mybranchchildren $child
+ return
+ }
+
+ method cutfromparentbranch {} { set myparentbranch "" ; return }
+
+ method removebranch {branch} {
+ ldelete mybranches $branch
+ return
+ }
+
+ method removechildonbranch {rev} {
+ ldelete mybranchchildren $rev
+ return
+ }
+
+ method sortbranches {} {
+ # Pass 2: CollectRev
+
+ if {[llength $mybranches] < 2} return
+
+ # Sort the branches spawned by this revision in creation
+ # order. To help in this our file gave all branches a position
+ # id, in order of their definition by the RCS archive.
+ #
+ # The creation order is (apparently) the reverse of the
+ # definition order. (If a branch is created then deleted, a
+ # later branch can be assigned the recycled branch number;
+ # therefore branch numbers are not an indication of creation
+ # order.)
+
+ set tmp {}
+ foreach branch $mybranches {
+ lappend tmp [list $branch [$branch position]]
+ }
+
+ set mybranches {}
+ foreach item [lsort -index 1 -decreasing $tmp] {
+ struct::list assign $item branch position
+ lappend mybranches $branch
+ }
+ return
+ }
+
+ method movebranchesto {rev} {
+ set revlod [$rev lod]
+ foreach branch $mybranches {
+ $rev addbranch $branch
+ $branch setparent $rev
+ $branch setlod $revlod
+ }
+ foreach branchrev $mybranchchildren {
+ $rev addchildonbranch $branchrev
+ $branchrev cutfromparent
+ $branchrev setparent $rev
+ }
+ set mybranches {}
+ set mybranchchildren {}
+ return
+ }
+
+ method removeallbranches {} {
+ set mybranches {}
+ set mybranchchildren {}
+ return
+ }
+
+ # Tag linkage _________________________
+
+ method addtag {tag} {
+ lappend mytags $tag
+ return
+ }
+
+ method tags {} { return $mytags }
+
+ method removealltags {} {
+ set mytags {}
+ return
+ }
+
+ method movetagsto {rev} {
+ set revlod [$rev lod]
+ foreach tag $mytags {
+ $rev addtag $tag
+ $tag settagrev $rev
+ $tag setlod $revlod
+ }
+ set mytags {}
+ return
+ }
+
+ # general symbol operations ___________
+
+ method movesymbolsto {rev} {
+ # Move the tags and branches attached to this revision to the
+ # destination and fix all pointers.
+
+ $self movetagsto $rev
+ $self movebranchesto $rev
+ return
+ }
+
+ # Derived stuff _______________________
+
+ method determineoperation {} {
+ # Look at the state of both this revision and its parent to
+ # determine the type opf operation which was performed (add,
+ # modify, delete, none).
+ #
+ # The important information is dead vs not-dead for both,
+ # giving rise to four possible types.
+
+ set sdead [expr {$mystate eq "dead"}]
+ set pdead [expr {$myparent eq "" || [$myparent state] eq "dead"}]
+
+ set myoperation $myopstate([list $pdead $sdead])
+ return
+ }
+
+ method operation {} { return $myoperation }
+ method retype {x} { set myoperation $x ; return }
+
+ method isondefaultbranch {} { return $myisondefaultbranch }
+
+ method setondefaultbranch {x} { set myisondefaultbranch $x ; return }
+
+ method setdefaultbranchchild {rev} { set mydbchild $rev ; return }
+ method setdefaultbranchparent {rev} {
+ set mydbparent $rev
+
+ # Retype the revision (may change from 'add' to 'change').
+
+ set sdead [expr {$myoperation ne "change"}]
+ set pdead [expr {[$rev operation] ne "change"}]
+ set myoperation $myopstate([list $pdead $sdead])
+ return
+ }
+
+ method cutdefaultbranchparent {} { set mydbparent "" ; return }
+ method cutdefaultbranchchild {} { set mydbchild "" ; return }
+
+ method defaultbranchchild {} { return $mydbchild }
+ method defaultbranchparent {} { return $mydbparent }
+
+ method hasdefaultbranchchild {} { return [expr {$mydbchild ne ""}] }
+ method hasdefaultbranchparent {} { return [expr {$mydbparent ne ""}] }
+
+ # # ## ### ##### ######## #############
+ ## Type API
+
+ typemethod istrunkrevnr {revnr} {
+ return [expr {[llength [split $revnr .]] == 2}]
+ }
+
+ typemethod isbranchrevnr {revnr _ bv} {
+ if {[regexp $mybranchpattern $revnr -> head tail]} {
+ upvar 1 $bv branchnr
+ set branchnr ${head}$tail
+ return 1
+ }
+ return 0
+ }
+
+ typemethod 2branchnr {revnr} {
+ # Input is a branch revision number, i.e. a revision number
+ # with an even number of components; for example '2.9.2.1'
+ # (never '2.9.2' nor '2.9.0.2'). The return value is the
+ # branch number (for example, '2.9.2'). For trunk revisions,
+ # like '3.4', we return the empty string.
+
+ if {[$type istrunkrevnr $revnr]} {
+ return ""
+ }
+ return [join [lrange [split $revnr .] 0 end-1] .]
+ }
+
+ typemethod 2branchparentrevnr {branchnr} {
+ # Chop the last segment off
+ return [join [lrange [split $branchnr .] 0 end-1] .]
+ }
+
+ # # ## ### ##### ######## #############
+
+ method persist {} {
+ set fid [$myfile id]
+ set lod [$mylod id]
+ set op $myopcode($myoperation)
+ set idb $myisondefaultbranch
+
+ lappend map @P@ [expr { ($myparent eq "") ? "NULL" : [$myparent id] }]
+ lappend map @C@ [expr { ($mychild eq "") ? "NULL" : [$mychild id] }]
+ lappend map @DP [expr { ($mydbparent eq "") ? "NULL" : [$mydbparent id] }]
+ lappend map @DC [expr { ($mydbchild eq "") ? "NULL" : [$mydbchild id] }]
+ lappend map @BP [expr { ($myparentbranch eq "") ? "NULL" : [$myparentbranch id] }]
+
+ set cmd {
+ INSERT INTO revision ( rid, fid, rev, lod, parent, child, isdefault, dbparent, dbchild, bparent, op, date, state, mid)
+ VALUES ($myid, $fid, $myrevnr, $lod, @P@, @C@, $idb, @DP, @DC, @BP , $op, $mydate, $mystate, $mymetaid);
+ }
+
+ state transaction {
+ state run [string map $map $cmd]
+
+ # And the branch children as well, for pass 5.
+ foreach bc $mybranchchildren {
+ set bcid [$bc id]
+ state run {
+ INSERT INTO revisionbranchchildren (rid, brid)
+ VALUES ($myid, $bcid);
+ }
+ }
+ }
+ return
+ }
+
+ # # ## ### ##### ######## #############
+ ## State
+
+ # Persistent: myid - revision.rid
+ # myfile - revision.fid
+ # mylod - revision.lod
+ # myrevnr - revision.rev
+ # mydate - revision.date
+ # mystate - revision.state
+ # mymetaid - revision.mid
+ # mytext{start,end} - revision.{cs,ce}
+ # myparent - revision.parent
+ # mychild - revision.child
+ # myparentbranch - revision.bparent
+ # myoperation - revision.op
+ # myisondefaultbranch - revision.isdefault
+ # mydbparent - revision.dbparent
+ # mydbchild - revision.dbchild
+
+ method DUMP {label} {
+ puts "$label = $self <$myrevnr> (NTDB=$myisondefaultbranch) \{"
+ puts "\tP\t$myparent"
+ puts "\tC\t$mychild"
+ puts "\tPB\t$myparentbranch"
+ puts "\tdbP\t$mydbparent"
+ puts "\tdbC\t$mydbchild"
+ foreach b $mybranches {
+ puts \t\tB\t$b
+ }
+ foreach b $mybranchchildren {
+ puts \t\tBC\t$b
+ }
+ puts "\}"
+ return
+ }
+
+ typevariable mybranchpattern {^((?:\d+\.\d+\.)+)(?:0\.)?(\d+)$}
+ # First a nonzero even number of digit groups with trailing dot
+ # CVS then sticks an extra 0 in here; RCS does not.
+ # And the last digit group.
+
+ typevariable myidcounter 0 ; # Counter for revision ids.
+ variable myid {} ; # Revision id.
+
+ variable myrevnr {} ; # Revision number of the revision.
+ variable mydate {} ; # Timestamp of the revision, seconds since epoch
+ variable myorigdate {} ; # Original unmodified timestamp.
+ variable mystate {} ; # State of the revision.
+ variable myfile {} ; # Ref to the file object the revision belongs to.
+ variable mytextstart {} ; # Start of the range of the (delta) text
+ # for this revision in the file.
+ variable mytextend {} ; # End of the range of the (delta) text
+ # for this revision in the file.
+ variable mymetaid {} ; # Id of the meta data group the revision
+ # belongs to. This is later used to put
+ # the file revisions into preliminary
+ # changesets (aka project revisions).
+ # This id encodes 4 pieces of data,
+ # namely: the project and branch the
+ # revision was committed to, the author
+ # who did the commit, and the message
+ # used.
+ variable mylod {} ; # Reference to the line-of-development
+ # object the revision belongs to. An
+ # alternative idiom would be to call it
+ # the branch the revision is on. This
+ # reference is to either project-level
+ # trunk or file-level symbol.
+
+ # Basic parent/child linkage (lines of development)
+
+ variable myparent {} ; # Ref to parent revision object. Link required because of
+ # ; # 'cvsadmin -o', which can create arbitrary gaps in the
+ # ; # numbering sequence. This is in the same line of development
+ # ; # Note: For the first revision on a branch the revision
+ # ; # it was spawned from is the parent. Only the root revision
+ # ; # of myfile's revision tree has nothing set here.
+ # ; #
+
+ variable mychild {} ; # Ref to the primary child revision object, i.e. the next
+ # ; # revision in the same line of development.
+
+ # Branch linkage ____________________
+
+ variable mybranches {} ; # List of the branches (objs) spawned by this revision.
+ variable myparentbranch {} ; # For the first revision on a branch the relevant
+ # ; # branch object. This also allows us to determine if
+ # ; # myparent is in the same LOD, or the revision the
+ # ; # branch spawned from.
+
+ # List of the revision objects of the first commits on any
+ # branches spawned by this revision on which commits occurred.
+ # This dependency is kept explicitly because otherwise a
+ # revision-only topological sort would miss the dependency that
+ # exists via -> mybranches.
+
+ variable mybranchchildren {} ; # List of the revisions (objs) which are the first
+ # ; # commits on any of the branches spawned from this
+ # ; # revision. The dependency is kept explicitly to
+ # ; # ensure that a revision-only topological sort will
+ # ; # not miss it, as it otherwise exists only via
+ # ; # mybranches.
+
+ # Tag linkage ________________________
+
+ variable mytags {} ; # List of tags (objs) associated with this revision.
+
+ # More derived data
+
+ variable myoperation {} ; # One of 'add', 'change', 'delete', or
+ # 'nothing'. Derived from our and
+ # its parent's state.
+ variable myisondefaultbranch 0 ; # Boolean flag, set if the
+ # revision is on the non-trunk
+ # default branch, aka vendor
+ # branch.
+ variable mydbparent {} ; # Reference to the last revision
+ # on the vendor branch if this is
+ # the primary child of the
+ # regular root.
+ variable mydbchild {} ; # Reference to the primary child
+ # of the regular root if this is
+ # the last revision on the vendor
+ # branch.
+
+ # dead(self) x dead(parent) -> operation
+ typevariable myopstate -array {
+ {0 0} change
+ {0 1} delete
+ {1 0} add
+ {1 1} nothing
+ }
+
+ typemethod getopcodes {} {
+ state foreachrow {
+ SELECT oid, name FROM optype;
+ } { set myopcode($name) $oid }
+ return
+ }
+
+ typevariable myopcode -array {}
+
+ # # ## ### ##### ######## #############
+ ## Internal methods
+
+ # # ## ### ##### ######## #############
+ ## Configuration
+
+ pragma -hastypeinfo no ; # no type introspection
+ pragma -hasinfo no ; # no object introspection
+ pragma -simpledispatch yes ; # simple fast dispatch
+
+ # # ## ### ##### ######## #############
+}
+
+namespace eval ::vc::fossil::import::cvs::file {
+ namespace export rev
+ namespace eval rev {
+ namespace import ::vc::tools::misc::*
+ namespace import ::vc::fossil::import::cvs::state
+ namespace import ::vc::fossil::import::cvs::integrity
+ }
+}
+
+# # ## ### ##### ######## ############# #####################
+## Ready
+
+package provide vc::fossil::import::cvs::file::rev 1.0
+return