Check-in [d8c18fc148]
Not logged in
Overview

SHA1 Hash:d8c18fc148f545ddcb8769389177a892a18666e8
Date: 2007-09-17 00:56:40
User: aku
Comment:Reworked the CVS handling code to have a simpler API, more like the reworked Fossil API. The API now has a form where adding the handling of branches should not require complex changes in the import controller any longer. Extended the system to allow the user to restrict the importing to a sub-directory of the chosen repository, via the new switch --project. This is required to pull a SF CVS repository apart into the various projects it may have. Example: Under Tcl we have 3 projects, namely Tcl itself, sampleextension, and Thread.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified tools/import-cvs.tcl from [c75797ae19] to [8655038b63].

@@ -42,10 +42,11 @@
 # Requirements
 
 package require Tcl 8.4
 package require vc::tools::log          ; # User Feedback
 package require vc::fossil::import::cvs ; # Importer Control
+package require vc::cvs::ws             ; # CVS frontend
 
 namespace eval ::import {
     namespace import ::vc::fossil::import::cvs::*
 }
 
@@ -69,10 +70,11 @@
     while {[string match "-*" [set opt [this]]]} {
 	switch -exact -- $opt {
 	    --breakat     { next ; import::configure -breakat [this] }
 	    --nosign      {        import::configure -nosign       1 }
 	    --saveto      { next ; import::configure -saveto  [file normalize [this]] }
+	    --project     { next ; import::configure -project [this] }
 	    -v            { incr verbosity ; ::vc::tools::log::verbosity $verbosity }
 	    -h            -
 	    default       usage
 	}
 	next
@@ -80,16 +82,12 @@
 
     remainder
     if {[llength $argv] != 2} usage
     foreach {cvs fossil} $argv break
 
-    if {
-	![file exists      $cvs] ||
-	![file readable    $cvs] ||
-	![file isdirectory $cvs]
-    } {
-	usage "CVS directory missing, not readable, or not a directory."
+    if {![::vc::cvs::ws::check $cvs msg]} {
+	usage $msg
     } elseif {[file exists $fossil]} {
 	usage "Fossil destination repository exists already."
     }
 
     return
@@ -124,10 +122,11 @@
     global argv0
     puts stderr "Usage: $argv0 ?-v? ?--nosign? ?--breakat id? ?--saveto path? cvs-repository fossil-repository"
     if {$text eq ""} {
 	puts stderr "       --nosign:  Do not sign the imported changesets."
 	puts stderr "       --breakat: Stop just before committing the identified changeset."
+	puts stderr "       --project: Path in the CVS repository to limit the import to."
 	puts stderr "       --saveto:  Save commit command to the specified file."
 	puts stderr "       -v:        Increase log verbosity. Can be used multiple times."
     } else {
 	puts stderr "       $text"
     }

Modified tools/lib/cvs.tcl from [a015f1382f] to [11e2bbce8e].

@@ -19,52 +19,169 @@
 }
 
 # -----------------------------------------------------------------------------
 # API
 
-# Define repository directory.
-
-proc ::vc::cvs::ws::at {path} {
-    variable base [file normalize $path]
-    write 0 cvs "Base: $base"
+# vc::cvs::ws::configure key value    - Configure the subsystem.
+# vc::cvs::ws::check     src mv       - Check if src is a CVS repository directory.
+# vc::cvs::ws::begin     src          - Start new workspace and return the top-
+#                                       most directory co'd files are put into.
+# vc::cvs::ws::ncsets    ?-import?    - Retrieve number of csets (all/to import)
+# vc::cvs::ws::foreach   csvar script - Run the script for each changeset, the
+#                                       id of the current changeset stored in
+#                                       the variable named by csvar.
+# vc::cvs::ws::done                   - Close workspace and delete it.
+# vc::cvs::ws::isadmin path           - Check if path is an admin file of CVS
+# vc::cvs::ws::checkout id            - Have workspace contain the changeset id.
+#
+# Configuration keys:
+#
+# -project path - Sub directory under 'src' to limit the import to.
+
+# -----------------------------------------------------------------------------
+# API Implementation
+
+proc ::vc::cvs::ws::configure {key value} {
+    variable project
+
+    switch -exact -- $key {
+	-project { set project $value }
+	default {
+	    return -code error "Unknown switch $key, expected \
+                                   -project"
+	}
+    }
+    return
+}
+
+proc ::vc::cvs::ws::check {src mv} {
+    variable project
+    upvar 1 $mv msg
+    if {
+	![fileutil::test $src         erd msg "CVS Repository"] ||
+	![fileutil::test $src/CVSROOT erd msg "CVS Admin directory"] ||
+	(($project ne "") &&
+	 ![fileutil::test $src/$project erd msg "Project directory"])
+    } {
+	return 0
+    }
+    return 1
+}
+
+proc ::vc::cvs::ws::begin {src} {
+    variable project
+    variable base
+
+    set src [file normalize $src]
+    if {![check $src msg]} {
+	return -code error $msg
+    }
+    set base $src
+    write 0 cvs "Base:    $base"
+    if {$project eq ""} {
+	write 0 cvs "Project: <ALL>"
+    } else {
+	write 0 cvs "Project: $project"
+    }
+
+    # OLD api calls ... TODO rework for more structure ...
+    scan     ; # Gather revision data from the archives
+    csets    ; # Group changes into sets
+    rtree    ; # Build revision tree (trunk only right now).
+
+    set w [workspace]   ; # OLD api ... TODO inline
+    if {$project ne ""} {
+	set w $w/$project
+	file mkdir $w
+    }
+    return $w
+}
+
+proc ::vc::cvs::ws::done {} {
+    variable cwd
+    variable workspace
+    cd $cwd
+    file delete -force $workspace
     return
 }
 
-namespace eval ::vc::cvs::ws {
-    # Toplevel repository directory
-    variable base {}
-}
+proc ::vc::cvs::ws::foreach {cv script} {
+    # OLD api ... TODO inline
+    uplevel 1 [list ::vc::cvs::ws::foreach_cset $cv 0 $script]
+}
+
+proc ::vc::cvs::ws::ncsets {args} {
+    variable ncs
+    variable ntrunk
+
+    if {[llength $args] > 1} {
+	return -code error "wrong#args: Expected ?-import?"
+    } elseif {[llength $args] == 1} {
+	if {[set k [lindex $args 0]] ne "-import"} {
+	    return -code "Unknown switch $k, expected -import"
+	} else {
+	    return $ntrunk
+	}
+    }
+
+    return  $ncs
+}
+
+proc ::vc::cvs::ws::isadmin {path} {
+    # Check if path is a CVS admin file.
+    if {[string match CVS/*   $path]} {return 1}
+    if {[string match */CVS/* $path]} {return 1}
+    return 0
+}
+
+proc ::vc::cvs::ws::checkout {id} {
+    variable workspace ; cd $workspace
+    wssetup $id ; # OLD api ... TODO inline
+}
+
+# -----------------------------------------------------------------------------
+# Internals - Old API for now.
 
 # Scan repository, collect archives, parse them, and collect revision
 # information (file, revision -> date, author, commit message)
 
 proc ::vc::cvs::ws::scan {} {
+    variable project
     variable base
     variable npaths
     variable rpaths
     variable timeline
 
     write 0 cvs {Scanning directory hierarchy}
 
     set n 0
-    foreach rcs [fileutil::findByPattern $base -glob *,v] {
-	set rcs [fileutil::stripPath $base $rcs]
-	# Now rcs is relative to base
+    set d $base ; if {$project ne ""} {append d /$project}
+
+    ::foreach rcs [fileutil::findByPattern $d -glob *,v] {
+	set rcs [fileutil::stripPath $d $rcs]
+	# Now rcs is relative to base/project
 
 	write 1 cvs "Archive $rcs"
 
-	if {[string match CVSROOT* $rcs]} {
+	if {[string match CVSROOT/* $rcs]} {
 	    write 2 cvs {Ignored. Administrative file}
 	    continue
 	}
 
 	# Derive the regular path from the rcs path. Meaning: Chop of
 	# the ",v" suffix, and remove a possible "Attic".
 	set f [string range $rcs 0 end-2]
 	if {"Attic" eq [lindex [file split $rcs] end-1]} {
-	    set f [file join [file dirname [file dirname $f]] [file tail $f]]
-	    if {[file exists $base/$f,v]} {
+
+	    # The construction below ensures that Attic/X maps to X
+	    # instead of ./X. Otherwise, Y/Attic/X maps to Y/X.
+
+	    set fx [file dirname [file dirname $f]]
+	    set f  [file tail $f]
+	    if {$fx ne "."} { set f [file join $fx $f] }
+
+	    if {[file exists $d/$f,v]} {
 		# We have a regular archive and an Attic archive
 		# refering to the same user visible file. Ignore the
 		# file in the Attic.
 
 		write 2 cvs "Ignored. Attic superceded by regular archive"
@@ -84,18 +201,18 @@
 		continue
 	    }
 	}
 
 	# Get the meta data we need (revisions, timeline, messages).
-	set meta [process $base/$rcs]
+	set meta [process $d/$rcs]
 
 	set npaths($rcs) $f
 	set rpaths($f) $rcs
 
 	array set p $meta
 
-	foreach {rev ts} $p(date) {_ a} $p(author) {_ cm} $p(commit) {_ st} $p(state) {
+	::foreach {rev ts} $p(date) {_ a} $p(author) {_ cm} $p(commit) {_ st} $p(state) {
 	    set op [expr {($rev eq "1.1") ? "A" : "M"}]
 	    if {$st eq "dead"} {set op "R"}
 
 	    # A dead-first revision is rev 1.1 with op R. For an
 	    # example see the file memchan/DEPENDENCIES. Such a file
@@ -146,20 +263,20 @@
 
     write 0 cvs "Processing timeline"
 
     set n 0
     CSClear
-    foreach ts [lsort -dict [array names timeline]] {
+    ::foreach ts [lsort -dict [array names timeline]] {
 
 	# op tstamp author revision file commit
 	# 0  1      2      3        4    5/end
 	# b         c                    a
 
 	set entries [lsort -index 2 [lsort -index 0 [lsort -index end $timeline($ts)]]]
 	#puts [join $entries \n]
 
-	foreach entry  $entries {
+	::foreach entry  $entries {
 	    if {![CSNone] && [CSNew $entry]} {
 		CSSave
 		CSClear
 		#puts ==\n$reason
 	    }
@@ -203,12 +320,12 @@
     set b      0 ; # No branch csets found yet.
 
     # Extracting the trunk is easy, simply by looking at the involved
     # version numbers.
 
-    foreach c [lrange [lsort -integer [array names csets]] 1 end] {
-	foreach {u cm s e rd f} $csets($c) break
+    ::foreach c [lrange [lsort -integer [array names csets]] 1 end] {
+	::foreach {u cm s e rd f} $csets($c) break
 
 	# Ignore branch changes, just count them for the statistics.
 	if {$rd != 2} {
 	    incr b
 	    continue
@@ -243,40 +360,27 @@
 
     cd     $workspace ; # Checkouts go here.
     return $workspace
 }
 
-proc ::vc::cvs::ws::wsignore {path} {
-    # Ignore CVS admin files.
-    if {[string match */CVS/* $path]} {return 1}
-    return 0
-}
-
-proc ::vc::cvs::ws::wsclear {} {
-    variable cwd
-    variable workspace
-    cd $cwd
-    file delete -force $workspace
-    return
-}
-
 proc ::vc::cvs::ws::wssetup {c} {
     variable csets
     variable base
+    variable project
 
     # pwd = workspace
 
-    foreach {u cm s e rd fs} $csets($c) break
+    ::foreach {u cm s e rd fs} $csets($c) break
 
     write 1 cvs "@  $s"
 
-    foreach l [split [string trim $cm] \n] {
+    ::foreach l [split [string trim $cm] \n] {
 	write 1 cvs "|  $l"
     }
 
-    foreach {f or} $fs {
-	foreach {op r} $or break
+    ::foreach {f or} $fs {
+	::foreach {op r} $or break
 	write 2 cvs "$op  $f $r"
 
 	if {$op eq "R"} {
 	    # Remove file from workspace. Prune empty directories.
 	    #
@@ -299,18 +403,19 @@
 	    }
 	} else {
 	    # Added or modified, put the requested version of the file
 	    # into the workspace.
 
+	    if {$project ne ""} {set f $project/$f}
 	    if {[catch {
 		dova -d $base co -r $r $f
 	    } msg]} {
 		if {[string match {*invalid change text*} $msg]} {
 		    # The archive of the file is corrupted and the
 		    # chosen version not accessible due to that. We
 		    # report the problem, but otherwise ignore it. As
-		    # a consequence the fossil repository will not
+		    # a consequence the destination repository will not
 		    # contain the full history of the named file. By
 		    # ignoring the problem we however get as much as
 		    # is possible.
 
 		    write 0 cvs "EE Corrupted archive file. Inaccessible revision."
@@ -362,24 +467,10 @@
 	set c [lindex [$rtree children $c] 0]
     }
     return
 }
 
-proc ::vc::cvs::ws::root {} {
-    return 0
-}
-
-proc ::vc::cvs::ws::ntrunk {} {
-    variable ntrunk
-    return  $ntrunk
-}
-
-proc ::vc::cvs::ws::ncsets {} {
-    variable ncs
-    return  $ncs
-}
-
 # -----------------------------------------------------------------------------
 # Internal helper commands: Changeset inspection and construction.
 
 proc ::vc::cvs::ws::CSClear {} {
     upvar 1 start start end end cm cm user user files files lastd lastd
@@ -402,11 +493,11 @@
 proc ::vc::cvs::ws::CSNew {entry} {
     upvar 1 start start end end cm cm user user files files lastd lastd reason reason
 
     #puts -nonewline stdout . ; flush stdout
 
-    foreach {op ts a rev f ecm} $entry break
+    ::foreach {op ts a rev f ecm} $entry break
 
     # User change
     if {$a ne $user} {set reason user ; return 1}
 
     # File already in current cset
@@ -430,12 +521,12 @@
     upvar 1 start start end end cm cm user user files files lastd lastd
 
     set csets($ncs) [list $user $cm $start $end $lastd [array get files]]
 
     # Record which revisions of a file are in what csets
-    foreach {f or} [array get files] {
-	foreach {_ rev} $or break
+    ::foreach {f or} [array get files] {
+	::foreach {_ rev} $or break
 	set cmap([list $f $rev]) $ncs
     }
 
     #CSDump $ncs
 
@@ -444,11 +535,11 @@
 }
 
 proc ::vc::cvs::ws::CSAdd {entry} {
     upvar 1 start start end end cm cm user user files files lastd lastd
 
-    foreach {op ts a rev f ecm} $entry break
+    ::foreach {op ts a rev f ecm} $entry break
 
     if {$start eq ""} {set start $ts}
     set end       $ts
     set cm        $ecm
     set user      $a
@@ -457,26 +548,30 @@
     return
 }
 
 proc ::vc::cvs::ws::CSDump {c} {
     variable csets
-    foreach {u cm s e rd f} $csets($c) break
+    ::foreach {u cm s e rd f} $csets($c) break
 
     puts "$u $s"; regsub -all {.} $u { } b
     puts "$b $e"
-    foreach {f or} $f {
-	foreach {o r} $or break
+    ::foreach {f or} $f {
+	::foreach {o r} $or break
 	puts "$b $o $f $r"
     }
     return
 }
 
+# -----------------------------------------------------------------------------
+
 namespace eval ::vc::cvs::ws {
-    namespace export at scan csets rtree workspace wsignore wsclear wssetup \
-	foreach_cset root ntrunk ncsets
+    variable base    {} ; # Toplevel repository directory
+    variable project {} ; # Sub directory to limit the import to.
+
+    namespace export configure begin done foreach ncsets checkout
 }
 
 # -----------------------------------------------------------------------------
 # Ready
 
 package provide vc::cvs::ws 1.0
 return

Modified tools/lib/fossil.tcl from [a0b0c0df17] to [a6c49a2ab2].

@@ -62,13 +62,15 @@
     }
     return
 }
 
 proc ::vc::fossil::ws::begin {origin} {
-    variable rp [file normalize [fileutil::tempfile import2_fsl_rp_]]
+    variable base [file normalize $origin]
+    variable rp   [file normalize [fileutil::tempfile import2_fsl_rp_]]
 
     cd $origin
+
     dova new  $rp ; # create and ...
     dova open $rp ; # ... connect
 
     write 0 fossil "Repository: $rp"
     return
@@ -81,10 +83,13 @@
     return
 }
 
 proc ::vc::fossil::ws::commit {cset user timestamp message} {
     variable lastuuid
+    variable base
+
+    cd $base
 
     # Commit the current state of the workspace. Scan for new and
     # removed files and issue the appropriate fossil add/rm commands
     # before actually comitting.
 
@@ -200,10 +205,11 @@
     variable breakat {} ; # Do not stop
     variable saveto  {} ; # Do not save commit message
     variable appname {} ; # Name of importer application using the package.
     variable ignore  {} ; # No files to ignore.
 
+    variable base     {} ; # Workspace directory
     variable rp       {} ; # Repository the package works on.
     variable lastuuid {} ; # Uuid of last imported changeset.
 
     namespace export configure begin done commit
 }

Modified tools/lib/importcvs.tcl from [4409d3ed45] to [8476a3bcc6].

@@ -18,23 +18,24 @@
     namespace eval fossil { namespace import ::vc::fossil::ws::* }
     namespace eval stats  { namespace import ::vc::fossil::import::stats::* }
     namespace eval map    { namespace import ::vc::fossil::import::map::* }
 
     fossil::configure -appname cvs2fossil
-    fossil::configure -ignore  ::vc::cvs::ws::wsignore
+    fossil::configure -ignore  ::vc::cvs::ws::isadmin
 }
 
 # -----------------------------------------------------------------------------
 # API
 
 # Configuration
 #
 #	vc::fossil::import::cvs::configure key value - Set configuration
 #
-#	Legal keys:	-nosign		<bool>, default false
-#			-breakat	<int>,  default :none:
-#			-saveto		<path>, default :none:
+#       Legal keys:     -nosign  <bool>, default false
+#                       -breakat <int>,  default :none:
+#                       -saveto  <path>, default :none:
+#                       -limit   <path>, default :none:
 #
 # Functionality
 #
 #	vc::fossil::import::cvs::run src dst         - Perform an import.
 
@@ -45,10 +46,11 @@
     # The options are simply passed through to the fossil importer
     # backend.
     switch -exact -- $key {
 	-breakat { fossil::configure -breakat $value }
 	-nosign  { fossil::configure -nosign  $value }
+	-project { cvs::configure    -project $value }
 	-saveto  { fossil::configure -saveto  $value }
 	default {
 	    return -code error "Unknown switch $key, expected one of \
                                    -breakat, -nosign, or -saveto"
 	}
@@ -58,30 +60,26 @@
 
 # Import the CVS repository found at directory 'src' into the new
 # fossil repository at 'dst'.
 
 proc ::vc::fossil::import::cvs::run {src dst} {
-    cvs::at $src  ; # Define location of CVS repository
-    cvs::scan     ; # Gather revision data from the archives
-    cvs::csets    ; # Group changes into sets
-    cvs::rtree    ; # Build revision tree (trunk only right now).
-
-    write 0 import {Begin conversion}
-    write 0 import {Setting up workspaces}
-
     #B map::set {} {}
-    cvs::workspace      ; # cd's to workspace
-    fossil::begin [pwd] ; # Uses cwd as workspace to connect to.
-    stats::setup [cvs::ntrunk] [cvs::ncsets]
-
-    cvs::foreach_cset cset [cvs::root] {
+
+    set src [file normalize $src]
+    set dst [file normalize $dst]
+
+    set ws [cvs::begin $src]
+    fossil::begin $ws
+    stats::setup [cvs::ncsets -import] [cvs::ncsets]
+
+    cvs::foreach cset {
 	Import1 $cset
     }
 
     stats::done
-    cvs::wsclear
     fossil::done $dst
+    cvs::done
 
     write 0 import Ok.
     return
 }
 
@@ -98,11 +96,11 @@
     return
 }
 
 proc ::vc::fossil::import::cvs::ImportCS {cset} {
     #B fossil::setup [map::get [cvs::parentOf $cset]]
-    lassign [cvs::wssetup   $cset] user  timestamp  message
+    lassign [cvs::checkout  $cset] user  timestamp  message
     lassign [fossil::commit $cset $user $timestamp $message] uuid ad rm ch
     write 2 import "== +${ad}-${rm}*${ch}"
     map::set $cset $uuid
     return
 }