Check-in [b42cff97e3]
Not logged in
Overview

SHA1 Hash:b42cff97e319c2087208f98065f6c809ed560da4
Date: 2007-11-30 03:57:19
User: aku
Comment:Replaced the checks for self-referential changesets in the cycle breaker with a scheme in the changeset class doing checks when splitting a changeset, which is also called by the general changeset integrity code, after each pass. Extended log output at high verbosity levels. Thorough checking of the fragments a changeset is to be split into.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified tools/cvs2fossil/lib/c2f_cyclebreaker.tcl from [df041b5731] to [10b6b95ceb].

@@ -197,31 +197,10 @@
 	    foreach succ [$cset successors] {
 		# Changesets may have dependencies outside of the
 		# chosen set. These are ignored
 		if {![$dg node exists $succ]} continue
 		$dg arc insert $cset $succ
-
-		# Check for changesets referencing themselves. Such a
-		# loop shows that the changeset in question has
-		# internal dependencies. Something which is supposed
-		# to be not possible, as pass 5 (InitCsets) takes care
-		# to transform internal into external dependencies by
-		# breaking the relevant changesets apart. So having
-		# one indicates big trouble in pass 5. We report them
-		# and dump internal structures to make it easier to
-		# trace the links causing the problem.
-		if {$succ eq $cset} {
-		    log write 2 cyclebreaker "LOOP changeset [$cset str] __________________"
-		    array set nmap [$cset nextmap]
-		    foreach r [lsort -dict [array names nmap]] {
-			foreach succrev $nmap($r) {
-			    log write 2 cyclebreaker \
-				"LOOP * <$r> --> <$succrev> --> cs [[project::rev ofitem $succrev] str]"
-			}
-		    }
-		    trouble fatal "Self-referencing changeset [$cset str]"
-		}
 	    }
 	}
 
 	if {$log} {
 	    log write 3 cyclebreaker "Has [nsp [llength [$dg arcs]] dependency dependencies]"
@@ -232,13 +211,10 @@
 
 	if {$log} { Mark $dg -start }
 	MarkWatch $dg
 	PreHook   $dg
 	MarkWatch $dg
-
-	# This kills the application if loops (see above) were found.
-	trouble abort?
 	return  $dg
     }
 
     # Instead of searching the whole graph for the degree-0 nodes in
     # each iteration we compute the list once to start, and then only
@@ -440,21 +416,10 @@
 	    foreach succ [$cset successors] {
 		# The new changesets may have dependencies outside of
 		# the chosen set. These are ignored
 		if {![$dg node exists $succ]} continue
 		$dg arc insert $cset $succ
-		if {$succ eq $cset} {
-		    log write 2 cyclebreaker "LOOP changeset [$cset str] __________________"
-		    array set nmap [$cset nextmap]
-		    foreach r [lsort -dict [array names nmap]] {
-			foreach succrev $nmap($r) {
-			    log write 2 cyclebreaker \
-				"LOOP * <$r> --> <$succrev> --> cs [[project::rev ofitem $succrev] str]"
-			}
-		    }
-		    trouble internal "Self-referencing changeset [$cset str]"
-		}
 	    }
 	}
 	foreach cset $pre {
 	    foreach succ [$cset successors] {
 		# Note that the arc may already exist in the graph. If

Modified tools/cvs2fossil/lib/c2f_integrity.tcl from [6d1e02083a] to [ea08d30250].

@@ -51,17 +51,18 @@
 	set n 0
 	AllButMeta
 	return
     }
 
-    typemethod changesets {} {
+    typemethod changesets {csets} {
 	log write 4 integrity {Check database consistency}
 
 	set n 0
 	RevisionChangesets
 	TagChangesets
 	BranchChangesets
+	Selfreferentiality $csets
 	return
     }
 
     # # ## ### ##### ######## #############
     ## Internal methods
@@ -732,10 +733,21 @@
 				       GROUP BY V.cid) AS VV
 				 WHERE VV.cid = UU.cid
 				 AND   UU.fcount < VV.rcount)
 		AND    T.tid = C.type
 	    }
