# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
-# CVS $Revision: 1.22 $
+# CVS $Revision: 1.24 $
proc getcommits {rargs} {
global commits commfd phase canv mainfont
global startmsecs nextupdate
+ global ctext maincursor textcursor leftover
- if {$rargs == {}} {
- set rargs HEAD
- }
set commits {}
set phase getcommits
set startmsecs [clock clicks -milliseconds]
set nextupdate [expr $startmsecs + 100]
- if [catch {set commfd [open "|git-rev-list --merge-order $rargs" r]} err] {
+ if [catch {
+ set parse_args [concat --default HEAD $rargs]
+ set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
+ }] {
+ # if git-rev-parse failed for some reason...
+ if {$rargs == {}} {
+ set rargs HEAD
+ }
+ set parsed_args $rargs
+ }
+ if [catch {
+ set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
+ } err] {
puts stderr "Error executing git-rev-list: $err"
exit 1
}
- fconfigure $commfd -blocking 0
- fileevent $commfd readable "getcommitline $commfd"
+ set leftover {}
+ fconfigure $commfd -blocking 0 -translation binary
+ fileevent $commfd readable "getcommitlines $commfd"
$canv delete all
$canv create text 3 3 -anchor nw -text "Reading commits..." \
-font $mainfont -tags textitems
+ . config -cursor watch
+ $ctext config -cursor watch
}
-proc getcommitline {commfd} {
- global commits parents cdate children nchildren ncleft
+proc getcommitlines {commfd} {
+ global commits parents cdate children nchildren
global commitlisted phase commitinfo nextupdate
- global stopped redisplaying
+ global stopped redisplaying leftover
- set n [gets $commfd line]
- if {$n < 0} {
+ set stuff [read $commfd]
+ if {$stuff == {}} {
if {![eof $commfd]} return
# this works around what is apparently a bug in Tcl...
fconfigure $commfd -blocking 1
error_popup $err
exit 1
}
- if {![regexp {^[0-9a-f]{40}$} $line id]} {
- error_popup "Can't parse git-rev-list output: {$line}"
- exit 1
- }
- lappend commits $id
- set commitlisted($id) 1
- if {![info exists commitinfo($id)]} {
- readcommit $id
- }
- foreach p $parents($id) {
- if {[info exists commitlisted($p)]} {
- puts "oops, parent $p before child $id"
+ set start 0
+ while 1 {
+ set i [string first "\0" $stuff $start]
+ if {$i < 0} {
+ set leftover [string range $stuff $start end]
+ return
}
- }
- drawcommit $id
- if {[clock clicks -milliseconds] >= $nextupdate} {
- doupdate
- }
- while {$redisplaying} {
- set redisplaying 0
- if {$stopped == 1} {
- set stopped 0
- set phase "getcommits"
- foreach id $commits {
- drawcommit $id
- if {$stopped} break
- if {[clock clicks -milliseconds] >= $nextupdate} {
- doupdate
+ set cmit [string range $stuff $start [expr {$i - 1}]]
+ if {$start == 0} {
+ set cmit "$leftover$cmit"
+ }
+ set start [expr {$i + 1}]
+ if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
+ error_popup "Can't parse git-rev-list output: {$cmit}"
+ exit 1
+ }
+ set cmit [string range $cmit 41 end]
+ lappend commits $id
+ set commitlisted($id) 1
+ parsecommit $id $cmit 1
+ drawcommit $id
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ doupdate
+ }
+ while {$redisplaying} {
+ set redisplaying 0
+ if {$stopped == 1} {
+ set stopped 0
+ set phase "getcommits"
+ foreach id $commits {
+ drawcommit $id
+ if {$stopped} break
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ doupdate
+ }
}
}
}
incr nextupdate 100
fileevent $commfd readable {}
update
- fileevent $commfd readable "getcommitline $commfd"
+ fileevent $commfd readable "getcommitlines $commfd"
}
proc readcommit {id} {
+ if [catch {set contents [exec git-cat-file commit $id]}] return
+ parsecommit $id $contents 0
+}
+
+proc parsecommit {id contents listed} {
global commitinfo children nchildren parents nparents cdate ncleft
- global noreadobj
set inhdr 1
set comment {}
}
set parents($id) {}
set nparents($id) 0
- if {$noreadobj} {
- if [catch {set contents [exec git-cat-file commit $id]}] return
- } else {
- if [catch {set x [readobj $id]}] return
- if {[lindex $x 0] != "commit"} return
- set contents [lindex $x 1]
- }
foreach line [split $contents "\n"] {
if {$inhdr} {
if {$line == {}} {
}
lappend parents($id) $p
incr nparents($id)
- if {[lsearch -exact $children($p) $id] < 0} {
+ # sometimes we get a commit that lists a parent twice...
+ if {$listed && [lsearch -exact $children($p) $id] < 0} {
lappend children($p) $id
incr nchildren($p)
incr ncleft($p)
- } else {
- puts "child $id already in $p's list??"
}
} elseif {$tag == "author"} {
set x [expr {[llength $line] - 2}]
}
} else {
if {$comment == {}} {
- set headline $line
+ set headline [string trim $line]
} else {
append comment "\n"
}
+ if {!$listed} {
+ # git-rev-list indents the comment by 4 spaces;
+ # if we got this via git-cat-file, add the indentation
+ append comment " "
+ }
append comment $line
}
}
global canv canv2 canv3 linespc charspc ctext cflist textfont
global findtype findloc findstring fstring geometry
global entries sha1entry sha1string sha1but
+ global maincursor textcursor
+ global linectxmenu
menu .bar
.bar add cascade -label "File" -menu .bar.file
bind . <Button-1> "click %W"
bind $fstring <Key-Return> dofind
bind $sha1entry <Key-Return> gotocommit
+
+ set maincursor [. cget -cursor]
+ set textcursor [$ctext cget -cursor]
+
+ set linectxmenu .linectxmenu
+ menu $linectxmenu -tearoff 0
+ $linectxmenu add command -label "Select" -command lineselect
}
# when we make a key binding for the toplevel, make sure
Use and redistribute under the terms of the GNU General Public License
-(CVS $Revision: 1.22 $)} \
+(CVS $Revision: 1.24 $)} \
-justify center -aspect 400
pack $w.m -side top -fill x -padx 20 -pady 20
button $w.ok -text Close -command "destroy $w"
proc assigncolor {id} {
global commitinfo colormap commcolors colors nextcolor
global parents nparents children nchildren
+ global cornercrossings crossings
+
if [info exists colormap($id)] return
set ncolors [llength $colors]
- if {$nparents($id) == 1 && $nchildren($id) == 1} {
+ if {$nparents($id) <= 1 && $nchildren($id) == 1} {
set child [lindex $children($id) 0]
if {[info exists colormap($child)]
&& $nparents($child) == 1} {
}
}
set badcolors {}
- foreach child $children($id) {
- if {[info exists colormap($child)]
- && [lsearch -exact $badcolors $colormap($child)] < 0} {
- lappend badcolors $colormap($child)
+ if {[info exists cornercrossings($id)]} {
+ foreach x $cornercrossings($id) {
+ if {[info exists colormap($x)]
+ && [lsearch -exact $badcolors $colormap($x)] < 0} {
+ lappend badcolors $colormap($x)
+ }
+ }
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors {}
}
- if {[info exists parents($child)]} {
- foreach p $parents($child) {
- if {[info exists colormap($p)]
- && [lsearch -exact $badcolors $colormap($p)] < 0} {
- lappend badcolors $colormap($p)
+ }
+ set origbad $badcolors
+ if {[llength $badcolors] < $ncolors - 1} {
+ if {[info exists crossings($id)]} {
+ foreach x $crossings($id) {
+ if {[info exists colormap($x)]
+ && [lsearch -exact $badcolors $colormap($x)] < 0} {
+ lappend badcolors $colormap($x)
}
}
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors $origbad
+ }
}
+ set origbad $badcolors
}
- if {[llength $badcolors] >= $ncolors} {
- set badcolors {}
+ if {[llength $badcolors] < $ncolors - 1} {
+ foreach child $children($id) {
+ if {[info exists colormap($child)]
+ && [lsearch -exact $badcolors $colormap($child)] < 0} {
+ lappend badcolors $colormap($child)
+ }
+ if {[info exists parents($child)]} {
+ foreach p $parents($child) {
+ if {[info exists colormap($p)]
+ && [lsearch -exact $badcolors $colormap($p)] < 0} {
+ lappend badcolors $colormap($p)
+ }
+ }
+ }
+ }
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors $origbad
+ }
}
for {set i 0} {$i <= $ncolors} {incr i} {
set c [lindex $colors $nextcolor]
proc initgraph {} {
global canvy canvy0 lineno numcommits lthickness nextcolor linespc
- global linestarty
+ global mainline sidelines
global nchildren ncleft
allcanvs delete all
set lineno -1
set numcommits 0
set lthickness [expr {int($linespc / 9) + 1}]
- catch {unset linestarty}
+ catch {unset mainline}
+ catch {unset sidelines}
foreach id [array names nchildren] {
set ncleft($id) $nchildren($id)
}
}
+proc bindline {t id} {
+ global canv
+
+ $canv bind $t <Button-3> "linemenu %X %Y $id"
+ $canv bind $t <Enter> "lineenter %x %y $id"
+ $canv bind $t <Motion> "linemotion %x %y $id"
+ $canv bind $t <Leave> "lineleave $id"
+}
+
proc drawcommitline {level} {
- global parents children nparents nchildren ncleft todo
+ global parents children nparents nchildren todo
global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
- global datemode cdate
global lineid linehtag linentag linedtag commitinfo
- global colormap numcommits currentparents
+ global colormap numcommits currentparents dupparents
global oldlevel oldnlines oldtodo
global idtags idline idheads
- global lineno lthickness linestarty
+ global lineno lthickness mainline sidelines
global commitlisted
incr numcommits
set nparents($id) 0
}
}
+ assigncolor $id
set currentparents {}
+ set dupparents {}
if {[info exists commitlisted($id)] && [info exists parents($id)]} {
- set currentparents $parents($id)
+ foreach p $parents($id) {
+ if {[lsearch -exact $currentparents $p] < 0} {
+ lappend currentparents $p
+ } else {
+ # remember that this parent was listed twice
+ lappend dupparents $p
+ }
+ }
}
set x [expr $canvx0 + $level * $linespc]
set y1 $canvy
set canvy [expr $canvy + $linespc]
allcanvs conf -scrollregion \
[list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
- if {[info exists linestarty($id)] && $linestarty($id) < $y1} {
- set t [$canv create line $x $linestarty($id) $x $y1 \
+ if {[info exists mainline($id)]} {
+ lappend mainline($id) $x $y1
+ set t [$canv create line $mainline($id) \
-width $lthickness -fill $colormap($id)]
$canv lower $t
+ bindline $t $id
+ }
+ if {[info exists sidelines($id)]} {
+ foreach ls $sidelines($id) {
+ set coords [lindex $ls 0]
+ set thick [lindex $ls 1]
+ set t [$canv create line $coords -fill $colormap($id) \
+ -width [expr {$thick * $lthickness}]]
+ $canv lower $t
+ bindline $t $id
+ }
}
set orad [expr {$linespc / 3}]
set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
-fill $ofill -outline black -width 1]
$canv raise $t
set xt [expr $canvx0 + [llength $todo] * $linespc]
- if {$nparents($id) > 2} {
- set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
+ if {[llength $currentparents] > 2} {
+ set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
}
set marks {}
set ntags 0
}
proc updatetodo {level noshortcut} {
- global datemode currentparents ncleft todo
- global linestarty oldlevel oldtodo oldnlines
- global canvy linespc
+ global currentparents ncleft todo
+ global mainline oldlevel oldtodo oldnlines
+ global canvx0 canvy linespc mainline
global commitinfo
- foreach p $currentparents {
- if {![info exists commitinfo($p)]} {
- readcommit $p
- }
- }
+ set oldlevel $level
+ set oldtodo $todo
+ set oldnlines [llength $todo]
if {!$noshortcut && [llength $currentparents] == 1} {
set p [lindex $currentparents 0]
- if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
- assigncolor $p
- set linestarty($p) [expr $canvy - $linespc]
+ if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
+ set ncleft($p) 0
+ set x [expr $canvx0 + $level * $linespc]
+ set y [expr $canvy - $linespc]
+ set mainline($p) [list $x $y]
set todo [lreplace $todo $level $level $p]
return 0
}
}
- set oldlevel $level
- set oldtodo $todo
- set oldnlines [llength $todo]
set todo [lreplace $todo $level $level]
set i $level
foreach p $currentparents {
incr ncleft($p) -1
set k [lsearch -exact $todo $p]
if {$k < 0} {
- assigncolor $p
set todo [linsert $todo $i $p]
incr i
}
return 1
}
+proc notecrossings {id lo hi corner} {
+ global oldtodo crossings cornercrossings
+
+ for {set i $lo} {[incr i] < $hi} {} {
+ set p [lindex $oldtodo $i]
+ if {$p == {}} continue
+ if {$i == $corner} {
+ if {![info exists cornercrossings($id)]
+ || [lsearch -exact $cornercrossings($id) $p] < 0} {
+ lappend cornercrossings($id) $p
+ }
+ if {![info exists cornercrossings($p)]
+ || [lsearch -exact $cornercrossings($p) $id] < 0} {
+ lappend cornercrossings($p) $id
+ }
+ } else {
+ if {![info exists crossings($id)]
+ || [lsearch -exact $crossings($id) $p] < 0} {
+ lappend crossings($id) $p
+ }
+ if {![info exists crossings($p)]
+ || [lsearch -exact $crossings($p) $id] < 0} {
+ lappend crossings($p) $id
+ }
+ }
+ }
+}
+
proc drawslants {} {
- global canv linestarty canvx0 canvy linespc
- global oldlevel oldtodo todo currentparents
+ global canv mainline sidelines canvx0 canvy linespc
+ global oldlevel oldtodo todo currentparents dupparents
global lthickness linespc canvy colormap
set y1 [expr $canvy - $linespc]
if {$i == $oldlevel} {
foreach p $currentparents {
set j [lsearch -exact $todo $p]
- if {$i == $j && ![info exists linestarty($p)]} {
- set linestarty($p) $y1
- } else {
- set xj [expr {$canvx0 + $j * $linespc}]
- set coords [list $xi $y1]
- if {$j < $i - 1} {
- lappend coords [expr $xj + $linespc] $y1
- } elseif {$j > $i + 1} {
- lappend coords [expr $xj - $linespc] $y1
- }
+ set coords [list $xi $y1]
+ set xj [expr {$canvx0 + $j * $linespc}]
+ if {$j < $i - 1} {
+ lappend coords [expr $xj + $linespc] $y1
+ notecrossings $p $j $i [expr {$j + 1}]
+ } elseif {$j > $i + 1} {
+ lappend coords [expr $xj - $linespc] $y1
+ notecrossings $p $i $j [expr {$j - 1}]
+ }
+ if {[lsearch -exact $dupparents $p] >= 0} {
+ # draw a double-width line to indicate the doubled parent
lappend coords $xj $y2
- set t [$canv create line $coords -width $lthickness \
- -fill $colormap($p)]
- $canv lower $t
- if {![info exists linestarty($p)]} {
- set linestarty($p) $y2
+ lappend sidelines($p) [list $coords 2]
+ if {![info exists mainline($p)]} {
+ set mainline($p) [list $xj $y2]
+ }
+ } else {
+ # normal case, no parent duplicated
+ if {![info exists mainline($p)]} {
+ if {$i != $j} {
+ lappend coords $xj $y2
+ }
+ set mainline($p) $coords
+ } else {
+ lappend coords $xj $y2
+ lappend sidelines($p) [list $coords 1]
}
}
}
} elseif {[lindex $todo $i] != $id} {
set j [lsearch -exact $todo $id]
set xj [expr {$canvx0 + $j * $linespc}]
- set coords {}
- if {[info exists linestarty($id)] && $linestarty($id) < $y1} {
- lappend coords $xi $linestarty($id)
- }
- lappend coords $xi $y1 $xj $y2
- set t [$canv create line $coords -width $lthickness \
- -fill $colormap($id)]
- $canv lower $t
- set linestarty($id) $y2
+ lappend mainline($id) $xi $y1 $xj $y2
}
}
}
if {$todo != {}} {
puts "ERROR: none of the pending commits can be done yet:"
foreach p $todo {
- puts " $p"
+ puts " $p ($ncleft($p))"
}
}
return -1
set todo $id
set startcommits $id
initgraph
- assigncolor $id
drawcommitline 0
updatetodo 0 $datemode
} else {
if {$nchildren($id) == 0} {
lappend todo $id
lappend startcommits $id
- assigncolor $id
}
set level [decidenext]
if {$id != [lindex $todo $level]} {
proc finishcommits {} {
global phase
global startcommits
+ global ctext maincursor textcursor
if {$phase != "incrdraw"} {
$canv delete all
drawslants
set level [decidenext]
drawrest $level [llength $startcommits]
+ . config -cursor $maincursor
+ $ctext config -cursor $textcursor
}
proc drawgraph {} {
global numcommits
global nextupdate startmsecs startcommits idline
- set phase drawgraph
- set startid [lindex $startcommits $startix]
- set startline -1
- if {$startid != {}} {
- set startline $idline($startid)
- }
- while 1 {
- if {$stopped} break
- drawcommitline $level
- set hard [updatetodo $level $datemode]
- if {$numcommits == $startline} {
- lappend todo $startid
- set hard 1
- incr startix
- set startid [lindex $startcommits $startix]
- set startline -1
- if {$startid != {}} {
- set startline $idline($startid)
- }
- }
- if {$hard} {
- set level [decidenext]
- if {$level < 0} break
- drawslants
+ if {$level >= 0} {
+ set phase drawgraph
+ set startid [lindex $startcommits $startix]
+ set startline -1
+ if {$startid != {}} {
+ set startline $idline($startid)
}
- if {[clock clicks -milliseconds] >= $nextupdate} {
- update
- incr nextupdate 100
+ while 1 {
+ if {$stopped} break
+ drawcommitline $level
+ set hard [updatetodo $level $datemode]
+ if {$numcommits == $startline} {
+ lappend todo $startid
+ set hard 1
+ incr startix
+ set startid [lindex $startcommits $startix]
+ set startline -1
+ if {$startid != {}} {
+ set startline $idline($startid)
+ }
+ }
+ if {$hard} {
+ set level [decidenext]
+ if {$level < 0} break
+ drawslants
+ }
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ update
+ incr nextupdate 100
+ }
}
}
set phase {}
set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
- puts "overall $drawmsecs ms for $numcommits commits"
+ #puts "overall $drawmsecs ms for $numcommits commits"
if {$redisplaying} {
if {$stopped == 0 && [info exists selectedline]} {
selectline $selectedline
global canvy0 linespc nparents treepending
global cflist treediffs currentid sha1entry
global commentend seenfile idtags
+ $canv delete hover
if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
$canv delete secsel
set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
error_popup "$type $sha1string is not known"
}
+proc linemenu {x y id} {
+ global linectxmenu linemenuid
+ set linemenuid $id
+ $linectxmenu post $x $y
+}
+
+proc lineselect {} {
+ global linemenuid idline
+ if {[info exists linemenuid] && [info exists idline($linemenuid)]} {
+ selectline $idline($linemenuid)
+ }
+}
+
+proc lineenter {x y id} {
+ global hoverx hovery hoverid hovertimer
+ global commitinfo canv
+
+ if {![info exists commitinfo($id)]} return
+ set hoverx $x
+ set hovery $y
+ set hoverid $id
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ }
+ set hovertimer [after 500 linehover]
+ $canv delete hover
+}
+
+proc linemotion {x y id} {
+ global hoverx hovery hoverid hovertimer
+
+ if {[info exists hoverid] && $id == $hoverid} {
+ set hoverx $x
+ set hovery $y
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ }
+ set hovertimer [after 500 linehover]
+ }
+}
+
+proc lineleave {id} {
+ global hoverid hovertimer canv
+
+ if {[info exists hoverid] && $id == $hoverid} {
+ $canv delete hover
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ unset hovertimer
+ }
+ unset hoverid
+ }
+}
+
+proc linehover {} {
+ global hoverx hovery hoverid hovertimer
+ global canv linespc lthickness
+ global commitinfo mainfont
+
+ set text [lindex $commitinfo($hoverid) 0]
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax == {}} return
+ set yfrac [lindex [$canv yview] 0]
+ set x [expr {$hoverx + 2 * $linespc}]
+ set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
+ set x0 [expr {$x - 2 * $lthickness}]
+ set y0 [expr {$y - 2 * $lthickness}]
+ set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
+ set y1 [expr {$y + $linespc + 2 * $lthickness}]
+ set t [$canv create rectangle $x0 $y0 $x1 $y1 \
+ -fill \#ffff80 -outline black -width 1 -tags hover]
+ $canv raise $t
+ set t [$canv create text $x $y -anchor nw -text $text -tags hover]
+ $canv raise $t
+}
+
proc doquit {} {
global stopped
set stopped 100
}
}
-set noreadobj [load libreadobj.so.0.0]
-set noreadobj 0
set stopped 0
set redisplaying 0
set stuffsaved 0