1## -*- tcl -*- 2# # ## ### ##### ######## ############# ##################### 3## Copyright (c) 2007-2008 Andreas Kupries. 4# 5# This software is licensed as described in the file LICENSE, which 6# you should have received as part of this distribution. 7# 8# This software consists of voluntary contributions made by many 9# individuals. For exact contribution history, see the revision 10# history and logs, available at http://fossil-scm.hwaci.com/fossil 11# # ## ### ##### ######## ############# ##################### 12 13## Revisions per project, aka Changesets. These objects are first used 14## in pass 5, which creates the initial set covering the repository. 15 16# # ## ### ##### ######## ############# ##################### 17## Requirements 18 19package require Tcl 8.4 ; # Required runtime. 20package require snit ; # OO system. 21package require struct::set ; # Set operations. 22package require vc::tools::misc ; # Text formatting 23package require vc::tools::trouble ; # Error reporting. 24package require vc::tools::log ; # User feedback. 25package require vc::fossil::import::cvs::state ; # State storage. 26package require vc::fossil::import::cvs::integrity ; # State integrity checks. 27 28# # ## ### ##### ######## ############# ##################### 29## 30 31snit::type ::vc::fossil::import::cvs::project::rev { 32 # # ## ### ##### ######## ############# 33 ## Public API 34 35 constructor {project cstype srcid items {theid {}}} { 36 if {$theid ne ""} { 37 set myid $theid 38 } else { 39 set myid [incr mycounter] 40 } 41 42 integrity assert { 43 [info exists mycstype($cstype)] 44 } {Bad changeset type '$cstype'.} 45 46 set myproject $project 47 set mytype $cstype 48 set mytypeobj ::vc::fossil::import::cvs::project::rev::${cstype} 49 set mysrcid $srcid 50 set myitems $items 51 set mypos {} ; # Commit location is not known yet. 52 53 foreach iid $items { lappend mytitems [list $cstype $iid] } 54 55 # Keep track of the generated changesets and of the inverse 56 # mapping from items to them. 57 lappend mychangesets $self 58 lappend mytchangesets($cstype) $self 59 set myidmap($myid) $self 60 61 MapItems $cstype $items 62 return 63 } 64 65 destructor { 66 # We may be able to get rid of this entirely, at least for 67 # (de)construction and pass InitCSets. 68 69 UnmapItems $mytype $myitems 70 unset myidmap($myid) 71 72 set pos [lsearch -exact $mychangesets $self] 73 set mychangesets [lreplace $mychangesets $pos $pos] 74 set pos [lsearch -exact $mytchangesets($mytype) $self] 75 set mytchangesets($mytype) [lreplace $mytchangesets($mytype) $pos $pos] 76 return 77 } 78 79 method str {} { 80 set str "<" 81 set detail "" 82 if {[$mytypeobj bysymbol]} { 83 set detail " '[state one { 84 SELECT S.name 85 FROM symbol S 86 WHERE S.sid = $mysrcid 87 }]'" 88 } 89 append str "$mytype ${myid}${detail}>" 90 return $str 91 } 92 93 method lod {} { 94 return [$mytypeobj cs_lod $mysrcid $myitems] 95 } 96 97 method id {} { return $myid } 98 method items {} { return $mytitems } 99 method data {} { return [list $myproject $mytype $mysrcid] } 100 101 delegate method bysymbol to mytypeobj 102 delegate method byrevision to mytypeobj 103 delegate method isbranch to mytypeobj 104 delegate method istag to mytypeobj 105 106 method setpos {p} { set mypos $p ; return } 107 method pos {} { return $mypos } 108 109 method determinesuccessors {} { 110 # Pass 6 operation. Compute project-level dependencies from 111 # the file-level data and save it back to the state. This may 112 # be called during the cycle breaker passes as well, to adjust 113 # the successor information of changesets which are the 114 # predecessors of dropped changesets. For them we have to 115 # remove their existing information first before inserting the 116 # new data. 117 state run { 118 DELETE FROM cssuccessor WHERE cid = $myid; 119 } 120 set loop 0 121 # TODO: Check other uses of cs_sucessors. 122 # TODO: Consider merging cs_sucessor's SELECT with the INSERT here. 123 foreach nid [$mytypeobj cs_successors $myitems] { 124 state run { 125 INSERT INTO cssuccessor (cid, nid) 126 VALUES ($myid,$nid) 127 } 128 if {$nid == $myid} { set loop 1 } 129 } 130 # Report after the complete structure has been saved. 131 if {$loop} { $self reportloop } 132 return 133 } 134 135 # result = list (changeset) 136 method successors {} { 137 # Use the data saved by pass 6. 138 return [struct::list map [state run { 139 SELECT S.nid 140 FROM cssuccessor S 141 WHERE S.cid = $myid 142 }] [mytypemethod of]] 143 } 144 145 # item -> list (item) 146 method nextmap {} { 147 $mytypeobj successors tmp $myitems 148 return [array get tmp] 149 } 150 151 method breakinternaldependencies {cv} { 152 upvar 1 $cv counter 153 log write 14 csets {[$self str] BID} 154 vc::tools::mem::mark 155 156 # This method inspects the changeset, looking for internal 157 # dependencies. Nothing is done if there are no such. 158 159 # Otherwise the changeset is split into a set of fragments 160 # which have no internal dependencies, transforming the 161 # internal dependencies into external ones. The new changesets 162 # generated from the fragment information are added to the 163 # list of all changesets (by the caller). 164 165 # The code checks only successor dependencies, as this auto- 166 # matically covers the predecessor dependencies as well (Any 167 # successor dependency a -> b is also a predecessor dependency 168 # b -> a). 169 170 array set breaks {} 171 172 set fragments [BreakDirectDependencies $myitems breaks] 173 174 if {![llength $fragments]} { return {} } 175 176 return [$self CreateFromFragments $fragments counter breaks] 177 } 178 179 method persist {} { 180 set tid $mycstype($mytype) 181 set pid [$myproject id] 182 set pos 0 183 184 state transaction { 185 state run { 186 INSERT INTO changeset (cid, pid, type, src) 187 VALUES ($myid, $pid, $tid, $mysrcid); 188 } 189 190 foreach iid $myitems { 191 state run { 192 INSERT INTO csitem (cid, pos, iid) 193 VALUES ($myid, $pos, $iid); 194 } 195 incr pos 196 } 197 } 198 return 199 } 200 201 method timerange {} { return [$mytypeobj timerange $myitems] } 202 203 method limits {} { 204 struct::list assign [$mytypeobj limits $myitems] maxp mins 205 return [list [TagItemDict $maxp $mytype] [TagItemDict $mins $mytype]] 206 } 207 208 method drop {} { 209 log write 8 csets {Dropping $self = [$self str]} 210 211 state transaction { 212 state run { 213 DELETE FROM changeset WHERE cid = $myid; 214 DELETE FROM csitem WHERE cid = $myid; 215 DELETE FROM cssuccessor WHERE cid = $myid; 216 } 217 } 218 219 # Return the list of predecessors so that they can be adjusted. 220 return [struct::list map [state run { 221 SELECT cid 222 FROM cssuccessor 223 WHERE nid = $myid 224 }] [mytypemethod of]] 225 } 226 227 method reportloop {{kill 1}} { 228 # We print the items which are producing the loop, and how. 229 230 set hdr "Self-referential changeset [$self str] __________________" 231 set ftr [regsub -all {[^ ]} $hdr {_}] 232 233 log write 0 csets $hdr 234 foreach {item nextitem} [$mytypeobj loops $myitems] { 235 # Create tagged items from the id and our type. 236 set item [list $mytype $item] 237 set nextitem [list $mytype $nextitem] 238 # Printable labels. 239 set i "<[$type itemstr $item]>" 240 set n "<[$type itemstr $nextitem]>" 241 set ncs $myitemmap($nextitem) 242 # Print 243 log write 0 csets {* $i --> $n --> cs [$ncs str]} 244 } 245 log write 0 csets $ftr 246 247 if {!$kill} return 248 trouble internal "[$self str] depends on itself" 249 return 250 } 251 252 method pushto {repository date rstate} { 253 # Generate and import the manifest for this changeset. 254 # 255 # Data needed: 256 # - Commit message (-- mysrcid -> repository meta) 257 # - User doing the commit (s.a.) 258 # 259 # - Timestamp of when committed (command argument) 260 # 261 # - The parent changeset, if any. If there is no parent fossil 262 # will use the empty base revision as parent. 263 # 264 # - List of the file revisions in the changeset. 265 266 # We derive the lod information directly from the revisions of 267 # the changeset, as the branch part of the meta data (s.a.) is 268 # outdated since pass FilterSymbols. See the method 'run' in 269 # file "c2f_pfiltersym.tcl" for more commentary on this. 270 271 set lodname [$self lod] 272 273 log write 2 csets {Importing changeset [$self str] on $lodname} 274 275 if {[$mytypeobj istag]} { 276 # Handle tags. They appear immediately after the revision 277 # they are attached to (*). We can assume that the 278 # workspace for the relevant line of development 279 # exists. We retrieve it, then the uuid of the last 280 # revision entered into it, then tag this revision. 281 282 # (*) Immediately in terms of the relevant line of 283 # development. Revisions on other lines may come in 284 # between, but they do not matter to that. 285 286 set lws [Getworkspace $rstate $lodname $myproject 0] 287 set uuid [lindex [$lws getid] 1] 288 289 $repository tag $uuid [state one { 290 SELECT S.name 291 FROM symbol S 292 WHERE S.sid = $mysrcid 293 }] 294 295 } elseif {[$mytypeobj isbranch]} { 296 297 # Handle branches. They appear immediately after the 298 # revision they are spawned from (*). We can assume that 299 # the workspace for the relevant line of development 300 # exists. 301 302 # We retrieve it, then the uuid of the last revision 303 # entered into it. That revision is tagged as the root of 304 # the branch (**). A new workspace for the branch is 305 # created as well, for the future revisions of the new 306 # line of development. 307 308 # An exception is made of the non-trunk default branch, 309 # aka vendor branch. This lod has to have a workspace not 310 # inherited from anything else. It has no root either, so 311 # tagging is out as well. 312 313 # (*) Immediately in terms of the relevant line of 314 # development. Revisions on other lines may come in 315 # between, but they do not matter to that. 316 317 # (**) Tagging the parent revision of the branch as its 318 # root is done to let us know about the existence of 319 # the branch even if it has no revisions committed to 320 # it, and thus no regular branch tag anywhere else. 321 # The name of the tag is the name for the lod, with 322 # the suffix '-root' appended to it. 323 324 # LOD is self symbol of branch, not parent 325 set lodname [state one { 326 SELECT S.name 327 FROM symbol S 328 WHERE S.sid = $mysrcid 329 }] 330 331 if {![$rstate has :trunk:]} { 332 # No trunk implies default branch. Just create the 333 # proper workspace. 334 Getworkspace $rstate $lodname $myproject 1 335 } else { 336 # Non-default branch. Need workspace, and tag parent 337 # revision. 338 339 set lws [Getworkspace $rstate $lodname $myproject 0] 340 set uuid [lindex [$lws getid] 1] 341 342 $repository tag $uuid ${lodname}-root 343 } 344 } else { 345 # Revision changeset. 346 347 struct::list assign [$myproject getmeta $mysrcid] __ __ user message 348 349 # Perform the import. As part of that we determine the 350 # parent we need, and convert the list of items in the 351 # changeset into uuids and printable data. 352 353 struct::list assign [Getisdefault $myitems] \ 354 isdefault lastdefaultontrunk 355 356 log write 8 csets {LOD '$lodname'} 357 log write 8 csets { def? $isdefault} 358 log write 8 csets { last? $lastdefaultontrunk} 359 360 set lws [Getworkspace $rstate $lodname $myproject $isdefault] 361 $lws add [Getrevisioninfo $myitems] 362 363 struct::list assign \ 364 [$repository importrevision [$self str] \ 365 $user $message $date \ 366 [lindex [$lws getid] 0] [$lws get]] \ 367 rid uuid 368 369 if {[$lws ticks] == 1} { 370 # First commit on this line of development. Set our 371 # own name as a propagating tag. And if the LOD has a 372 # parent we have to prevent the propagation of that 373 # tag into this new line. 374 375 set plws [$lws parent] 376 if {$plws ne ""} { 377 $repository branchcancel $uuid [$plws name] 378 } 379 $repository branchmark $uuid [$lws name] 380 } 381 382 # Remember the imported changeset in the state, under our 383 # LOD. And if it is the last trunk changeset on the vendor 384 # branch then the revision is also the actual root of the 385 # :trunk:, so we remember it as such in the state. However 386 # if the trunk already exists then the changeset cannot be 387 # on it any more. This indicates weirdness in the setup of 388 # the vendor branch, but one we can work around. 389 390 $lws defid [list $rid $uuid] 391 if {$lastdefaultontrunk} { 392 log write 2 csets {This cset is the last on the NTDB, set the trunk workspace up} 393 394 if {[$rstate has :trunk:]} { 395 log write 2 csets {Multiple changesets declared to be the last trunk changeset on the vendor-branch} 396 } else { 397 $rstate new :trunk: [$lws name] 398 } 399 } 400 } 401 402 log write 2 csets { } 403 log write 2 csets { } 404 return 405 } 406 407 proc Getrevisioninfo {revisions} { 408 set theset ('[join $revisions {','}]') 409 set revisions {} 410 state foreachrow [subst -nocommands -nobackslashes { 411 SELECT U.uuid AS frid, 412 F.visible AS path, 413 F.name AS fname, 414 R.rev AS revnr, 415 R.op AS rop 416 FROM revision R, revuuid U, file F 417 WHERE R.rid IN $theset -- All specified revisions 418 AND U.rid = R.rid -- get fossil uuid of revision 419 AND F.fid = R.fid -- get file of revision 420 }] { 421 lappend revisions $frid $path $fname/$revnr $rop 422 } 423 return $revisions 424 } 425 426 proc Getworkspace {rstate lodname project isdefault} { 427 428 # The state object holds the workspace state of each known 429 # line-of-development (LOD), up to the last committed 430 # changeset belonging to that LOD. 431 432 # (*) Standard handling if in-LOD changesets. If the LOD of 433 # the current changeset exists in the state (= has been 434 # committed to) then this it has the workspace we are 435 # looking for. 436 437 if {[$rstate has $lodname]} { 438 return [$rstate get $lodname] 439 } 440 441 # If the LOD is however not yet known, then the current 442 # changeset can be either of 443 # (a) root of a vendor branch, 444 # (b) root of the trunk LOD, or 445 # (c) the first changeset in a new LOD which was spawned from 446 # an existing LOD. 447 448 # For both (a) and (b) we have to create a new workspace for 449 # the lod, and it doesn't inherit from anything. 450 451 # One exception for (a). If we already have a :vendor: branch 452 # then multiple symbols were used for the vendor branch by 453 # different files. In that case the 'new' branch is made an 454 # alias of the :vendor:, effectively merging the symbols 455 # together. 456 457 # Note that case (b) may never occur. See the variable 458 # 'lastdefaultontrunk' in the caller (method pushto). This 459 # flag can the generation of the workspace for the :trunk: LOD 460 # as well, making it inherit the state of the last 461 # trunk-changeset on the vendor-branch. 462 463 if {$isdefault} { 464 if {![$rstate has ":vendor:"]} { 465 # Create the vendor branch if not present already. We 466 # use the actual name for the lod, and additional make 467 # it accessible under an internal name (:vendor:) so 468 # that we can merge to it later, should it become 469 # necessary. See the other branch below. 470 $rstate new $lodname 471 $rstate dup :vendor: <-- $lodname 472 } else { 473 # Merge the new symbol to the vendor branch 474 $rstate dup $lodname <-- :vendor: 475 } 476 return [$rstate get $lodname] 477 } 478 479 if {$lodname eq ":trunk:"} { 480 return [$rstate new $lodname] 481 } 482 483 # Case (c). We find the parent LOD of our LOD and let the new 484 # workspace inherit from the parent's workspace. 485 486 set plodname [[[$project getsymbol $lodname] parent] name] 487 488 log write 8 csets {pLOD '$plodname'} 489 490 if {[$rstate has $plodname]} { 491 return [$rstate new $lodname $plodname] 492 } 493 494 foreach k [lsort [$rstate names]] { 495 log write 8 csets { $k = [[$rstate get $k] getid]} 496 } 497 498 trouble internal {Unable to determine changeset parent} 499 return 500 } 501 502 proc Getisdefault {revisions} { 503 set theset ('[join $revisions {','}]') 504 505 struct::list assign [state run [subst -nocommands -nobackslashes { 506 SELECT R.isdefault, R.dbchild 507 FROM revision R 508 WHERE R.rid IN $theset -- All specified revisions 509 LIMIT 1 510 }]] def last 511 512 # TODO/CHECK: look for changesets where isdefault/dbchild is 513 # ambigous. 514 515 return [list $def [expr {$last ne ""}]] 516 } 517 518 typemethod split {cset args} { 519 # As part of the creation of the new changesets specified in 520 # ARGS as sets of items, all subsets of CSET's item set, CSET 521 # will be dropped from all databases, in and out of memory, 522 # and then destroyed. 523 # 524 # Note: The item lists found in args are tagged items. They 525 # have to have the same type as the changeset, being subsets 526 # of its items. This is checked in Untag1. 527 528 log write 8 csets {OLD: [lsort [$cset items]]} 529 ValidateFragments $cset $args 530 531 # All checks pass, actually perform the split. 532 533 struct::list assign [$cset data] project cstype cssrc 534 535 set predecessors [$cset drop] 536 $cset destroy 537 538 set newcsets {} 539 foreach fragmentitems $args { 540 log write 8 csets {MAKE: [lsort $fragmentitems]} 541 542 set fragment [$type %AUTO% $project $cstype $cssrc \ 543 [Untag $fragmentitems $cstype]] 544 lappend newcsets $fragment 545 546 $fragment persist 547 $fragment determinesuccessors 548 } 549 550 # The predecessors have to recompute their successors, i.e. 551 # remove the dropped changeset and put one of the fragments 552 # into its place. 553 foreach p $predecessors { 554 $p determinesuccessors 555 } 556 557 return $newcsets 558 } 559 560 typemethod itemstr {item} { 561 struct::list assign $item itype iid 562 return [$itype str $iid] 563 } 564 565 typemethod strlist {changesets} { 566 return [join [struct::list map $changesets [myproc ID]]] 567 } 568 569 proc ID {cset} { $cset str } 570 571 proc Untag {taggeditems cstype} { 572 return [struct::list map $taggeditems [myproc Untag1 $cstype]] 573 } 574 575 proc Untag1 {cstype theitem} { 576 struct::list assign $theitem t i 577 integrity assert {$cstype eq $t} {Item $i's type is '$t', expected '$cstype'} 578 return $i 579 } 580 581 proc TagItemDict {itemdict cstype} { 582 set res {} 583 foreach {i v} $itemdict { lappend res [list $cstype $i] $v } 584 return $res 585 } 586 587 proc ValidateFragments {cset fragments} { 588 # Check the various integrity constraints for the fragments 589 # specifying how to split the changeset: 590 # 591 # * We must have two or more fragments, as splitting a 592 # changeset into one makes no sense. 593 # * No fragment may be empty. 594 # * All fragments have to be true subsets of the items in the 595 # changeset to split. The 'true' is implied because none are 596 # allowed to be empty, so each has to be smaller than the 597 # total. 598 # * The union of the fragments has to be the item set of the 599 # changeset. 600 # * The fragment must not overlap, i.e. their pairwise 601 # intersections have to be empty. 602 603 set cover {} 604 foreach fragmentitems $fragments { 605 log write 8 csets {NEW: [lsort $fragmentitems]} 606 607 integrity assert { 608 ![struct::set empty $fragmentitems] 609 } {changeset fragment is empty} 610 611 integrity assert { 612 [struct::set subsetof $fragmentitems [$cset items]] 613 } {changeset fragment is not a subset} 614 struct::set add cover $fragmentitems 615 } 616 617 integrity assert { 618 [struct::set equal $cover [$cset items]] 619 } {The fragments do not cover the original changeset} 620 621 set i 1 622 foreach fia $fragments { 623 foreach fib [lrange $fragments $i end] { 624 integrity assert { 625 [struct::set empty [struct::set intersect $fia $fib]] 626 } {The fragments <$fia> and <$fib> overlap} 627 } 628 incr i 629 } 630 631 return 632 } 633 634 # # ## ### ##### ######## ############# 635 ## State 636 637 variable myid {} ; # Id of the cset for the persistent 638 # state. 639 variable myproject {} ; # Reference of the project object the 640 # changeset belongs to. 641 variable mytype {} ; # What the changeset is based on 642 # (revisions, tags, or branches). 643 # Values: See mycstype. Note that we 644 # have to keep the names of the helper 645 # singletons in sync with the contents 646 # of state table 'cstype', and various 647 # other places using them hardwired. 648 variable mytypeobj {} ; # Reference to the container for the 649 # type dependent code. Derived from 650 # mytype. 651 variable mysrcid {} ; # Id of the metadata or symbol the cset 652 # is based on. 653 variable myitems {} ; # List of the file level revisions, 654 # tags, or branches in the cset, as 655 # ids. Not tagged. 656 variable mytitems {} ; # As myitems, the tagged form. 657 variable mypos {} ; # Commit position of the changeset, if 658 # known. 659 660 # # ## ### ##### ######## ############# 661 ## Internal methods 662 663 typevariable mycounter 0 ; # Id counter for csets. Last id 664 # used. 665 typevariable mycstype -array {} ; # Map cstypes (names) to persistent 666 # ids. Note that we have to keep 667 # the names in the table 'cstype' 668 # in sync with the names of the 669 # helper singletons. 670 671 typemethod inorder {projectid} { 672 # Return all changesets (object references) for the specified 673 # project, in the order given to them by the sort passes. Both 674 # the filtering by project and the sorting by time make the 675 # use of 'project::rev rev' impossible. 676 677 set res {} 678 state foreachrow { 679 SELECT C.cid AS xcid, 680 T.date AS cdate 681 FROM changeset C, cstimestamp T 682 WHERE C.pid = $projectid -- limit to changesets in project 683 AND T.cid = C.cid -- get ordering information 684 ORDER BY T.date -- sort into commit order 685 } { 686 lappend res $myidmap($xcid) $cdate 687 } 688 return $res 689 } 690 691 typemethod getcstypes {} { 692 state foreachrow { 693 SELECT tid, name FROM cstype; 694 } { set mycstype($name) $tid } 695 return 696 } 697 698 typemethod load {repository} { 699 set n 0 700 log write 2 csets {Loading the changesets} 701 state foreachrow { 702 SELECT C.cid AS id, 703 C.pid AS xpid, 704 CS.name AS cstype, 705 C.src AS srcid 706 FROM changeset C, cstype CS 707 WHERE C.type = CS.tid 708 ORDER BY C.cid 709 } { 710 log progress 2 csets $n {} 711 set r [$type %AUTO% [$repository projectof $xpid] $cstype $srcid [state run { 712 SELECT C.iid 713 FROM csitem C 714 WHERE C.cid = $id 715 ORDER BY C.pos 716 }] $id] 717 incr n 718 } 719 return 720 } 721 722 typemethod loadcounter {} { 723 # Initialize the counter from the state 724 log write 2 csets {Loading changeset counter} 725 set mycounter [state one { SELECT MAX(cid) FROM changeset }] 726 return 727 } 728 729 typemethod num {} { return $mycounter } 730 731 # # ## ### ##### ######## ############# 732 733 method CreateFromFragments {fragments cv bv} { 734 upvar 1 $cv counter $bv breaks 735 UnmapItems $mytype $myitems 736 737 # Create changesets for the fragments, reusing the current one 738 # for the first fragment. We sort them in order to allow 739 # checking for gaps and nice messages. 740 741 set newcsets {} 742 set fragments [lsort -index 0 -integer $fragments] 743 744 #puts \t.[join [PRs $fragments] .\n\t.]. 745 746 Border [lindex $fragments 0] firsts firste 747 748 integrity assert { 749 $firsts == 0 750 } {Bad fragment start @ $firsts, gap, or before beginning of the range} 751 752 set laste $firste 753 foreach fragment [lrange $fragments 1 end] { 754 Border $fragment s e 755 integrity assert { 756 $laste == ($s - 1) 757 } {Bad fragment border <$laste | $s>, gap or overlap} 758 759 set new [$type %AUTO% $myproject $mytype $mysrcid [lrange $myitems $s $e]] 760 lappend newcsets $new 761 incr counter 762 763 log write 4 csets {Breaking [$self str ] @ $laste, new [$new str], cutting $breaks($laste)} 764 765 set laste $e 766 } 767 768 integrity assert { 769 $laste == ([llength $myitems]-1) 770 } {Bad fragment end @ $laste, gap, or beyond end of the range} 771 772 # Put the first fragment into the current changeset, and 773 # update the in-memory index. We can simply (re)add the items 774 # because we cleared the previously existing information, see 775 # 'UnmapItems' above. Persistence does not matter here, none 776 # of the changesets has been saved to the persistent state 777 # yet. 778 779 set myitems [lrange $myitems 0 $firste] 780 set mytitems [lrange $mytitems 0 $firste] 781 MapItems $mytype $myitems 782 return $newcsets 783 } 784 785 # # ## ### ##### ######## ############# 786 787 proc BreakDirectDependencies {theitems bv} { 788 upvar 1 mytypeobj mytypeobj self self $bv breaks 789 790 # Array of dependencies (parent -> child). This is pulled from 791 # the state, and limited to successors within the changeset. 792 793 array set dependencies {} 794 795 $mytypeobj internalsuccessors dependencies $theitems 796 if {![array size dependencies]} { 797 return {} 798 } ; # Nothing to break. 799 800 log write 5 csets ...[$self str]....................................................... 801 vc::tools::mem::mark 802 803 return [BreakerCore $theitems dependencies breaks] 804 } 805 806 proc BreakerCore {theitems dv bv} { 807 # Break a set of revisions into fragments which have no 808 # internal dependencies. 809 810 # We perform all necessary splits in one go, instead of only 811 # one. The previous algorithm, adapted from cvs2svn, computed 812 # a lot of state which was thrown away and then computed again 813 # for each of the fragments. It should be easier to update and 814 # reuse that state. 815 816 upvar 1 $dv dependencies $bv breaks 817 818 # We have internal dependencies to break. We now iterate over 819 # all positions in the list (which is chronological, at least 820 # as far as the timestamps are correct and unique) and 821 # determine the best position for the break, by trying to 822 # break as many dependencies as possible in one go. When a 823 # break was found this is redone for the fragments coming and 824 # after, after upding the crossing information. 825 826 # Data structures: 827 # Map: POS revision id -> position in list. 828 # CROSS position in list -> number of dependencies crossing it 829 # DEPC dependency -> positions it crosses 830 # List: RANGE Of the positions itself. 831 # Map: DELTA position in list -> time delta between its revision 832 # and the next, if any. 833 # A dependency is a single-element map parent -> child 834 835 # InitializeBreakState initializes their contents after 836 # upvar'ing them from this scope. It uses the information in 837 # DEPENDENCIES to do so. 838 839 InitializeBreakState $theitems 840 841 set fragments {} 842 set new [list $range] 843 844 # Instead of one list holding both processed and pending 845 # fragments we use two, one for the framents to process, one 846 # to hold the new fragments, and the latter is copied to the 847 # former when they run out. This keeps the list of pending 848 # fragments short without sacrificing speed by shifting stuff 849 # down. We especially drop the memory of fragments broken 850 # during processing after a short time, instead of letting it 851 # consume memory. 852 853 while {[llength $new]} { 854 855 set pending $new 856 set new {} 857 set at 0 858 859 while {$at < [llength $pending]} { 860 set current [lindex $pending $at] 861 862 log write 6 csets {. . .. ... ..... ........ .............} 863 log write 6 csets {Scheduled [join [PRs [lrange $pending $at end]] { }]} 864 log write 6 csets {Considering [PR $current] \[$at/[llength $pending]\]} 865 866 set best [FindBestBreak $current] 867 868 if {$best < 0} { 869 # The inspected range has no internal 870 # dependencies. This is a complete fragment. 871 lappend fragments $current 872 873 log write 6 csets "No breaks, final" 874 } else { 875 # Split the range and schedule the resulting 876 # fragments for further inspection. Remember the 877 # number of dependencies cut before we remove them 878 # from consideration, for documentation later. 879 880 set breaks($best) $cross($best) 881 882 log write 6 csets "Best break @ $best, cutting [nsp $cross($best) dependency dependencies]" 883 884 # Note: The value of best is an abolute location 885 # in myitems. Use the start of current to make it 886 # an index absolute to current. 887 888 set brel [expr {$best - [lindex $current 0]}] 889 set bnext $brel ; incr bnext 890 set fragbefore [lrange $current 0 $brel] 891 set fragafter [lrange $current $bnext end] 892 893 log write 6 csets "New pieces [PR $fragbefore] [PR $fragafter]" 894 895 integrity assert {[llength $fragbefore]} {Found zero-length fragment at the beginning} 896 integrity assert {[llength $fragafter]} {Found zero-length fragment at the end} 897 898 lappend new $fragbefore $fragafter 899 CutAt $best 900 } 901 902 incr at 903 } 904 } 905 906 log write 6 csets ". . .. ... ..... ........ ............." 907 908 return $fragments 909 } 910 911 proc InitializeBreakState {revisions} { 912 upvar 1 pos pos cross cross range range depc depc delta delta \ 913 dependencies dependencies 914 915 # First we create a map of positions to make it easier to 916 # determine whether a dependency crosses a particular index. 917 918 log write 14 csets {IBS: #rev [llength $revisions]} 919 log write 14 csets {IBS: pos map, cross counter} 920 921 array set pos {} 922 array set cross {} 923 array set depc {} 924 set range {} 925 set n 0 926 foreach rev $revisions { 927 lappend range $n 928 set pos($rev) $n 929 set cross($n) 0 930 incr n 931 } 932 933 log write 14 csets {IBS: pos/[array size pos], cross/[array size cross]} 934 935 # Secondly we count the crossings per position, by iterating 936 # over the recorded internal dependencies. 937 938 # Note: If the timestamps are badly out of order it is 939 # possible to have a backward successor dependency, 940 # i.e. with start > end. We may have to swap the indices 941 # to ensure that the following loop runs correctly. 942 # 943 # Note 2: start == end is not possible. It indicates a 944 # self-dependency due to the uniqueness of positions, 945 # and that is something we have ruled out already, see 946 # 'rev internalsuccessors'. 947 948 log write 14 csets {IBS: cross counter filling, pos/cross map} 949 950 foreach {rid children} [array get dependencies] { 951 foreach child $children { 952 set dkey [list $rid $child] 953 set start $pos($rid) 954 set end $pos($child) 955 956 if {$start > $end} { 957 set crosses [list $end [expr {$start-1}]] 958 while {$end < $start} { 959 incr cross($end) 960 incr end 961 } 962 } else { 963 set crosses [list $start [expr {$end-1}]] 964 while {$start < $end} { 965 incr cross($start) 966 incr start 967 } 968 } 969 set depc($dkey) $crosses 970 } 971 } 972 973 log write 14 csets {IBS: pos/[array size pos], cross/[array size cross], depc/[array size depc] (for [llength $revisions])} 974 log write 14 csets {IBS: timestamps, deltas} 975 976 InitializeDeltas $revisions 977 978 log write 14 csets {IBS: delta [array size delta]} 979 return 980 } 981 982 proc InitializeDeltas {revisions} { 983 upvar 1 delta delta 984 985 # Pull the timestamps for all revisions in the changesets and 986 # compute their deltas for use by the break finder. 987 988 array set delta {} 989 array set stamp {} 990 991 set theset ('[join $revisions {','}]') 992 state foreachrow [subst -nocommands -nobackslashes { 993 SELECT R.rid AS xrid, R.date AS time 994 FROM revision R 995 WHERE R.rid IN $theset 996 }] { 997 set stamp($xrid) $time 998 } 999 1000 log write 14 csets {IBS: stamp [array size stamp]} 1001 1002 set n 0 1003 foreach rid [lrange $revisions 0 end-1] rnext [lrange $revisions 1 end] { 1004 set delta($n) [expr {$stamp($rnext) - $stamp($rid)}] 1005 incr n 1006 } 1007 return 1008 } 1009 1010 proc FindBestBreak {range} { 1011 upvar 1 cross cross delta delta 1012 1013 # Determine the best break location in the given range of 1014 # positions. First we look for the locations with the maximal 1015 # number of crossings. If there are several we look for the 1016 # shortest time interval among them. If we still have multiple 1017 # possibilities after that we select the earliest location 1018 # among these. 1019 1020 # Note: If the maximal number of crossings is 0 then the range 1021 # has no internal dependencies, and no break location at 1022 # all. This possibility is signaled via result -1. 1023 1024 # Note: A range of length 1 or less cannot have internal 1025 # dependencies, as that needs at least two revisions in 1026 # the range. 1027 1028 if {[llength $range] < 2} { return -1 } 1029 1030 set max -1 1031 set best {} 1032 1033 foreach location $range { 1034 set crossings $cross($location) 1035 if {$crossings > $max} { 1036 set max $crossings 1037 set best [list $location] 1038 continue 1039 } elseif {$crossings == $max} { 1040 lappend best $location 1041 } 1042 } 1043 1044 if {$max == 0} { return -1 } 1045 if {[llength $best] == 1} { return [lindex $best 0] } 1046 1047 set locations $best 1048 set best {} 1049 set min -1 1050 1051 foreach location $locations { 1052 set interval $delta($location) 1053 if {($min < 0) || ($interval < $min)} { 1054 set min $interval 1055 set best [list $location] 1056 } elseif {$interval == $min} { 1057 lappend best $location 1058 } 1059 } 1060 1061 if {[llength $best] == 1} { return [lindex $best 0] } 1062 1063 return [lindex [lsort -integer -increasing $best] 0] 1064 } 1065 1066 proc CutAt {location} { 1067 upvar 1 cross cross depc depc 1068 1069 # It was decided to split the changeset at the given 1070 # location. This cuts a number of dependencies. Here we update 1071 # the cross information so that the break finder has accurate 1072 # data when we look at the generated fragments. 1073 1074 set six [log visible? 6] 1075 1076 # Note: The loop below could be made faster by keeping a map 1077 # from positions to the dependencies crossing. An extension of 1078 # CROSS, i.e. list of dependencies, counter is implied. Takes 1079 # a lot more memory however, and takes time to update here 1080 # (The inner loop is not incr -1, but ldelete). 1081 1082 foreach dep [array names depc] { 1083 set range $depc($dep) 1084 # Check all dependencies still known, take their range and 1085 # see if the break location falls within. 1086 1087 Border $range s e 1088 if {$location < $s} continue ; # break before range, ignore 1089 if {$location > $e} continue ; # break after range, ignore. 1090 1091 # This dependency crosses the break location. We remove it 1092 # from the crossings counters, and then also from the set 1093 # of known dependencies, as we are done with it. 1094 1095 Border $depc($dep) ds de 1096 for {set loc $ds} {$loc <= $de} {incr loc} { 1097 incr cross($loc) -1 1098 } 1099 unset depc($dep) 1100 1101 if {!$six} continue 1102 1103 struct::list assign $dep parent child 1104 log write 5 csets "Broke dependency [PD $parent] --> [PD $child]" 1105 } 1106 1107 return 1108 } 1109 1110 # Print identifying data for a revision (project, file, dotted rev 1111 # number), for high verbosity log output. 1112 # TODO: Replace with call to itemstr (list rev $id) 1113 1114 proc PD {id} { 1115 foreach {p f r} [state run { 1116 SELECT P.name , F.name, R.rev 1117 FROM revision R, file F, project P 1118 WHERE R.rid = $id -- Find specified file revision 1119 AND F.fid = R.fid -- Get file of the revision 1120 AND P.pid = F.pid -- Get project of the file. 1121 }] break 1122 return "'$p : $f/$r'" 1123 } 1124 1125 # Printing one or more ranges, formatted, and only their border to 1126 # keep the strings short. 1127 1128 proc PRs {ranges} { 1129 return [struct::list map $ranges [myproc PR]] 1130 } 1131 1132 proc PR {range} { 1133 Border $range s e 1134 return <${s}...${e}> 1135 } 1136 1137 proc Border {range sv ev} { 1138 upvar 1 $sv s $ev e 1139 set s [lindex $range 0] 1140 set e [lindex $range end] 1141 return 1142 } 1143 1144 # # ## ### ##### ######## ############# 1145 1146 proc UnmapItems {thetype theitems} { 1147 # (*) We clear out the associated part of the myitemmap 1148 # in-memory index in preparation for new data, or as part of 1149 # object destruction. A simple unset is enough, we have no 1150 # symbol changesets at this time, and thus never more than one 1151 # reference in the list. 1152 1153 upvar 1 myitemmap myitemmap self self 1154 foreach iid $theitems { 1155 set key [list $thetype $iid] 1156 unset myitemmap($key) 1157 log write 8 csets {MAP- item <$key> $self = [$self str]} 1158 } 1159 return 1160 } 1161 1162 proc MapItems {thetype theitems} { 1163 upvar 1 myitemmap myitemmap self self 1164 1165 foreach iid $theitems { 1166 set key [list $thetype $iid] 1167 set myitemmap($key) $self 1168 log write 8 csets {MAP+ item <$key> $self = [$self str]} 1169 } 1170 return 1171 } 1172 1173 # # ## ### ##### ######## ############# 1174 1175 typevariable mychangesets {} ; # List of all known 1176 # changesets. 1177 1178 # List of all known changesets of a type. 1179 typevariable mytchangesets -array { 1180 sym::branch {} 1181 sym::tag {} 1182 rev {} 1183 } 1184 1185 typevariable myitemmap -array {} ; # Map from items (tagged) 1186 # to the list of changesets 1187 # containing it. Each item 1188 # can be used by only one 1189 # changeset. 1190 typevariable myidmap -array {} ; # Map from changeset id to 1191 # changeset. 1192 1193 typemethod all {} { return $mychangesets } 1194 typemethod of {cid} { return $myidmap($cid) } 1195 typemethod ofitem {iid} { return $myitemmap($iid) } 1196 1197 typemethod rev {} { return $mytchangesets(rev) } 1198 typemethod sym {} { return [concat \ 1199 ${mytchangesets(sym::branch)} \ 1200 ${mytchangesets(sym::tag)}] } 1201 1202 # # ## ### ##### ######## ############# 1203 ## Configuration 1204 1205 pragma -hastypeinfo no ; # no type introspection 1206 pragma -hasinfo no ; # no object introspection 1207 1208 # # ## ### ##### ######## ############# 1209} 1210 1211## 1212## NOTE: The successor and predecessor methods defined by the classes 1213## below are -- bottle necks --. Look for ways to make the SQL 1214## faster. 1215## 1216 1217# # ## ### ##### ######## ############# ##################### 1218## Helper singleton. Commands for revision changesets. 1219 1220snit::type ::vc::fossil::import::cvs::project::rev::rev { 1221 typemethod byrevision {} { return 1 } 1222 typemethod bysymbol {} { return 0 } 1223 typemethod istag {} { return 0 } 1224 typemethod isbranch {} { return 0 } 1225 1226 typemethod str {revision} { 1227 struct::list assign [state run { 1228 SELECT R.rev, F.name, P.name 1229 FROM revision R, file F, project P 1230 WHERE R.rid = $revision -- Find specified file revision 1231 AND F.fid = R.fid -- Get file of the revision 1232 AND P.pid = F.pid -- Get project of the file. 1233 }] revnr fname pname 1234 return "$pname/${revnr}::$fname" 1235 } 1236 1237 # result = list (mintime, maxtime) 1238 typemethod timerange {items} { 1239 set theset ('[join $items {','}]') 1240 return [state run [subst -nocommands -nobackslashes { 1241 SELECT MIN(R.date), MAX(R.date) 1242 FROM revision R 1243 WHERE R.rid IN $theset -- Restrict to revisions of interest 1244 }]] 1245 } 1246 1247 # var(dv) = dict (revision -> list (revision)) 1248 typemethod internalsuccessors {dv revisions} { 1249 upvar 1 $dv dependencies 1250 set theset ('[join $revisions {','}]') 1251 1252 log write 14 csets internalsuccessors 1253 1254 # See 'successors' below for the main explanation of 1255 # the various cases. This piece is special in that it 1256 # restricts the successors we look for to the same set of 1257 # revisions we start from. Sensible as we are looking for 1258 # changeset internal dependencies. 1259 1260 array set dep {} 1261 1262 state foreachrow [subst -nocommands -nobackslashes { 1263 -- (1) Primary child 1264 SELECT R.rid AS xrid, R.child AS xchild 1265 FROM revision R 1266 WHERE R.rid IN $theset -- Restrict to revisions of interest 1267 AND R.child IS NOT NULL -- Has primary child 1268 AND R.child IN $theset -- Which is also of interest 1269 UNION 1270 -- (2) Secondary (branch) children 1271 SELECT R.rid AS xrid, B.brid AS xchild 1272 FROM revision R, revisionbranchchildren B 1273 WHERE R.rid IN $theset -- Restrict to revisions of interest 1274 AND R.rid = B.rid -- Select subset of branch children 1275 AND B.brid IN $theset -- Which is also of interest 1276 UNION 1277 -- (4) Child of trunk root successor of last NTDB on trunk. 1278 SELECT R.rid AS xrid, RA.child AS xchild 1279 FROM revision R, revision RA 1280 WHERE R.rid IN $theset -- Restrict to revisions of interest 1281 AND R.isdefault -- Restrict to NTDB 1282 AND R.dbchild IS NOT NULL -- and last NTDB belonging to trunk 1283 AND RA.rid = R.dbchild -- Go directly to trunk root 1284 AND RA.child IS NOT NULL -- Has primary child. 1285 AND RA.child IN $theset -- Which is also of interest 1286 }] { 1287 # Consider moving this to the integrity module. 1288 integrity assert {$xrid != $xchild} {Revision $xrid depends on itself.} 1289 lappend dependencies($xrid) $xchild 1290 set dep($xrid,$xchild) . 1291 } 1292 1293 # The sql statements above looks only for direct dependencies 1294 # between revision in the changeset. However due to the 1295 # vagaries of meta data it is possible for two revisions of 1296 # the same file to end up in the same changeset, without a 1297 # direct dependency between them. However we know that there 1298 # has to be a an indirect dependency, be it through primary 1299 # children, branch children, or a combination thereof. 1300 1301 # We now fill in these pseudo-dependencies, if no such 1302 # dependency exists already. The direction of the dependency 1303 # is actually irrelevant for this. 1304 1305 # NOTE: This is different from cvs2svn. Our spiritual ancestor 1306 # does not use such pseudo-dependencies, however it uses a 1307 # COMMIT_THRESHOLD, a time interval commits should fall. This 1308 # will greatly reduces the risk of getting far separated 1309 # revisions of the same file into one changeset. 1310 1311 # We allow revisions to be far apart in time in the same 1312 # changeset, but in turn need the pseudo-dependencies to 1313 # handle this. 1314 1315 log write 14 csets {internal [array size dep]} 1316 log write 14 csets {collected [array size dependencies]} 1317 log write 14 csets pseudo-internalsuccessors 1318 1319 array set fids {} 1320 state foreachrow [subst -nocommands -nobackslashes { 1321 SELECT R.rid AS xrid, R.fid AS xfid 1322 FROM revision R 1323 WHERE R.rid IN $theset 1324 }] { lappend fids($xfid) $xrid } 1325 1326 set groups {} 1327 foreach {fid rids} [array get fids] { 1328 if {[llength $rids] < 2} continue 1329 foreach a $rids { 1330 foreach b $rids { 1331 if {$a == $b} continue 1332 if {[info exists dep($a,$b)]} continue 1333 if {[info exists dep($b,$a)]} continue 1334 lappend dependencies($a) $b 1335 set dep($a,$b) . 1336 set dep($b,$a) . 1337 } 1338 } 1339 set n [llength $rids] 1340 lappend groups [list $n [expr {($n*$n-$n)/2}]] 1341 } 1342 1343 log write 14 csets {pseudo [array size fids] ([lsort -index 0 -decreasing -integer $groups])} 1344 log write 14 csets {internal [array size dep]} 1345 log write 14 csets {collected [array size dependencies]} 1346 log write 14 csets complete 1347 return 1348 } 1349 1350 # result = 4-list (itemtype itemid nextitemtype nextitemid ...) 1351 typemethod loops {revisions} { 1352 # Note: Tags and branches cannot cause the loop. Their id's, 1353 # being of a fundamentally different type than the revisions 1354 # coming in cannot be in the set. 1355 1356 set theset ('[join $revisions {','}]') 1357 return [state run [subst -nocommands -nobackslashes { 1358 -- (1) Primary child 1359 SELECT R.rid, R.child 1360 FROM revision R 1361 WHERE R.rid IN $theset -- Restrict to revisions of interest 1362 AND R.child IS NOT NULL -- Has primary child 1363 AND R.child IN $theset -- Loop 1364 -- 1365 UNION 1366 -- (2) Secondary (branch) children 1367 SELECT R.rid, B.brid 1368 FROM revision R, revisionbranchchildren B 1369 WHERE R.rid IN $theset -- Restrict to revisions of interest 1370 AND R.rid = B.rid -- Select subset of branch children 1371 AND B.rid IN $theset -- Loop 1372 -- 1373 UNION 1374 -- (4) Child of trunk root successor of last NTDB on trunk. 1375 SELECT R.rid, RA.child 1376 FROM revision R, revision RA 1377 WHERE R.rid IN $theset -- Restrict to revisions of interest 1378 AND R.isdefault -- Restrict to NTDB 1379 AND R.dbchild IS NOT NULL -- and last NTDB belonging to trunk 1380 AND RA.rid = R.dbchild -- Go directly to trunk root 1381 AND RA.child IS NOT NULL -- Has primary child. 1382 AND RA.child IN $theset -- Loop 1383 }]] 1384 } 1385 1386 # var(dv) = dict (item -> list (item)), item = list (type id) 1387 typemethod successors {dv revisions} { 1388 upvar 1 $dv dependencies 1389 set theset ('[join $revisions {','}]') 1390 1391 # The following cases specify when a revision S is a successor 1392 # of a revision R. Each of the cases translates into one of 1393 # the branches of the SQL UNION coming below. 1394 # 1395 # (1) S can be a primary child of R, i.e. in the same LOD. R 1396 # references S directly. R.child = S(.rid), if it exists. 1397 # 1398 # (2) S can be a secondary, i.e. branch, child of R. Here the 1399 # link is made through the helper table 1400 # REVISIONBRANCHCHILDREN. R.rid -> RBC.rid, RBC.brid = 1401 # S(.rid) 1402 # 1403 # (3) Originally this use case defined the root of a detached 1404 # NTDB as the successor of the trunk root. This leads to a 1405 # bad tangle later on. With a detached NTDB the original 1406 # trunk root revision was removed as irrelevant, allowing 1407 # the nominal root to be later in time than the NTDB 1408 # root. Now setting this dependency will be backward in 1409 # time. REMOVED. 1410 # 1411 # (4) If R is the last of the NTDB revisions which belong to 1412 # the trunk, then the primary child of the trunk root (the 1413 # '1.2' revision) is a successor, if it exists. 1414 1415 # Note that the branches spawned from the revisions, and the 1416 # tags associated with them are successors as well. 1417 1418 state foreachrow [subst -nocommands -nobackslashes { 1419 -- (1) Primary child 1420 SELECT R.rid AS xrid, R.child AS xchild 1421 FROM revision R 1422 WHERE R.rid IN $theset -- Restrict to revisions of interest 1423 AND R.child IS NOT NULL -- Has primary child 1424 UNION 1425 -- (2) Secondary (branch) children 1426 SELECT R.rid AS xrid, B.brid AS xchild 1427 FROM revision R, revisionbranchchildren B 1428 WHERE R.rid IN $theset -- Restrict to revisions of interest 1429 AND R.rid = B.rid -- Select subset of branch children 1430 UNION 1431 -- (4) Child of trunk root successor of last NTDB on trunk. 1432 SELECT R.rid AS xrid, RA.child AS xchild 1433 FROM revision R, revision RA 1434 WHERE R.rid IN $theset -- Restrict to revisions of interest 1435 AND R.isdefault -- Restrict to NTDB 1436 AND R.dbchild IS NOT NULL -- and last NTDB belonging to trunk 1437 AND RA.rid = R.dbchild -- Go directly to trunk root 1438 AND RA.child IS NOT NULL -- Has primary child. 1439 }] { 1440 # Consider moving this to the integrity module. 1441 integrity assert {$xrid != $xchild} {Revision $xrid depends on itself.} 1442 lappend dependencies([list rev $xrid]) [list rev $xchild] 1443 } 1444 state foreachrow [subst -nocommands -nobackslashes { 1445 SELECT R.rid AS xrid, T.tid AS xchild 1446 FROM revision R, tag T 1447 WHERE R.rid IN $theset -- Restrict to revisions of interest 1448 AND T.rev = R.rid -- Select tags attached to them 1449 }] { 1450 lappend dependencies([list rev $xrid]) [list sym::tag $xchild] 1451 } 1452 state foreachrow [subst -nocommands -nobackslashes { 1453 SELECT R.rid AS xrid, B.bid AS xchild 1454 FROM revision R, branch B 1455 WHERE R.rid IN $theset -- Restrict to revisions of interest 1456 AND B.root = R.rid -- Select branches attached to them 1457 }] { 1458 lappend dependencies([list rev $xrid]) [list sym::branch $xchild] 1459 } 1460 return 1461 } 1462 1463 # result = list (changeset-id) 1464 typemethod cs_successors {revisions} { 1465 # This is a variant of 'successors' which maps the low-level 1466 # data directly to the associated changesets. I.e. instead 1467 # millions of dependency pairs (in extreme cases (Example: Tcl 1468 # CVS)) we return a very short and much more manageable list 1469 # of changesets. 1470 1471 set theset ('[join $revisions {','}]') 1472 return [state run [subst -nocommands -nobackslashes { 1473 -- (1) Primary child 1474 SELECT C.cid 1475 FROM revision R, csitem CI, changeset C 1476 WHERE R.rid IN $theset -- Restrict to revisions of interest 1477 AND R.child IS NOT NULL -- Has primary child 1478 AND CI.iid = R.child -- Select all changesets 1479 AND C.cid = CI.cid -- containing the primary child 1480 AND C.type = 0 -- which are revision changesets 1481 UNION 1482 -- (2) Secondary (branch) children 1483 SELECT C.cid 1484 FROM revision R, revisionbranchchildren B, csitem CI, changeset C 1485 WHERE R.rid IN $theset -- Restrict to revisions of interest 1486 AND R.rid = B.rid -- Select subset of branch children 1487 AND CI.iid = B.brid -- Select all changesets 1488 AND C.cid = CI.cid -- containing the branch 1489 AND C.type = 0 -- which are revision changesets 1490 UNION 1491 -- (4) Child of trunk root successor of last NTDB on trunk. 1492 SELECT C.cid 1493 FROM revision R, revision RA, csitem CI, changeset C 1494 WHERE R.rid IN $theset -- Restrict to revisions of interest 1495 AND R.isdefault -- Restrict to NTDB 1496 AND R.dbchild IS NOT NULL -- and last NTDB belonging to trunk 1497 AND RA.rid = R.dbchild -- Go directly to trunk root 1498 AND RA.child IS NOT NULL -- Has primary child. 1499 AND CI.iid = RA.child -- Select all changesets 1500 AND C.cid = CI.cid -- containing the primary child 1501 AND C.type = 0 -- which are revision changesets 1502 UNION 1503 SELECT C.cid 1504 FROM revision R, tag T, csitem CI, changeset C 1505 WHERE R.rid in $theset -- Restrict to revisions of interest 1506 AND T.rev = R.rid -- Select tags attached to them 1507 AND CI.iid = T.tid -- Select all changesets 1508 AND C.cid = CI.cid -- containing the tags 1509 AND C.type = 1 -- which are tag changesets 1510 UNION 1511 SELECT C.cid 1512 FROM revision R, branch B, csitem CI, changeset C 1513 WHERE R.rid in $theset -- Restrict to revisions of interest 1514 AND B.root = R.rid -- Select branches attached to them 1515 AND CI.iid = B.bid -- Select all changesets 1516 AND C.cid = CI.cid -- containing the branches 1517 AND C.type = 2 -- which are branch changesets 1518 }]] 1519 1520 # Regarding rev -> branch|tag, we could consider looking at 1521 # the symbol of the branch|tag, its lod-symbol, and the 1522 # revisions on that lod, but don't. Because it is not exact 1523 # enough, the branch|tag would depend on revisions coming 1524 # after its creation on the parental lod. 1525 } 1526 1527 # result = symbol name 1528 typemethod cs_lod {metaid revisions} { 1529 # Determines the name of the symbol which is the line of 1530 # development for the revisions in a changeset. The 1531 # information in the meta data referenced by the source metaid 1532 # is out of date by the time we come here (since pass 1533 # FilterSymbols), so it cannot be used. See the method 'run' 1534 # in file "c2f_pfiltersym.tcl" for more commentary on this. 1535 1536 set theset ('[join $revisions {','}]') 1537 return [state run [subst -nocommands -nobackslashes { 1538 SELECT 1539 DISTINCT L.name 1540 FROM revision R, symbol L 1541 WHERE R.rid in $theset -- Restrict to revisions of interest 1542 AND L.sid = R.lod -- Get lod symbol of revision 1543 }]] 1544 } 1545} 1546 1547# # ## ### ##### ######## ############# ##################### 1548## Helper singleton. Commands for tag symbol changesets. 1549 1550snit::type ::vc::fossil::import::cvs::project::rev::sym::tag { 1551 typemethod byrevision {} { return 0 } 1552 typemethod bysymbol {} { return 1 } 1553 typemethod istag {} { return 1 } 1554 typemethod isbranch {} { return 0 } 1555 1556 typemethod str {tag} { 1557 struct::list assign [state run { 1558 SELECT S.name, F.name, P.name 1559 FROM tag T, symbol S, file F, project P 1560 WHERE T.tid = $tag -- Find specified tag 1561 AND F.fid = T.fid -- Get file of tag 1562 AND P.pid = F.pid -- Get project of file 1563 AND S.sid = T.sid -- Get symbol of tag 1564 }] sname fname pname 1565 return "$pname/T'${sname}'::$fname" 1566 } 1567 1568 # result = list (mintime, maxtime) 1569 typemethod timerange {tags} { 1570 # The range is defined as the range of the revisions the tags 1571 # are attached to. 1572 1573 set theset ('[join $tags {','}]') 1574 return [state run [subst -nocommands -nobackslashes { 1575 SELECT MIN(R.date), MAX(R.date) 1576 FROM tag T, revision R 1577 WHERE T.tid IN $theset -- Restrict to tags of interest 1578 AND R.rid = T.rev -- Select tag parent revisions 1579 }]] 1580 } 1581 1582 # var(dv) = dict (item -> list (item)), item = list (type id) 1583 typemethod successors {dv tags} { 1584 # Tags have no successors. 1585 return 1586 } 1587 1588 # result = 4-list (itemtype itemid nextitemtype nextitemid ...) 1589 typemethod loops {tags} { 1590 # Tags have no successors, therefore cannot cause loops 1591 return {} 1592 } 1593 1594 # result = list (changeset-id) 1595 typemethod cs_successors {tags} { 1596 # Tags have no successors. 1597 return 1598 } 1599 1600 # result = symbol name 1601 typemethod cs_lod {sid tags} { 1602 # Determines the name of the symbol which is the line of 1603 # development for the tags in a changeset. Comes directly from 1604 # the symbol which is the changeset's source and its prefered 1605 # parent. 1606 1607 return [state run { 1608 SELECT P.name 1609 FROM preferedparent SP, symbol P 1610 WHERE SP.sid = $sid 1611 AND P.sid = SP.pid 1612 }] 1613 } 1614} 1615 1616# # ## ### ##### ######## ############# ##################### 1617## Helper singleton. Commands for branch symbol changesets. 1618 1619snit::type ::vc::fossil::import::cvs::project::rev::sym::branch { 1620 typemethod byrevision {} { return 0 } 1621 typemethod bysymbol {} { return 1 } 1622 typemethod istag {} { return 0 } 1623 typemethod isbranch {} { return 1 } 1624 1625 typemethod str {branch} { 1626 struct::list assign [state run { 1627 SELECT S.name, F.name, P.name 1628 FROM branch B, symbol S, file F, project P 1629 WHERE B.bid = $branch -- Find specified branch 1630 AND F.fid = B.fid -- Get file of branch 1631 AND P.pid = F.pid -- Get project of file 1632 AND S.sid = B.sid -- Get symbol of branch 1633 }] sname fname pname 1634 return "$pname/B'${sname}'::$fname" 1635 } 1636 1637 # result = list (mintime, maxtime) 1638 typemethod timerange {branches} { 1639 # The range of a branch is defined as the range of the 1640 # revisions the branches are spawned by. NOTE however that the 1641 # branches associated with a detached NTDB will have no root 1642 # spawning them, hence they have no real timerange any 1643 # longer. By using 0 we put them in front of everything else, 1644 # as they logically are. 1645 1646 set theset ('[join $branches {','}]') 1647 return [state run [subst -nocommands -nobackslashes { 1648 SELECT IFNULL(MIN(R.date),0), IFNULL(MAX(R.date),0) 1649 FROM branch B, revision R 1650 WHERE B.bid IN $theset -- Restrict to branches of interest 1651 AND R.rid = B.root -- Select branch parent revisions 1652 }]] 1653 } 1654 1655 # result = 4-list (itemtype itemid nextitemtype nextitemid ...) 1656 typemethod loops {branches} { 1657 # Note: Revisions and tags cannot cause the loop. Being of a 1658 # fundamentally different type they cannot be in the incoming 1659 # set of ids. 1660 1661 set theset ('[join $branches {','}]') 1662 return [state run [subst -nocommands -nobackslashes { 1663 SELECT B.bid, BX.bid 1664 FROM branch B, preferedparent P, branch BX 1665 WHERE B.bid IN $theset -- Restrict to branches of interest 1666 AND B.sid = P.pid -- Get the prefered branches via 1667 AND BX.sid = P.sid -- the branch symbols 1668 AND BX.bid IN $theset -- Loop 1669 }]] 1670 } 1671 1672 # var(dv) = dict (item -> list (item)), item = list (type id) 1673 typemethod successors {dv branches} { 1674 upvar 1 $dv dependencies 1675 # The first revision committed on a branch, and all branches 1676 # and tags which have it as their prefered parent are the 1677 # successors of a branch. 1678 1679 set theset ('[join $branches {','}]') 1680 state foreachrow [subst -nocommands -nobackslashes { 1681 SELECT B.bid AS xbid, R.rid AS xchild 1682 FROM branch B, revision R 1683 WHERE B.bid IN $theset -- Restrict to branches of interest 1684 AND B.first = R.rid -- Get first revision on the branch 1685 }] { 1686 lappend dependencies([list sym::branch $xbid]) [list rev $xchild] 1687 } 1688 state foreachrow [subst -nocommands -nobackslashes { 1689 SELECT B.bid AS xbid, BX.bid AS xchild 1690 FROM branch B, preferedparent P, branch BX 1691 WHERE B.bid IN $theset -- Restrict to branches of interest 1692 AND B.sid = P.pid -- Get subordinate branches via the 1693 AND BX.sid = P.sid -- prefered parents of their symbols 1694 }] { 1695 lappend dependencies([list sym::branch $xbid]) [list sym::branch $xchild] 1696 } 1697 state foreachrow [subst -nocommands -nobackslashes { 1698 SELECT B.bid AS xbid, T.tid AS xchild 1699 FROM branch B, preferedparent P, tag T 1700 WHERE B.bid IN $theset -- Restrict to branches of interest 1701 AND B.sid = P.pid -- Get subordinate tags via the 1702 AND T.sid = P.sid -- prefered parents of their symbols 1703 }] { 1704 lappend dependencies([list sym::branch $xbid]) [list sym::tag $xchild] 1705 } 1706 return 1707 } 1708 1709 # result = list (changeset-id) 1710 typemethod cs_successors {branches} { 1711 # This is a variant of 'successors' which maps the low-level 1712 # data directly to the associated changesets. I.e. instead 1713 # millions of dependency pairs (in extreme cases (Example: Tcl 1714 # CVS)) we return a very short and much more manageable list 1715 # of changesets. 1716 1717 set theset ('[join $branches {','}]') 1718 return [state run [subst -nocommands -nobackslashes { 1719 SELECT C.cid 1720 FROM branch B, revision R, csitem CI, changeset C 1721 WHERE B.bid IN $theset -- Restrict to branches of interest 1722 AND B.first = R.rid -- Get first revision on the branch 1723 AND CI.iid = R.rid -- Select all changesets 1724 AND C.cid = CI.cid -- containing this revision 1725 AND C.type = 0 -- which are revision changesets 1726 UNION 1727 SELECT C.cid 1728 FROM branch B, preferedparent P, branch BX, csitem CI, changeset C 1729 WHERE B.bid IN $theset -- Restrict to branches of interest 1730 AND B.sid = P.pid -- Get subordinate branches via the 1731 AND BX.sid = P.sid -- prefered parents of their symbols 1732 AND CI.iid = BX.bid -- Select all changesets 1733 AND C.cid = CI.cid -- containing the subordinate branches 1734 AND C.type = 2 -- which are branch changesets 1735 UNION 1736 SELECT C.cid 1737 FROM branch B, preferedparent P, tag T, csitem CI, changeset C 1738 WHERE B.bid IN $theset -- Restrict to branches of interest 1739 AND B.sid = P.pid -- Get subordinate tags via the 1740 AND T.sid = P.sid -- prefered parents of their symbols 1741 AND CI.iid = T.tid -- Select all changesets 1742 AND C.cid = CI.cid -- containing the subordinate tags 1743 AND C.type = 1 -- which are tag changesets 1744 }]] 1745 return 1746 } 1747 1748 # result = symbol name 1749 typemethod cs_lod {sid branches} { 1750 # Determines the name of the symbol which is the line of 1751 # development for the branches in a changeset. Comes directly 1752 # from the symbol which is the changeset's source and its 1753 # prefered parent. 1754 1755 return [state run { 1756 SELECT P.name 1757 FROM preferedparent SP, symbol P 1758 WHERE SP.sid = $sid 1759 AND P.sid = SP.pid 1760 }] 1761 } 1762 1763 typemethod limits {branches} { 1764 # Notes. This method exists only for branches. It is needed to 1765 # get detailed information about a backward branch. It does 1766 # not apply to tags, nor revisions. The queries can also 1767 # restrict themselves to the revision sucessors/predecessors 1768 # of branches, as only they have ordering data and thus can 1769 # cause the backwardness. 1770 1771 set theset ('[join $branches {','}]') 1772 1773 set maxp [state run [subst -nocommands -nobackslashes { 1774 -- maximal predecessor position per branch 1775 SELECT B.bid, MAX (CO.pos) 1776 FROM branch B, revision R, csitem CI, changeset C, csorder CO 1777 WHERE B.bid IN $theset -- Restrict to branches of interest 1778 AND B.root = R.rid -- Get branch root revisions 1779 AND CI.iid = R.rid -- Get changesets containing the 1780 AND C.cid = CI.cid -- root revisions, which are 1781 AND C.type = 0 -- revision changesets 1782 AND CO.cid = C.cid -- Get their topological ordering 1783 GROUP BY B.bid 1784 }]] 1785 1786 set mins [state run [subst -nocommands -nobackslashes { 1787 -- minimal successor position per branch 1788 SELECT B.bid, MIN (CO.pos) 1789 FROM branch B, revision R, csitem CI, changeset C, csorder CO 1790 WHERE B.bid IN $theset -- Restrict to branches of interest 1791 AND B.first = R.rid -- Get the first revisions on the branches 1792 AND CI.iid = R.rid -- Get changesets containing the 1793 AND C.cid = CI.cid -- first revisions, which are 1794 AND C.type = 0 -- revision changesets 1795 AND CO.cid = C.cid -- Get their topological ordering 1796 GROUP BY B.bid 1797 }]] 1798 1799 return [list $maxp $mins] 1800 } 1801 1802 # # ## ### ##### ######## ############# 1803 ## Configuration 1804 1805 pragma -hasinstances no ; # singleton 1806 pragma -hastypeinfo no ; # no introspection 1807 pragma -hastypedestroy no ; # immortal 1808} 1809 1810# # ## ### ##### ######## ############# ##################### 1811## 1812 1813namespace eval ::vc::fossil::import::cvs::project { 1814 namespace export rev 1815 namespace eval rev { 1816 namespace import ::vc::fossil::import::cvs::state 1817 namespace import ::vc::fossil::import::cvs::integrity 1818 namespace import ::vc::tools::misc::* 1819 namespace import ::vc::tools::trouble 1820 namespace import ::vc::tools::log 1821 log register csets 1822 1823 # Set up the helper singletons 1824 namespace eval rev { 1825 namespace import ::vc::fossil::import::cvs::state 1826 namespace import ::vc::fossil::import::cvs::integrity 1827 namespace import ::vc::tools::log 1828 } 1829 namespace eval sym::tag { 1830 namespace import ::vc::fossil::import::cvs::state 1831 namespace import ::vc::fossil::import::cvs::integrity 1832 namespace import ::vc::tools::log 1833 } 1834 namespace eval sym::branch { 1835 namespace import ::vc::fossil::import::cvs::state 1836 namespace import ::vc::fossil::import::cvs::integrity 1837 namespace import ::vc::tools::log 1838 } 1839 } 1840} 1841 1842# # ## ### ##### ######## ############# ##################### 1843## Ready 1844 1845package provide vc::fossil::import::cvs::project::rev 1.0 1846return 1847