Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Rewrote the algorithm for breaking internal dependencies to my liking. The complex part handling multiple splits has moved from the pass code to the changeset class itself, reusing the state computed for the first split. The state is a bit more complex to allow for its incremental update after a break has been done. Factored major pieces into separate procedures to keep the highlevel code readable. Added lots of official log output to help debugging in case of trouble. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
08ebab80cd39311fed41f400d3dab0c4 |
| User & Date: | aku 2007-11-10 23:44:29.000 |
Context
|
2007-11-11
| ||
| 00:08 | Started on pass 6, breaking cycles between revision based changesets. Added skeleton files. ... (check-in: 2a01d50430 user: aku tags: trunk) | |
|
2007-11-10
| ||
| 23:44 | Rewrote the algorithm for breaking internal dependencies to my liking. The complex part handling multiple splits has moved from the pass code to the changeset class itself, reusing the state computed for the first split. The state is a bit more complex to allow for its incremental update after a break has been done. Factored major pieces into separate procedures to keep the highlevel code readable. Added lots of official log output to help debugging in case of trouble. ... (check-in: 08ebab80cd user: aku tags: trunk) | |
| 20:40 | Oops. pass 5 is not complete. Missed the breaking of internal dependencies, this is done in this pass already. Extended pass _2_ and file revisions with code to save the branchchildren (possible dependencies), and pass 5 and changesets with the proper algorithm. From cvs2svn, works, do not truly like it, as it throws away and recomputes a lot of state after each split of a cset. Could update and reuse the state to perform all splits in one go. Will try that next, for now we have a working form in the code base. ... (check-in: 95af789e1f user: aku tags: trunk) | |
Changes
Changes to tools/cvs2fossil/lib/c2f_pinitcsets.tcl.
| ︙ | ︙ | |||
281 282 283 284 285 286 287 |
# it, before the symbol changesets are made. The changesets
# are inspected for internal conflicts and any such are broken
# by splitting the problematic changeset into multiple
# fragments. The results are changesets which have no internal
# dependencies, only external ones.
log write 3 initcsets {Break internal dependencies}
| | < < < < < < < < < < < < < < < < | < | < < < < < | | 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# it, before the symbol changesets are made. The changesets
# are inspected for internal conflicts and any such are broken
# by splitting the problematic changeset into multiple
# fragments. The results are changesets which have no internal
# dependencies, only external ones.
log write 3 initcsets {Break internal dependencies}
set old [llength $csets]
foreach cset $csets {
$cset breakinternaldependencies csets
}
set n [expr {[llength $csets] - $old}]
log write 4 initcsets "Created [nsp $n {additional revision changeset}]"
log write 4 initcsets Ok.
return
}
proc PersistTheChangesets {csets} {
log write 3 initcsets "Saving [nsp [llength $csets] {initial changeset}] to the persistent state"
|
| ︙ | ︙ |
Changes to tools/cvs2fossil/lib/c2f_prev.tcl.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
## in pass 5, which creates the initial set covering the repository.
# # ## ### ##### ######## ############# #####################
## Requirements
package require Tcl 8.4 ; # Required runtime.
package require snit ; # OO system.
package require vc::tools::log ; # User feedback.
package require vc::fossil::import::cvs::state ; # State storage.
# # ## ### ##### ######## ############# #####################
##
snit::type ::vc::fossil::import::cvs::project::rev {
| > > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
## in pass 5, which creates the initial set covering the repository.
# # ## ### ##### ######## ############# #####################
## Requirements
package require Tcl 8.4 ; # Required runtime.
package require snit ; # OO system.
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.
# # ## ### ##### ######## ############# #####################
##
snit::type ::vc::fossil::import::cvs::project::rev {
|
| ︙ | ︙ | |||
45 46 47 48 49 50 51 | # This method inspects the changesets for internal # dependencies. Nothing is done if there are no # such. Otherwise the changeset is split into a set of # fragments without internal dependencies, transforming the # internal dependencies into external ones. The new changesets # are added to the list of all changesets. | | | > | > | | | > < < | < < | < < < < < < < < < < < < < < < < < < < < | | | < | > > > > > > | | > | | | < | | < | | < | < < < > > > < < < < | | | > | < | | < | > > > | > | > | < < < > | | | | < < < | < < | | | > > | > > | | < < > | > | | > | < < < < < | < | > > > | > | > | > | < > > > | > > | | < | > < < > | | < < | > | < < < > > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# This method inspects the changesets for internal
# dependencies. Nothing is done if there are no
# such. Otherwise the changeset is split into a set of
# fragments without internal dependencies, transforming the
# internal dependencies into external ones. The new changesets
# are added to the list of all changesets.
# We perform all necessary splits in one go, instead of only
# one. The previous algorithm, adapted from cvs2svn, computed
# a lot of state which was thrown away and then computed again
# for each of the fragments. It should be easier to update and
# reuse that state.
# The code checks only sucessor dependencies, as this
# automatically covers the predecessor dependencies as well (A
# successor dependency a -> b is also a predecessor dependency
# b -> a).
# Array of dependencies (parent -> child). This is pulled from
# the state, and limited to successors within the changeset.
array set dependencies {}
PullInternalDependencies dependencies $myrevisions
if {![array size dependencies]} {return 0} ; # Nothing to break.
log write 6 csets ...<$myid>.......................................................
# We have internal dependencies to break. We now iterate over
# all positions in the list (which is chronological, at least
# as far as the timestamps are correct and unique) and
# determine the best position for the break, by trying to
# break as many dependencies as possible in one go. When a
# break was found this is redone for the fragments coming and
# after, after upding the crossing information.
# Data structures:
# Map: POS revision id -> position in list.
# CROSS position in list -> number of dependencies crossing it
# DEPC dependency -> positions it crosses
# List: RANGE Of the positions itself.
# A dependency is a single-element map parent -> child
InitializeBreakState $myrevisions
set fragments {}
set pending [list $range]
set at 0
array set breaks {}
while {$at < [llength $pending]} {
set current [lindex $pending $at]
log write 6 csets ". . .. ... ..... ........ ............."
log write 6 csets "Scheduled [join [PRs [lrange $pending $at end]] { }]"
log write 6 csets "Considering [PR $current] \[$at/[llength $pending]\]"
set best [FindBestBreak $current]
if {$best < 0} {
# The inspected range has no internal
# dependencies. This is a complete fragment.
lappend fragments $current
log write 6 csets "No breaks, final"
} else {
# Split the range and schedule the resulting fragments
# for further inspection. Remember the number of
# dependencies cut before we remove them from
# consideration, for documentation later.
set breaks($best) $cross($best)
log write 6 csets "Best break @ $best, cuts [nsp $cross($best) dependency dependencies]"
# Note: The value of best is an abolute location in
# myrevisions. Use the start of current to make it an
# index absolute to current.
set brel [expr {$best - [lindex $current 0]}]
set bnext $brel ; incr bnext
set fragbefore [lrange $current 0 $brel]
set fragafter [lrange $current $bnext end]
log write 6 csets "New pieces [PR $fragbefore] [PR $fragafter]"
if {![llength $fragbefore]} {
trouble internal "Tried to split off a zero-length fragment at the beginning"
}
if {![llength $fragafter]} {
trouble internal "Tried to split off a zero-length fragment at the end"
}
lappend pending $fragbefore $fragafter
CutAt $best
}
incr at
}
log write 6 csets ". . .. ... ..... ........ ............."
# 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.
set fragments [lsort -index 0 -integer $fragments]
#puts \t.[join [PRs $fragments] .\n\t.].
Border [lindex $fragments 0] firsts firste
if {$firsts != 0} {
trouble internal "Bad fragment start @ $firsts, gap, or before beginning of the range"
}
set laste $firste
foreach fragment [lrange $fragments 1 end] {
Border $fragment s e
if {$laste != ($s - 1)} {
trouble internal "Bad fragment border <$laste | $s>, gap or overlap"
}
set new [$type %AUTO% $myproject $mytype $mysrcid [lrange $myrevisions $s $e]]
lappend csets $new
log write 4 csets "Breaking <$myid> @ $laste, new <[$new id]>, cutting $breaks($laste)"
set laste $e
}
if {$laste != ([llength $myrevisions]-1)} {
trouble internal "Bad fragment end @ $laste, gap, or beyond end of the range"
}
# Put the first fragment into the current changeset.
set myrevisions [lrange $myrevisions 0 $firste]
return 1
}
method persist {} {
set tid $mycstype($mytype)
set pid [$myproject id]
|
| ︙ | ︙ | |||
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
typemethod getcstypes {} {
foreach {tid name} [state run {
SELECT tid, name FROM cstype;
}] { set mycstype($name) $tid }
return
}
# # ## ### ##### ######## #############
## 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::project {
namespace export rev
namespace eval rev {
namespace import ::vc::fossil::import::cvs::state
namespace import ::vc::tools::log
log register csets
}
}
# # ## ### ##### ######## ############# #####################
## Ready
package provide vc::fossil::import::cvs::project::rev 1.0
return
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
typemethod getcstypes {} {
foreach {tid name} [state run {
SELECT tid, name FROM cstype;
}] { set mycstype($name) $tid }
return
}
proc PullInternalDependencies {dv revisions} {
upvar 1 $dv dependencies
set theset ('[join $revisions {','}]')
foreach {rid child} [state run "
-- Primary children
SELECT R.rid, R.child
FROM revision R
WHERE R.rid IN $theset
AND R.child IS NOT NULL
AND R.child IN $theset
UNION
-- Transition NTDB to trunk
SELECT R.rid, R.dbchild
FROM revision R
WHERE R.rid IN $theset
AND R.dbchild IS NOT NULL
AND R.dbchild IN $theset
UNION
-- Secondary (branch) children
SELECT R.rid, B.brid
FROM revision R, revisionbranchchildren B
WHERE R.rid IN $theset
AND R.rid = B.rid
AND B.brid IN $theset
"] {
# Consider moving this to the integrity module.
if {$rid == $child} {
trouble internal "Revision $rid depends on itself."
}
set dependencies($rid) $child
}
}
proc InitializeBreakState {revisions} {
upvar 1 pos pos cross cross range range depc depc delta delta \
dependencies dependencies
# First we create a map of positions to make it easier to
# determine whether a dependency crosses a particular index.
array set pos {}
array set cross {}
array set depc {}
set range {}
set n 0
foreach rev $revisions {
lappend range $n
set pos($rev) $n
set cross($n) 0
incr n
}
# Secondly we count the crossings per position, by iterating
# over the recorded internal dependencies.
# Note: If the timestamps are badly out of order it is
# possible to have a backward successor dependency,
# i.e. with start > end. We may have to swap the indices
# to ensure that the following loop runs correctly.
#
# Note 2: start == end is not possible. It indicates a
# self-dependency due to the uniqueness of positions,
# and that is something we have ruled out already, see
# PullInternalDependencies.
foreach {rid child} [array get dependencies] {
set dkey [list $rid $child]
set start $pos($rid)
set end $pos($child)
set crosses {}
if {$start > $end} {
while {$end < $start} {
lappend crosses $end
incr cross($end)
incr end
}
} else {
while {$start < $end} {
lappend crosses $start
incr cross($start)
incr start
}
}
set depc($dkey) $crosses
}
InitializeDeltas $revisions
return
}
proc InitializeDeltas {revisions} {
upvar 1 delta delta
# Pull the timestamps for all revisions in the changesets and
# compute their deltas for use by the break finder.
array set delta {}
array set stamp {}
set theset ('[join $revisions {','}]')
foreach {rid time} [state run "
SELECT R.rid, R.date
FROM revision R
WHERE R.rid IN $theset
"] {
set stamp($rid) $time
}
set n 0
foreach rid [lrange $revisions 0 end-1] rnext [lrange $revisions 1 end] {
set delta($n) [expr {$stamp($rnext) - $stamp($rid)}]
incr n
}
return
}
proc FindBestBreak {range} {
upvar 1 cross cross delta delta
# Determine the best break location in the given range of
# positions. First we look for the locations with the maximal
# number of crossings. If there are several we look for the
# shortest time interval among them. If we still have multiple
# possibilities after that we select the earliest location
# among these.
# Note: If the maximal number of crossings is 0 then the range
# has no internal dependencies, and no break location at
# all. This possibility is signaled via result -1.
# Note: A range of length 1 or less cannot have internal
# dependencies, as that needs at least two revisions in
# the range.
if {[llength $range] < 2} { return -1 }
set max -1
set best {}
foreach location $range {
set crossings $cross($location)
if {$crossings > $max} {
set max $crossings
set best [list $location]
continue
} elseif {$crossings == $max} {
lappend best $location
}
}
if {$max == 0} { return -1 }
if {[llength $best] == 1} { return [lindex $best 0] }
set locations $best
set best {}
set min -1
foreach location $locations {
set interval $delta($location)
if {($min < 0) || ($interval < $min)} {
set min $interval
set best [list $location]
} elseif {$interval == $min} {
lappend best $location
}
}
if {[llength $best] == 1} { return [lindex $best 0] }
return [lindex [lsort -integer -increasing $best] 0]
}
proc CutAt {location} {
upvar 1 cross cross depc depc
# It was decided to split the changeset at the given
# location. This cuts a number of dependencies. Here we update
# the cross information so that the break finder has accurate
# data when we look at the generated fragments.
set six [log visible? 6]
foreach {dep range} [array get depc] {
# Check all dependencies still known, take their range and
# see if the break location falls within.
Border $range s e
if {$location < $s} continue ; # break before range, ignore
if {$location > $e} continue ; # break after range, ignore.
# This dependency crosses the break location. We remove it
# from the crossings counters, and then also from the set
# of known dependencies, as we are done with it.
foreach loc $depc($dep) { incr cross($loc) -1 }
unset depc($dep)
if {!$six} continue
struct::list assign $dep parent child
log write 6 csets "Broke dependency [PD $parent] --> [PD $child]"
}
return
}
# Print identifying data for a revision (project, file, dotted rev
# number), for high verbosity log output.
proc PD {id} {
foreach {p f r} [state run {
SELECT P.name , F.name, R.rev
FROM revision R, file F, project P
WHERE R.rid = $id
AND R.fid = F.fid
AND F.pid = P.pid
}] break
return "'$p : $f/$r'"
}
# Printing one or more ranges, formatted, and only their border to
# keep the strings short.
proc PRs {ranges} {
return [struct::list map $ranges [myproc PR]]
}
proc PR {range} {
Border $range s e
return <${s}...${e}>
}
proc Border {range sv ev} {
upvar 1 $sv s $ev e
set s [lindex $range 0]
set e [lindex $range end]
return
}
# # ## ### ##### ######## #############
## 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::project {
namespace export rev
namespace eval rev {
namespace import ::vc::fossil::import::cvs::state
namespace import ::vc::tools::misc::*
namespace import ::vc::tools::trouble
namespace import ::vc::tools::log
log register csets
}
}
# # ## ### ##### ######## ############# #####################
## Ready
package provide vc::fossil::import::cvs::project::rev 1.0
return
|
Changes to tools/cvs2fossil/lib/log.tcl.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# empty 'max' indicates an infinite progress display.
typemethod progress {verbosity system n max} {
if {$verbosity > $myloglevel} return
uplevel #0 [linsert $mylogcmd end progress [System $system] $n $max]
return
}
# # ## ### ##### ######## #############
# Public API, Administrative methods
# Set verbosity to the chosen 'level'. Only messages with a level
# less or equal to this one will be shown.
| > > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# empty 'max' indicates an infinite progress display.
typemethod progress {verbosity system n max} {
if {$verbosity > $myloglevel} return
uplevel #0 [linsert $mylogcmd end progress [System $system] $n $max]
return
}
typemethod visible? {verbosity} {
return [expr {$verbosity <= $myloglevel}]
}
# # ## ### ##### ######## #############
# Public API, Administrative methods
# Set verbosity to the chosen 'level'. Only messages with a level
# less or equal to this one will be shown.
|
| ︙ | ︙ |