+	return
+    }
+
+    proc Selfreferentiality {csets} {
+	log write 4 integrity {Checking changesets for self-references}
+
+	foreach cset $csets {
+	    if {[$cset selfreferential]} {
+		trouble fatal "[$cset str] depends on itself"
+	    }
+	}
 	return
     }
 
     proc ___UnusedChangesetChecks___ {} {
 	# This code performs a number of paranoid checks of the

Modified tools/cvs2fossil/lib/c2f_pbreakacycle.tcl from [da5cab23e3] to [2c3ba3a1ce].

@@ -80,11 +80,11 @@
 	    LoadCommitOrder
 	    cyclebreaker run break-all [myproc Changesets]
 	}
 
 	repository printcsetstatistics
-	integrity changesets
+	integrity changesets [project::rev all]
 	return
     }
 
     typemethod discard {} {
 	# Pass manager interface. Executed for all passes after the

Modified tools/cvs2fossil/lib/c2f_pbreakrcycle.tcl from [4ea6d5327b] to [0b1951c1de].

@@ -69,11 +69,11 @@
 	state transaction {
 	    cyclebreaker run break-rev [myproc Changesets]
 	}
 
 	repository printcsetstatistics
-	integrity changesets
+	integrity changesets [project::rev all]
 	return
     }
 
     typemethod discard {} {
 	# Pass manager interface. Executed for all passes after the

Modified tools/cvs2fossil/lib/c2f_pbreakscycle.tcl from [aa112553ed] to [427766d2fb].

@@ -68,11 +68,11 @@
 	state transaction {
 	    cyclebreaker run break-sym [myproc Changesets]
 	}
 
 	repository printcsetstatistics
-	integrity changesets
+	integrity changesets [project::rev all]
 	return
     }
 
     typemethod discard {} {
 	# Pass manager interface. Executed for all passes after the

Modified tools/cvs2fossil/lib/c2f_pinitcsets.tcl from [b1c3a78eda] to [39a67db2c5].

@@ -142,11 +142,11 @@
 	    CreateSymbolChangesets    ; # Create csets for tags and branches.
 	    PersistTheChangesets
 	}
 
 	repository printcsetstatistics
-	integrity changesets
+	integrity changesets [project::rev all]
 	return
     }
 
     typemethod discard {} {
 	# Pass manager interface. Executed for all passes after the

Modified tools/cvs2fossil/lib/c2f_prev.tcl from [1c3ee2d518] to [6ad5e03750].

@@ -16,10 +16,11 @@
 # # ## ### ##### ######## ############# #####################
 ## Requirements
 
 package require Tcl 8.4                               ; # Required runtime.
 package require snit                                  ; # OO system.
+package require struct::set                           ; # Set operations.
 package require vc::tools::misc                       ; # Text formatting
 package require vc::tools::trouble                    ; # Error reporting.
 package require vc::tools::log                        ; # User feedback.
 package require vc::fossil::import::cvs::state        ; # State storage.
 package require vc::fossil::import::cvs::integrity    ; # State integrity checks.
@@ -36,11 +37,13 @@
 	    set myid $theid
 	} else {
 	    set myid [incr mycounter]
 	}
 
-	integrity assert {[info exists mycstype($cstype)]} {Bad changeset type '$cstype'.}
+	integrity assert {
+	    [info exists mycstype($cstype)]
+	} {Bad changeset type '$cstype'.}
 
 	set myproject   $project
 	set mytype      $cstype
 	set mytypeobj   ::vc::fossil::import::cvs::project::rev::${cstype}
 	set mysrcid	$srcid
