2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 # CVS $Revision: 1.24 $
12 proc getcommits {rargs} {
13 global commits commfd phase canv mainfont
14 global startmsecs nextupdate
15 global ctext maincursor textcursor nlines
22 set startmsecs [clock clicks -milliseconds]
23 set nextupdate [expr $startmsecs + 100]
24 if [catch {set commfd [open "|git-rev-list --merge-order $rargs" r]} err] {
25 puts stderr "Error executing git-rev-list: $err"
29 fconfigure $commfd -blocking 0
30 fileevent $commfd readable "getcommitline $commfd"
32 $canv create text 3 3 -anchor nw -text "Reading commits..." \
33 -font $mainfont -tags textitems
34 . config -cursor watch
35 $ctext config -cursor watch
38 proc getcommitline {commfd} {
39 global commits parents cdate children nchildren ncleft
40 global commitlisted phase commitinfo nextupdate
41 global stopped redisplaying nlines
43 set n [gets $commfd line]
45 if {![eof $commfd]} return
46 # this works around what is apparently a bug in Tcl...
47 fconfigure $commfd -blocking 1
48 if {![catch {close $commfd} err]} {
49 after idle finishcommits
52 if {[string range $err 0 4] == "usage"} {
54 {Gitk: error reading commits: bad arguments to git-rev-list.
55 (Note: arguments to gitk are passed to git-rev-list
56 to allow selection of commits to be displayed.)}
58 set err "Error reading commits: $err"
64 if {![regexp {^[0-9a-f]{40}$} $line id]} {
65 error_popup "Can't parse git-rev-list output: {$line}"
69 set commitlisted($id) 1
70 if {![info exists commitinfo($id)]} {
73 foreach p $parents($id) {
74 if {[info exists commitlisted($p)]} {
75 puts "oops, parent $p before child $id"
79 if {[clock clicks -milliseconds] >= $nextupdate} {
82 while {$redisplaying} {
86 set phase "getcommits"
90 if {[clock clicks -milliseconds] >= $nextupdate} {
99 global commfd nextupdate
102 fileevent $commfd readable {}
104 fileevent $commfd readable "getcommitline $commfd"
107 proc readcommit {id} {
108 global commitinfo children nchildren parents nparents cdate ncleft
118 if {![info exists nchildren($id)]} {
126 if [catch {set contents [exec git-cat-file commit $id]}] return
128 if [catch {set x [readobj $id]}] return
129 if {[lindex $x 0] != "commit"} return
130 set contents [lindex $x 1]
132 foreach line [split $contents "\n"] {
137 set tag [lindex $line 0]
138 if {$tag == "parent"} {
139 set p [lindex $line 1]
140 if {![info exists nchildren($p)]} {
145 lappend parents($id) $p
147 if {[lsearch -exact $children($p) $id] < 0} {
148 lappend children($p) $id
152 puts "child $id already in $p's list??"
154 } elseif {$tag == "author"} {
155 set x [expr {[llength $line] - 2}]
156 set audate [lindex $line $x]
157 set auname [lrange $line 1 [expr {$x - 1}]]
158 } elseif {$tag == "committer"} {
159 set x [expr {[llength $line] - 2}]
160 set comdate [lindex $line $x]
161 set comname [lrange $line 1 [expr {$x - 1}]]
165 if {$comment == {}} {
174 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
176 if {$comdate != {}} {
177 set cdate($id) $comdate
178 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
180 set commitinfo($id) [list $headline $auname $audate \
181 $comname $comdate $comment]
185 global tagids idtags headids idheads
186 set tags [glob -nocomplain -types f .git/refs/tags/*]
191 if {[regexp {^[0-9a-f]{40}} $line id]} {
192 set direct [file tail $f]
193 set tagids($direct) $id
194 lappend idtags($id) $direct
195 set contents [split [exec git-cat-file tag $id] "\n"]
199 foreach l $contents {
201 switch -- [lindex $l 0] {
202 "object" {set obj [lindex $l 1]}
203 "type" {set type [lindex $l 1]}
204 "tag" {set tag [string range $l 4 end]}
207 if {$obj != {} && $type == "commit" && $tag != {}} {
208 set tagids($tag) $obj
209 lappend idtags($obj) $tag
215 set heads [glob -nocomplain -types f .git/refs/heads/*]
219 set line [read $fd 40]
220 if {[regexp {^[0-9a-f]{40}} $line id]} {
221 set head [file tail $f]
222 set headids($head) $line
223 lappend idheads($line) $head
230 proc error_popup msg {
234 message $w.m -text $msg -justify center -aspect 400
235 pack $w.m -side top -fill x -padx 20 -pady 20
236 button $w.ok -text OK -command "destroy $w"
237 pack $w.ok -side bottom -fill x
238 bind $w <Visibility> "grab $w; focus $w"
243 global canv canv2 canv3 linespc charspc ctext cflist textfont
244 global findtype findloc findstring fstring geometry
245 global entries sha1entry sha1string sha1but
246 global maincursor textcursor
250 .bar add cascade -label "File" -menu .bar.file
252 .bar.file add command -label "Quit" -command doquit
254 .bar add cascade -label "Help" -menu .bar.help
255 .bar.help add command -label "About gitk" -command about
256 . configure -menu .bar
258 if {![info exists geometry(canv1)]} {
259 set geometry(canv1) [expr 45 * $charspc]
260 set geometry(canv2) [expr 30 * $charspc]
261 set geometry(canv3) [expr 15 * $charspc]
262 set geometry(canvh) [expr 25 * $linespc + 4]
263 set geometry(ctextw) 80
264 set geometry(ctexth) 30
265 set geometry(cflistw) 30
267 panedwindow .ctop -orient vertical
268 if {[info exists geometry(width)]} {
269 .ctop conf -width $geometry(width) -height $geometry(height)
270 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
271 set geometry(ctexth) [expr {($texth - 8) /
272 [font metrics $textfont -linespace]}]
276 pack .ctop.top.bar -side bottom -fill x
277 set cscroll .ctop.top.csb
278 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
279 pack $cscroll -side right -fill y
280 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
281 pack .ctop.top.clist -side top -fill both -expand 1
283 set canv .ctop.top.clist.canv
284 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
286 -yscrollincr $linespc -yscrollcommand "$cscroll set"
287 .ctop.top.clist add $canv
288 set canv2 .ctop.top.clist.canv2
289 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
290 -bg white -bd 0 -yscrollincr $linespc
291 .ctop.top.clist add $canv2
292 set canv3 .ctop.top.clist.canv3
293 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
294 -bg white -bd 0 -yscrollincr $linespc
295 .ctop.top.clist add $canv3
296 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
298 set sha1entry .ctop.top.bar.sha1
299 set entries $sha1entry
300 set sha1but .ctop.top.bar.sha1label
301 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
302 -command gotocommit -width 8
303 $sha1but conf -disabledforeground [$sha1but cget -foreground]
304 pack .ctop.top.bar.sha1label -side left
305 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
306 trace add variable sha1string write sha1change
307 pack $sha1entry -side left -pady 2
308 button .ctop.top.bar.findbut -text "Find" -command dofind
309 pack .ctop.top.bar.findbut -side left
311 set fstring .ctop.top.bar.findstring
312 lappend entries $fstring
313 entry $fstring -width 30 -font $textfont -textvariable findstring
314 pack $fstring -side left -expand 1 -fill x
316 tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
317 set findloc "All fields"
318 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
319 Comments Author Committer
320 pack .ctop.top.bar.findloc -side right
321 pack .ctop.top.bar.findtype -side right
323 panedwindow .ctop.cdet -orient horizontal
325 frame .ctop.cdet.left
326 set ctext .ctop.cdet.left.ctext
327 text $ctext -bg white -state disabled -font $textfont \
328 -width $geometry(ctextw) -height $geometry(ctexth) \
329 -yscrollcommand ".ctop.cdet.left.sb set"
330 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
331 pack .ctop.cdet.left.sb -side right -fill y
332 pack $ctext -side left -fill both -expand 1
333 .ctop.cdet add .ctop.cdet.left
335 $ctext tag conf filesep -font [concat $textfont bold]
336 $ctext tag conf hunksep -back blue -fore white
337 $ctext tag conf d0 -back "#ff8080"
338 $ctext tag conf d1 -back green
339 $ctext tag conf found -back yellow
341 frame .ctop.cdet.right
342 set cflist .ctop.cdet.right.cfiles
343 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
344 -yscrollcommand ".ctop.cdet.right.sb set"
345 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
346 pack .ctop.cdet.right.sb -side right -fill y
347 pack $cflist -side left -fill both -expand 1
348 .ctop.cdet add .ctop.cdet.right
349 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
351 pack .ctop -side top -fill both -expand 1
353 bindall <1> {selcanvline %x %y}
354 bindall <B1-Motion> {selcanvline %x %y}
355 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
356 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
357 bindall <2> "allcanvs scan mark 0 %y"
358 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
359 bind . <Key-Up> "selnextline -1"
360 bind . <Key-Down> "selnextline 1"
361 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
362 bind . <Key-Next> "allcanvs yview scroll 1 pages"
363 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
364 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
365 bindkey <Key-space> "$ctext yview scroll 1 pages"
366 bindkey p "selnextline -1"
367 bindkey n "selnextline 1"
368 bindkey b "$ctext yview scroll -1 pages"
369 bindkey d "$ctext yview scroll 18 units"
370 bindkey u "$ctext yview scroll -18 units"
374 bind . <Control-q> doquit
375 bind . <Control-f> dofind
376 bind . <Control-g> findnext
377 bind . <Control-r> findprev
378 bind . <Control-equal> {incrfont 1}
379 bind . <Control-KP_Add> {incrfont 1}
380 bind . <Control-minus> {incrfont -1}
381 bind . <Control-KP_Subtract> {incrfont -1}
382 bind $cflist <<ListboxSelect>> listboxsel
383 bind . <Destroy> {savestuff %W}
384 bind . <Button-1> "click %W"
385 bind $fstring <Key-Return> dofind
386 bind $sha1entry <Key-Return> gotocommit
388 set maincursor [. cget -cursor]
389 set textcursor [$ctext cget -cursor]
391 set linectxmenu .linectxmenu
392 menu $linectxmenu -tearoff 0
393 $linectxmenu add command -label "Select" -command lineselect
396 # when we make a key binding for the toplevel, make sure
397 # it doesn't get triggered when that key is pressed in the
398 # find string entry widget.
399 proc bindkey {ev script} {
402 set escript [bind Entry $ev]
403 if {$escript == {}} {
404 set escript [bind Entry <Key>]
407 bind $e $ev "$escript; break"
411 # set the focus back to the toplevel for any click outside
422 global canv canv2 canv3 ctext cflist mainfont textfont
424 if {$stuffsaved} return
425 if {![winfo viewable .]} return
427 set f [open "~/.gitk-new" w]
428 puts $f "set mainfont {$mainfont}"
429 puts $f "set textfont {$textfont}"
430 puts $f "set geometry(width) [winfo width .ctop]"
431 puts $f "set geometry(height) [winfo height .ctop]"
432 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
433 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
434 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
435 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
436 set wid [expr {([winfo width $ctext] - 8) \
437 / [font measure $textfont "0"]}]
438 puts $f "set geometry(ctextw) $wid"
439 set wid [expr {([winfo width $cflist] - 11) \
440 / [font measure [$cflist cget -font] "0"]}]
441 puts $f "set geometry(cflistw) $wid"
443 file rename -force "~/.gitk-new" "~/.gitk"
448 proc resizeclistpanes {win w} {
450 if [info exists oldwidth($win)] {
451 set s0 [$win sash coord 0]
452 set s1 [$win sash coord 1]
454 set sash0 [expr {int($w/2 - 2)}]
455 set sash1 [expr {int($w*5/6 - 2)}]
457 set factor [expr {1.0 * $w / $oldwidth($win)}]
458 set sash0 [expr {int($factor * [lindex $s0 0])}]
459 set sash1 [expr {int($factor * [lindex $s1 0])}]
463 if {$sash1 < $sash0 + 20} {
464 set sash1 [expr $sash0 + 20]
466 if {$sash1 > $w - 10} {
467 set sash1 [expr $w - 10]
468 if {$sash0 > $sash1 - 20} {
469 set sash0 [expr $sash1 - 20]
473 $win sash place 0 $sash0 [lindex $s0 1]
474 $win sash place 1 $sash1 [lindex $s1 1]
476 set oldwidth($win) $w
479 proc resizecdetpanes {win w} {
481 if [info exists oldwidth($win)] {
482 set s0 [$win sash coord 0]
484 set sash0 [expr {int($w*3/4 - 2)}]
486 set factor [expr {1.0 * $w / $oldwidth($win)}]
487 set sash0 [expr {int($factor * [lindex $s0 0])}]
491 if {$sash0 > $w - 15} {
492 set sash0 [expr $w - 15]
495 $win sash place 0 $sash0 [lindex $s0 1]
497 set oldwidth($win) $w
501 global canv canv2 canv3
507 proc bindall {event action} {
508 global canv canv2 canv3
509 bind $canv $event $action
510 bind $canv2 $event $action
511 bind $canv3 $event $action
516 if {[winfo exists $w]} {
521 wm title $w "About gitk"
525 Copyright © 2005 Paul Mackerras
527 Use and redistribute under the terms of the GNU General Public License
529 (CVS $Revision: 1.24 $)} \
530 -justify center -aspect 400
531 pack $w.m -side top -fill x -padx 20 -pady 20
532 button $w.ok -text Close -command "destroy $w"
533 pack $w.ok -side bottom
536 proc assigncolor {id} {
537 global commitinfo colormap commcolors colors nextcolor
538 global parents nparents children nchildren
539 if [info exists colormap($id)] return
540 set ncolors [llength $colors]
541 if {$nparents($id) == 1 && $nchildren($id) == 1} {
542 set child [lindex $children($id) 0]
543 if {[info exists colormap($child)]
544 && $nparents($child) == 1} {
545 set colormap($id) $colormap($child)
550 foreach child $children($id) {
551 if {[info exists colormap($child)]
552 && [lsearch -exact $badcolors $colormap($child)] < 0} {
553 lappend badcolors $colormap($child)
555 if {[info exists parents($child)]} {
556 foreach p $parents($child) {
557 if {[info exists colormap($p)]
558 && [lsearch -exact $badcolors $colormap($p)] < 0} {
559 lappend badcolors $colormap($p)
564 if {[llength $badcolors] >= $ncolors} {
567 for {set i 0} {$i <= $ncolors} {incr i} {
568 set c [lindex $colors $nextcolor]
569 if {[incr nextcolor] >= $ncolors} {
572 if {[lsearch -exact $badcolors $c]} break
578 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
580 global nchildren ncleft
587 set lthickness [expr {int($linespc / 9) + 1}]
589 foreach id [array names nchildren] {
590 set ncleft($id) $nchildren($id)
594 proc drawcommitline {level} {
595 global parents children nparents nchildren ncleft todo
596 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
597 global datemode cdate
598 global lineid linehtag linentag linedtag commitinfo
599 global colormap numcommits currentparents
600 global oldlevel oldnlines oldtodo
601 global idtags idline idheads
602 global lineno lthickness glines
607 set id [lindex $todo $level]
608 set lineid($lineno) $id
609 set idline($id) $lineno
610 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
611 if {![info exists commitinfo($id)]} {
613 if {![info exists commitinfo($id)]} {
614 set commitinfo($id) {"No commit information available"}
618 set currentparents {}
619 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
620 set currentparents $parents($id)
622 set x [expr $canvx0 + $level * $linespc]
624 set canvy [expr $canvy + $linespc]
625 allcanvs conf -scrollregion \
626 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
627 if {[info exists glines($id)]} {
628 lappend glines($id) $x $y1
629 set t [$canv create line $glines($id) \
630 -width $lthickness -fill $colormap($id)]
632 $canv bind $t <Button-3> "linemenu %X %Y $id"
633 $canv bind $t <Enter> "lineenter %x %y $id"
634 $canv bind $t <Motion> "linemotion %x %y $id"
635 $canv bind $t <Leave> "lineleave $id"
637 set orad [expr {$linespc / 3}]
638 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
639 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
640 -fill $ofill -outline black -width 1]
642 set xt [expr $canvx0 + [llength $todo] * $linespc]
643 if {$nparents($id) > 2} {
644 set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
648 if {[info exists idtags($id)]} {
649 set marks $idtags($id)
650 set ntags [llength $marks]
652 if {[info exists idheads($id)]} {
653 set marks [concat $marks $idheads($id)]
656 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
657 set yt [expr $y1 - 0.5 * $linespc]
658 set yb [expr $yt + $linespc - 1]
662 set wid [font measure $mainfont $tag]
665 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
667 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
668 -width $lthickness -fill black]
670 $canv bind $t <Button-3> "linemenu %X %Y $id"
671 $canv bind $t <Enter> "lineenter %x %y $id"
672 $canv bind $t <Motion> "linemotion %x %y $id"
673 $canv bind $t <Leave> "lineleave $id"
674 foreach tag $marks x $xvals wid $wvals {
675 set xl [expr $x + $delta]
676 set xr [expr $x + $delta + $wid + $lthickness]
677 if {[incr ntags -1] >= 0} {
679 $canv create polygon $x [expr $yt + $delta] $xl $yt\
680 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
681 -width 1 -outline black -fill yellow
684 set xl [expr $xl - $delta/2]
685 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
686 -width 1 -outline black -fill green
688 $canv create text $xl $y1 -anchor w -text $tag \
692 set headline [lindex $commitinfo($id) 0]
693 set name [lindex $commitinfo($id) 1]
694 set date [lindex $commitinfo($id) 2]
695 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
696 -text $headline -font $mainfont ]
697 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
698 -text $name -font $namefont]
699 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
700 -text $date -font $mainfont]
703 proc updatetodo {level noshortcut} {
704 global datemode currentparents ncleft todo
705 global glines oldlevel oldtodo oldnlines
706 global canvx0 canvy linespc glines
709 foreach p $currentparents {
710 if {![info exists commitinfo($p)]} {
714 set x [expr $canvx0 + $level * $linespc]
715 set y [expr $canvy - $linespc]
716 if {!$noshortcut && [llength $currentparents] == 1} {
717 set p [lindex $currentparents 0]
718 if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
720 set glines($p) [list $x $y]
721 set todo [lreplace $todo $level $level $p]
728 set oldnlines [llength $todo]
729 set todo [lreplace $todo $level $level]
731 foreach p $currentparents {
733 set k [lsearch -exact $todo $p]
736 set todo [linsert $todo $i $p]
744 global canv glines canvx0 canvy linespc
745 global oldlevel oldtodo todo currentparents
746 global lthickness linespc canvy colormap
748 set y1 [expr $canvy - $linespc]
751 foreach id $oldtodo {
753 if {$id == {}} continue
754 set xi [expr {$canvx0 + $i * $linespc}]
755 if {$i == $oldlevel} {
756 foreach p $currentparents {
757 set j [lsearch -exact $todo $p]
758 if {$i == $j && ![info exists glines($p)]} {
759 set glines($p) [list $xi $y1]
761 set xj [expr {$canvx0 + $j * $linespc}]
762 set coords [list $xi $y1]
764 lappend coords [expr $xj + $linespc] $y1
765 } elseif {$j > $i + 1} {
766 lappend coords [expr $xj - $linespc] $y1
768 lappend coords $xj $y2
769 if {![info exists glines($p)]} {
770 set glines($p) $coords
772 set t [$canv create line $coords -width $lthickness \
775 $canv bind $t <Button-3> "linemenu %X %Y $p"
776 $canv bind $t <Enter> "lineenter %x %y $p"
777 $canv bind $t <Motion> "linemotion %x %y $p"
778 $canv bind $t <Leave> "lineleave $p"
782 } elseif {[lindex $todo $i] != $id} {
783 set j [lsearch -exact $todo $id]
784 set xj [expr {$canvx0 + $j * $linespc}]
785 lappend glines($id) $xi $y1 $xj $y2
791 global parents children nchildren ncleft todo
792 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
793 global datemode cdate
794 global lineid linehtag linentag linedtag commitinfo
795 global currentparents oldlevel oldnlines oldtodo
796 global lineno lthickness
798 # remove the null entry if present
799 set nullentry [lsearch -exact $todo {}]
800 if {$nullentry >= 0} {
801 set todo [lreplace $todo $nullentry $nullentry]
804 # choose which one to do next time around
805 set todol [llength $todo]
808 for {set k $todol} {[incr k -1] >= 0} {} {
809 set p [lindex $todo $k]
810 if {$ncleft($p) == 0} {
812 if {$latest == {} || $cdate($p) > $latest} {
814 set latest $cdate($p)
824 puts "ERROR: none of the pending commits can be done yet:"
832 # If we are reducing, put in a null entry
833 if {$todol < $oldnlines} {
834 if {$nullentry >= 0} {
837 && [lindex $oldtodo $i] == [lindex $todo $i]} {
847 set todo [linsert $todo $i {}]
856 proc drawcommit {id} {
857 global phase todo nchildren datemode nextupdate
860 if {$phase != "incrdraw"} {
867 updatetodo 0 $datemode
869 if {$nchildren($id) == 0} {
871 lappend startcommits $id
874 set level [decidenext]
875 if {$id != [lindex $todo $level]} {
880 drawcommitline $level
881 if {[updatetodo $level $datemode]} {
882 set level [decidenext]
884 set id [lindex $todo $level]
885 if {![info exists commitlisted($id)]} {
888 if {[clock clicks -milliseconds] >= $nextupdate} {
896 proc finishcommits {} {
899 global ctext maincursor textcursor
901 if {$phase != "incrdraw"} {
903 $canv create text 3 3 -anchor nw -text "No commits selected" \
904 -font $mainfont -tags textitems
909 set level [decidenext]
910 drawrest $level [llength $startcommits]
911 . config -cursor $maincursor
912 $ctext config -cursor $textcursor
916 global nextupdate startmsecs startcommits todo
918 if {$startcommits == {}} return
919 set startmsecs [clock clicks -milliseconds]
920 set nextupdate [expr $startmsecs + 100]
922 set todo [lindex $startcommits 0]
926 proc drawrest {level startix} {
927 global phase stopped redisplaying selectedline
928 global datemode currentparents todo
930 global nextupdate startmsecs startcommits idline
933 set startid [lindex $startcommits $startix]
935 if {$startid != {}} {
936 set startline $idline($startid)
940 drawcommitline $level
941 set hard [updatetodo $level $datemode]
942 if {$numcommits == $startline} {
943 lappend todo $startid
946 set startid [lindex $startcommits $startix]
948 if {$startid != {}} {
949 set startline $idline($startid)
953 set level [decidenext]
954 if {$level < 0} break
957 if {[clock clicks -milliseconds] >= $nextupdate} {
963 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
964 #puts "overall $drawmsecs ms for $numcommits commits"
966 if {$stopped == 0 && [info exists selectedline]} {
967 selectline $selectedline
978 proc findmatches {f} {
979 global findtype foundstring foundstrlen
980 if {$findtype == "Regexp"} {
981 set matches [regexp -indices -all -inline $foundstring $f]
983 if {$findtype == "IgnCase"} {
984 set str [string tolower $f]
990 while {[set j [string first $foundstring $str $i]] >= 0} {
991 lappend matches [list $j [expr $j+$foundstrlen-1]]
992 set i [expr $j + $foundstrlen]
999 global findtype findloc findstring markedmatches commitinfo
1000 global numcommits lineid linehtag linentag linedtag
1001 global mainfont namefont canv canv2 canv3 selectedline
1002 global matchinglines foundstring foundstrlen
1005 set matchinglines {}
1006 set fldtypes {Headline Author Date Committer CDate Comment}
1007 if {$findtype == "IgnCase"} {
1008 set foundstring [string tolower $findstring]
1010 set foundstring $findstring
1012 set foundstrlen [string length $findstring]
1013 if {$foundstrlen == 0} return
1014 if {![info exists selectedline]} {
1017 set oldsel $selectedline
1020 for {set l 0} {$l < $numcommits} {incr l} {
1022 set info $commitinfo($id)
1024 foreach f $info ty $fldtypes {
1025 if {$findloc != "All fields" && $findloc != $ty} {
1028 set matches [findmatches $f]
1029 if {$matches == {}} continue
1031 if {$ty == "Headline"} {
1032 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1033 } elseif {$ty == "Author"} {
1034 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1035 } elseif {$ty == "Date"} {
1036 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1040 lappend matchinglines $l
1041 if {!$didsel && $l > $oldsel} {
1047 if {$matchinglines == {}} {
1049 } elseif {!$didsel} {
1050 findselectline [lindex $matchinglines 0]
1054 proc findselectline {l} {
1055 global findloc commentend ctext
1057 if {$findloc == "All fields" || $findloc == "Comments"} {
1058 # highlight the matches in the comments
1059 set f [$ctext get 1.0 $commentend]
1060 set matches [findmatches $f]
1061 foreach match $matches {
1062 set start [lindex $match 0]
1063 set end [expr [lindex $match 1] + 1]
1064 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1070 global matchinglines selectedline
1071 if {![info exists matchinglines]} {
1075 if {![info exists selectedline]} return
1076 foreach l $matchinglines {
1077 if {$l > $selectedline} {
1086 global matchinglines selectedline
1087 if {![info exists matchinglines]} {
1091 if {![info exists selectedline]} return
1093 foreach l $matchinglines {
1094 if {$l >= $selectedline} break
1098 findselectline $prev
1104 proc markmatches {canv l str tag matches font} {
1105 set bbox [$canv bbox $tag]
1106 set x0 [lindex $bbox 0]
1107 set y0 [lindex $bbox 1]
1108 set y1 [lindex $bbox 3]
1109 foreach match $matches {
1110 set start [lindex $match 0]
1111 set end [lindex $match 1]
1112 if {$start > $end} continue
1113 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1114 set xlen [font measure $font [string range $str 0 [expr $end]]]
1115 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1116 -outline {} -tags matches -fill yellow]
1121 proc unmarkmatches {} {
1122 global matchinglines
1123 allcanvs delete matches
1124 catch {unset matchinglines}
1127 proc selcanvline {x y} {
1128 global canv canvy0 ctext linespc selectedline
1129 global lineid linehtag linentag linedtag
1130 set ymax [lindex [$canv cget -scrollregion] 3]
1131 if {$ymax == {}} return
1132 set yfrac [lindex [$canv yview] 0]
1133 set y [expr {$y + $yfrac * $ymax}]
1134 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1138 if {[info exists selectedline] && $selectedline == $l} return
1143 proc selectline {l} {
1144 global canv canv2 canv3 ctext commitinfo selectedline
1145 global lineid linehtag linentag linedtag
1146 global canvy0 linespc nparents treepending
1147 global cflist treediffs currentid sha1entry
1148 global commentend seenfile idtags
1150 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1152 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1153 -tags secsel -fill [$canv cget -selectbackground]]
1155 $canv2 delete secsel
1156 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1157 -tags secsel -fill [$canv2 cget -selectbackground]]
1159 $canv3 delete secsel
1160 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1161 -tags secsel -fill [$canv3 cget -selectbackground]]
1163 set y [expr {$canvy0 + $l * $linespc}]
1164 set ymax [lindex [$canv cget -scrollregion] 3]
1165 set ytop [expr {$y - $linespc - 1}]
1166 set ybot [expr {$y + $linespc + 1}]
1167 set wnow [$canv yview]
1168 set wtop [expr [lindex $wnow 0] * $ymax]
1169 set wbot [expr [lindex $wnow 1] * $ymax]
1170 set wh [expr {$wbot - $wtop}]
1172 if {$ytop < $wtop} {
1173 if {$ybot < $wtop} {
1174 set newtop [expr {$y - $wh / 2.0}]
1177 if {$newtop > $wtop - $linespc} {
1178 set newtop [expr {$wtop - $linespc}]
1181 } elseif {$ybot > $wbot} {
1182 if {$ytop > $wbot} {
1183 set newtop [expr {$y - $wh / 2.0}]
1185 set newtop [expr {$ybot - $wh}]
1186 if {$newtop < $wtop + $linespc} {
1187 set newtop [expr {$wtop + $linespc}]
1191 if {$newtop != $wtop} {
1195 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1201 $sha1entry delete 0 end
1202 $sha1entry insert 0 $id
1203 $sha1entry selection from 0
1204 $sha1entry selection to end
1206 $ctext conf -state normal
1207 $ctext delete 0.0 end
1208 set info $commitinfo($id)
1209 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1210 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1211 if {[info exists idtags($id)]} {
1212 $ctext insert end "Tags:"
1213 foreach tag $idtags($id) {
1214 $ctext insert end " $tag"
1216 $ctext insert end "\n"
1218 $ctext insert end "\n"
1219 $ctext insert end [lindex $info 5]
1220 $ctext insert end "\n"
1221 $ctext tag delete Comments
1222 $ctext tag remove found 1.0 end
1223 $ctext conf -state disabled
1224 set commentend [$ctext index "end - 1c"]
1226 $cflist delete 0 end
1227 if {$nparents($id) == 1} {
1228 if {![info exists treediffs($id)]} {
1229 if {![info exists treepending]} {
1236 catch {unset seenfile}
1239 proc selnextline {dir} {
1241 if {![info exists selectedline]} return
1242 set l [expr $selectedline + $dir]
1247 proc addtocflist {id} {
1248 global currentid treediffs cflist treepending
1249 if {$id != $currentid} {
1250 gettreediffs $currentid
1253 $cflist insert end "All files"
1254 foreach f $treediffs($currentid) {
1255 $cflist insert end $f
1260 proc gettreediffs {id} {
1261 global treediffs parents treepending
1263 set treediffs($id) {}
1264 set p [lindex $parents($id) 0]
1265 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1266 fconfigure $gdtf -blocking 0
1267 fileevent $gdtf readable "gettreediffline $gdtf $id"
1270 proc gettreediffline {gdtf id} {
1271 global treediffs treepending
1272 set n [gets $gdtf line]
1274 if {![eof $gdtf]} return
1280 set file [lindex $line 5]
1281 lappend treediffs($id) $file
1284 proc getblobdiffs {id} {
1285 global parents diffopts blobdifffd env curdifftag curtagstart
1286 global diffindex difffilestart
1287 set p [lindex $parents($id) 0]
1288 set env(GIT_DIFF_OPTS) $diffopts
1289 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1290 puts "error getting diffs: $err"
1293 fconfigure $bdf -blocking 0
1294 set blobdifffd($id) $bdf
1295 set curdifftag Comments
1298 catch {unset difffilestart}
1299 fileevent $bdf readable "getblobdiffline $bdf $id"
1302 proc getblobdiffline {bdf id} {
1303 global currentid blobdifffd ctext curdifftag curtagstart seenfile
1304 global diffnexthead diffnextnote diffindex difffilestart
1305 set n [gets $bdf line]
1309 if {$id == $currentid && $bdf == $blobdifffd($id)} {
1310 $ctext tag add $curdifftag $curtagstart end
1311 set seenfile($curdifftag) 1
1316 if {$id != $currentid || $bdf != $blobdifffd($id)} {
1319 $ctext conf -state normal
1320 if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
1321 # start of a new file
1322 $ctext insert end "\n"
1323 $ctext tag add $curdifftag $curtagstart end
1324 set seenfile($curdifftag) 1
1325 set curtagstart [$ctext index "end - 1c"]
1327 if {[info exists diffnexthead]} {
1328 set fname $diffnexthead
1329 set header "$diffnexthead ($diffnextnote)"
1332 set difffilestart($diffindex) [$ctext index "end - 1c"]
1334 set curdifftag "f:$fname"
1335 $ctext tag delete $curdifftag
1336 set l [expr {(78 - [string length $header]) / 2}]
1337 set pad [string range "----------------------------------------" 1 $l]
1338 $ctext insert end "$pad $header $pad\n" filesep
1339 } elseif {[string range $line 0 2] == "+++"} {
1340 # no need to do anything with this
1341 } elseif {[regexp {^Created: (.*) \((mode: *[0-7]*)\)} $line match fn m]} {
1342 set diffnexthead $fn
1343 set diffnextnote "created, mode $m"
1344 } elseif {[string range $line 0 8] == "Deleted: "} {
1345 set diffnexthead [string range $line 9 end]
1346 set diffnextnote "deleted"
1347 } elseif {[regexp {^diff --git a/(.*) b/} $line match fn]} {
1348 # save the filename in case the next thing is "new file mode ..."
1349 set diffnexthead $fn
1350 set diffnextnote "modified"
1351 } elseif {[regexp {^new file mode ([0-7]+)} $line match m]} {
1352 set diffnextnote "new file, mode $m"
1353 } elseif {[string range $line 0 11] == "deleted file"} {
1354 set diffnextnote "deleted"
1355 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1356 $line match f1l f1c f2l f2c rest]} {
1357 $ctext insert end "\t" hunksep
1358 $ctext insert end " $f1l " d0 " $f2l " d1
1359 $ctext insert end " $rest \n" hunksep
1361 set x [string range $line 0 0]
1362 if {$x == "-" || $x == "+"} {
1363 set tag [expr {$x == "+"}]
1364 set line [string range $line 1 end]
1365 $ctext insert end "$line\n" d$tag
1366 } elseif {$x == " "} {
1367 set line [string range $line 1 end]
1368 $ctext insert end "$line\n"
1369 } elseif {$x == "\\"} {
1370 # e.g. "\ No newline at end of file"
1371 $ctext insert end "$line\n" filesep
1373 # Something else we don't recognize
1374 if {$curdifftag != "Comments"} {
1375 $ctext insert end "\n"
1376 $ctext tag add $curdifftag $curtagstart end
1377 set seenfile($curdifftag) 1
1378 set curtagstart [$ctext index "end - 1c"]
1379 set curdifftag Comments
1381 $ctext insert end "$line\n" filesep
1384 $ctext conf -state disabled
1388 global difffilestart ctext
1389 set here [$ctext index @0,0]
1390 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1391 if {[$ctext compare $difffilestart($i) > $here]} {
1392 $ctext yview $difffilestart($i)
1398 proc listboxsel {} {
1399 global ctext cflist currentid treediffs seenfile
1400 if {![info exists currentid]} return
1401 set sel [$cflist curselection]
1402 if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
1404 $ctext tag conf Comments -elide 0
1405 foreach f $treediffs($currentid) {
1406 if [info exists seenfile(f:$f)] {
1407 $ctext tag conf "f:$f" -elide 0
1411 # just show selected files
1412 $ctext tag conf Comments -elide 1
1414 foreach f $treediffs($currentid) {
1415 set elide [expr {[lsearch -exact $sel $i] < 0}]
1416 if [info exists seenfile(f:$f)] {
1417 $ctext tag conf "f:$f" -elide $elide
1425 global linespc charspc canvx0 canvy0 mainfont
1426 set linespc [font metrics $mainfont -linespace]
1427 set charspc [font measure $mainfont "m"]
1428 set canvy0 [expr 3 + 0.5 * $linespc]
1429 set canvx0 [expr 3 + 0.5 * $linespc]
1433 global selectedline stopped redisplaying phase
1434 if {$stopped > 1} return
1435 if {$phase == "getcommits"} return
1437 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1444 proc incrfont {inc} {
1445 global mainfont namefont textfont selectedline ctext canv phase
1446 global stopped entries
1448 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1449 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1450 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1452 $ctext conf -font $textfont
1453 $ctext tag conf filesep -font [concat $textfont bold]
1454 foreach e $entries {
1455 $e conf -font $mainfont
1457 if {$phase == "getcommits"} {
1458 $canv itemconf textitems -font $mainfont
1463 proc sha1change {n1 n2 op} {
1464 global sha1string currentid sha1but
1465 if {$sha1string == {}
1466 || ([info exists currentid] && $sha1string == $currentid)} {
1471 if {[$sha1but cget -state] == $state} return
1472 if {$state == "normal"} {
1473 $sha1but conf -state normal -relief raised -text "Goto: "
1475 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1479 proc gotocommit {} {
1480 global sha1string currentid idline tagids
1481 if {$sha1string == {}
1482 || ([info exists currentid] && $sha1string == $currentid)} return
1483 if {[info exists tagids($sha1string)]} {
1484 set id $tagids($sha1string)
1486 set id [string tolower $sha1string]
1488 if {[info exists idline($id)]} {
1489 selectline $idline($id)
1492 if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
1497 error_popup "$type $sha1string is not known"
1500 proc linemenu {x y id} {
1501 global linectxmenu linemenuid
1503 $linectxmenu post $x $y
1506 proc lineselect {} {
1507 global linemenuid idline
1508 if {[info exists linemenuid] && [info exists idline($linemenuid)]} {
1509 selectline $idline($linemenuid)
1513 proc lineenter {x y id} {
1514 global hoverx hovery hoverid hovertimer
1515 global commitinfo canv
1517 if {![info exists commitinfo($id)]} return
1521 if {[info exists hovertimer]} {
1522 after cancel $hovertimer
1524 set hovertimer [after 500 linehover]
1528 proc linemotion {x y id} {
1529 global hoverx hovery hoverid hovertimer
1531 if {[info exists hoverid] && $id == $hoverid} {
1534 if {[info exists hovertimer]} {
1535 after cancel $hovertimer
1537 set hovertimer [after 500 linehover]
1541 proc lineleave {id} {
1542 global hoverid hovertimer canv
1544 if {[info exists hoverid] && $id == $hoverid} {
1546 if {[info exists hovertimer]} {
1547 after cancel $hovertimer
1555 global hoverx hovery hoverid hovertimer
1556 global canv linespc lthickness
1557 global commitinfo mainfont
1559 set text [lindex $commitinfo($hoverid) 0]
1560 set ymax [lindex [$canv cget -scrollregion] 3]
1561 if {$ymax == {}} return
1562 set yfrac [lindex [$canv yview] 0]
1563 set x [expr {$hoverx + 2 * $linespc}]
1564 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
1565 set x0 [expr {$x - 2 * $lthickness}]
1566 set y0 [expr {$y - 2 * $lthickness}]
1567 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
1568 set y1 [expr {$y + $linespc + 2 * $lthickness}]
1569 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
1570 -fill \#ffff80 -outline black -width 1 -tags hover]
1572 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
1585 set diffopts "-U 5 -p"
1587 set mainfont {Helvetica 9}
1588 set textfont {Courier 9}
1590 set colors {green red blue magenta darkgrey brown orange}
1592 catch {source ~/.gitk}
1594 set namefont $mainfont
1596 lappend namefont bold
1601 switch -regexp -- $arg {
1603 "^-b" { set boldnames 1 }
1604 "^-d" { set datemode 1 }
1606 lappend revtreeargs $arg
1611 set noreadobj [catch {load libreadobj.so.0.0}]
1618 getcommits $revtreeargs