@@ -53,10 +56,11 @@
 	set     myidmap($myid) $self
 	foreach iid $items {
 	    set key [list $cstype $iid]
 	    set myitemmap($key) $self
 	    lappend mytitems $key
+	    log write 8 csets {MAP+ item <$key> $self = [$self str]}
 	}
 	return
     }
 
     method str {} {
@@ -243,10 +247,11 @@
 	# thus never more than one reference in the list.
 
 	foreach iid $myitems {
 	    set key [list $mytype $iid]
 	    unset myitemmap($key)
+	    log write 8 csets {MAP- item <$key> $self = [$self str]}
 	}
 
 	# Create changesets for the fragments, reusing the current one
 	# for the first fragment. We sort them in order to allow
 	# checking for gaps and nice messages.
@@ -283,10 +288,11 @@
 
 	set myitems [lrange $myitems 0 $firste]
 	foreach iid $myitems {
 	    set key [list $mytype $iid]
 	    set myitemmap($key) $self
+	    log write 8 csets {MAP+ item <$key> $self = [$self str]}
 	}
 
 	return 1
     }
 
@@ -313,23 +319,56 @@
     }
 
     method timerange {} { return [$mytypeobj timerange $myitems] }
 
     method drop {} {
+	log write 8 csets {Dropping $self = [$self str]}
+
 	state transaction {
 	    state run {
 		DELETE FROM changeset WHERE cid = $myid;
 		DELETE FROM csitem    WHERE cid = $myid;
 	    }
 	}
 	foreach iid $myitems {
 	    set key [list $mytype $iid]
 	    unset myitemmap($key)
+	    log write 8 csets {MAP- item <$key> $self = [$self str]}
 	}
 	set pos          [lsearch -exact $mychangesets $self]
 	set mychangesets [lreplace $mychangesets $pos $pos]
 	return
+    }
+
+    method selfreferential {} {
+	log write 9 csets {Checking [$self str] /[llength $myitems]}
+
+	if {![struct::set contains [$self successors] $self]} {
+	    return 0
+	}
+	if {[log verbosity?] < 8} { return 1 }
+
+	# Print the detailed successor structure of the self-
+	# referential changeset, if the verbosity of the log is dialed
+	# high enough.
+
+	log write 8 csets [set hdr {Self-referential changeset [$self str] __________________}]
+	array set nmap [$self nextmap]
+	foreach item [lsort -dict [array names nmap]] {
+	    foreach succitem $nmap($item) {
+		set succcs $myitemmap($succitem)
+		set hint [expr {($succcs eq $self)
+				? "LOOP"
+				: "    "}]
+		set i   "<$item [$type itemstr $item]>"
+		set s   "<$succitem [$type itemstr $succitem]>"
+		set scs [$succcs str]
+		log write 8 csets {$hint * $i --> $s --> cs $scs}
+	    }
+	}
+	log write 8 csets [regsub -all {[^ 	]} $hdr {_}]
+	return 1
     }
 
     typemethod split {cset args} {
 	# As part of the creation of the new changesets specified in
 	# ARGS as sets of items, all subsets of CSET's item set, CSET
@@ -338,25 +377,63 @@
 	#
 	# Note: The item lists found in args are tagged items. They
 	# have to have the same type as the changeset, being subsets
 	# of its items. This is checked in Untag1.
 
+	# Constraints: No fragment must be empty. All fragments have
+	# to be subsets of the cset. The union has to cover the
+	# original. All pairwise intersections have to be empty.
+
+	log write 8 csets {OLD: [lsort [$cset items]]}
+
+	set cover {}
+	foreach fragmentitems $args {
+	    log write 8 csets {NEW: [lsort $fragmentitems]}
+
+	    integrity assert {
+		![struct::set empty $fragmentitems]
+	    } {changeset fragment is empty}
+	    integrity assert {
+		[struct::set subsetof $fragmentitems [$cset items]]
+	    } {changeset fragment is not a subset}
+	    struct::set add cover $fragmentitems
+	}
+	integrity assert {
+	    [struct::set equal $cover [$cset items]]
+	 } {The fragments do not cover the original changeset}
+	set i 1
+	foreach fia $args {
+	    foreach fib [lrange $args $i end] {
+		integrity assert {
+		    [struct::set empty [struct::set intersect $fia $fib]]
+		} {The fragments <$fia> and <$fib> overlap}
+	    }
+	    incr i
+	}
+
+	# All checks pass, actually perform the split.
+
 	struct::list assign [$cset data] project cstype cssrc
 
 	$cset drop
 	$cset destroy
 
 	set newcsets {}
 	foreach fragmentitems $args {
-	    integrity assert {
-		[llength $fragmentitems]
-	    } {Attempted to create an empty changeset, i.e. without items}
-	    lappend newcsets [$type %AUTO% $project $cstype $cssrc \
-				  [Untag $fragmentitems $cstype]]
-	}
-
-	foreach c $newcsets { $c persist }
+	    log write 8 csets {MAKE: [lsort $fragmentitems]}
+
+	    set fragment [$type %AUTO% $project $cstype $cssrc \
+			      [Untag $fragmentitems $cstype]]
+	    lappend newcsets $fragment
+	    $fragment persist
+
+	    if {[$fragment selfreferential]} {
+		trouble fatal "[$fragment str] depends on itself"
+	    }
+	}
+
+	trouble abort?
 	return $newcsets
     }
 
     typemethod strlist {changesets} {
 	return [join [struct::list map $changesets [myproc ID]]]
@@ -370,10 +447,15 @@
 
     proc Untag1 {cstype theitem} {
 	struct::list assign $theitem t i
 	integrity assert {$cstype eq $t} {Item $i's type is '$t', expected '$cstype'}
 	return $i
+    }
+
+    typemethod itemstr {item} {
+	struct::list assign $item itype iid
+	return [$itype str $iid]
     }
 
     # # ## ### ##### ######## #############
     ## State
 
@@ -674,10 +756,21 @@
     typemethod byrevision {} { return 1 }
     typemethod bysymbol   {} { return 0 }
     typemethod istag      {} { return 0 }
     typemethod isbranch   {} { return 0 }
 
+    typemethod str {revision} {
+	struct::list assign [state run {
+	    SELECT R.rev, F.name, P.name
+	    FROM   revision R, file F, project P
+	    WHERE  R.rid = $revision
+	    AND    F.fid = R.fid
+	    AND    P.pid = F.pid
+	}] revnr fname pname
+	return "$pname/${revnr}::$fname"
+    }
+
     # result = list (mintime, maxtime)
     typemethod timerange {items} {
 	set theset ('[join $items {','}]')
 	return [state run "
 	    SELECT MIN(R.date), MAX(R.date)
@@ -918,10 +1011,22 @@
     typemethod byrevision {} { return 0 }
     typemethod bysymbol   {} { return 1 }
     typemethod istag      {} { return 1 }
     typemethod isbranch   {} { return 0 }
 
+    typemethod str {tag} {
+	struct::list assign [state run {
+	    SELECT S.name, F.name, P.name
+	    FROM   tag T, symbol S, file F, project P
+	    WHERE  T.tid = $tag
+	    AND    F.fid = T.fid
+	    AND    P.pid = F.pid
+	    AND    S.sid = T.sid
+	}] sname fname pname
+	return "$pname/T'${sname}'::$fname"
+    }
+
     # result = list (mintime, maxtime)
     typemethod timerange {tags} {
 	# The range is defined as the range of the revisions the tags
 	# are attached to.
 
@@ -985,10 +1090,22 @@
 snit::type ::vc::fossil::import::cvs::project::rev::sym::branch {
     typemethod byrevision {} { return 0 }
     typemethod bysymbol   {} { return 1 }
     typemethod istag      {} { return 0 }
     typemethod isbranch   {} { return 1 }
+
+    typemethod str {branch} {
+	struct::list assign [state run {
+	    SELECT S.name, F.name, P.name
+	    FROM   branch B, symbol S, file F, project P
+	    WHERE  B.bid = $branch
+	    AND    F.fid = B.fid
+	    AND    P.pid = F.pid
+	    AND    S.sid = B.sid
+	}] sname fname pname
+	return "$pname/B'${sname}'::$fname"
+    }
 
     # result = list (mintime, maxtime)
     typemethod timerange {branches} {
 	# The range of a branch is defined as the range of the
 	# revisions the branches are spawned by. NOTE however that the