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
19 set startmsecs [clock clicks -milliseconds]
20 set nextupdate [expr $startmsecs + 100]
22 set parse_args [concat --default HEAD --merge-order $rargs]
23 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
28 set parsed_args [concat --merge-order $rargs]
31 set commfd [open "|git-rev-list $parsed_args" r]
33 puts stderr "Error executing git-rev-list: $err"
37 fconfigure $commfd -blocking 0
38 fileevent $commfd readable "getcommitline $commfd"
40 $canv create text 3 3 -anchor nw -text "Reading commits..." \
41 -font $mainfont -tags textitems
42 . config -cursor watch
43 $ctext config -cursor watch
46 proc getcommitline {commfd} {
47 global commits parents cdate children nchildren
48 global commitlisted phase commitinfo nextupdate
49 global stopped redisplaying nlines
51 set n [gets $commfd line]
53 if {![eof $commfd]} return
54 # this works around what is apparently a bug in Tcl...
55 fconfigure $commfd -blocking 1
56 if {![catch {close $commfd} err]} {
57 after idle finishcommits
60 if {[string range $err 0 4] == "usage"} {
62 {Gitk: error reading commits: bad arguments to git-rev-list.
63 (Note: arguments to gitk are passed to git-rev-list
64 to allow selection of commits to be displayed.)}
66 set err "Error reading commits: $err"
72 if {![regexp {^[0-9a-f]{40}$} $line id]} {
73 error_popup "Can't parse git-rev-list output: {$line}"
77 set commitlisted($id) 1
78 if {![info exists commitinfo($id)]} {
81 foreach p $parents($id) {
82 if {[info exists commitlisted($p)]} {
83 puts "oops, parent $p before child $id"
87 if {[clock clicks -milliseconds] >= $nextupdate} {
90 while {$redisplaying} {
94 set phase "getcommits"
98 if {[clock clicks -milliseconds] >= $nextupdate} {
107 global commfd nextupdate
110 fileevent $commfd readable {}
112 fileevent $commfd readable "getcommitline $commfd"
115 proc readcommit {id} {
116 global commitinfo children nchildren parents nparents cdate ncleft
126 if {![info exists nchildren($id)]} {
134 if [catch {set contents [exec git-cat-file commit $id]}] return
136 if [catch {set x [readobj $id]}] return
137 if {[lindex $x 0] != "commit"} return
138 set contents [lindex $x 1]
140 foreach line [split $contents "\n"] {
145 set tag [lindex $line 0]
146 if {$tag == "parent"} {
147 set p [lindex $line 1]
148 if {![info exists nchildren($p)]} {
153 lappend parents($id) $p
155 # sometimes we get a commit that lists a parent twice...
156 if {[lsearch -exact $children($p) $id] < 0} {
157 lappend children($p) $id
161 } elseif {$tag == "author"} {
162 set x [expr {[llength $line] - 2}]
163 set audate [lindex $line $x]
164 set auname [lrange $line 1 [expr {$x - 1}]]
165 } elseif {$tag == "committer"} {
166 set x [expr {[llength $line] - 2}]
167 set comdate [lindex $line $x]
168 set comname [lrange $line 1 [expr {$x - 1}]]
172 if {$comment == {}} {
181 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
183 if {$comdate != {}} {
184 set cdate($id) $comdate
185 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
187 set commitinfo($id) [list $headline $auname $audate \
188 $comname $comdate $comment]
192 global tagids idtags headids idheads
193 set tags [glob -nocomplain -types f .git/refs/tags/*]
198 if {[regexp {^[0-9a-f]{40}} $line id]} {
199 set direct [file tail $f]
200 set tagids($direct) $id
201 lappend idtags($id) $direct
202 set contents [split [exec git-cat-file tag $id] "\n"]
206 foreach l $contents {
208 switch -- [lindex $l 0] {
209 "object" {set obj [lindex $l 1]}
210 "type" {set type [lindex $l 1]}
211 "tag" {set tag [string range $l 4 end]}
214 if {$obj != {} && $type == "commit" && $tag != {}} {
215 set tagids($tag) $obj
216 lappend idtags($obj) $tag
222 set heads [glob -nocomplain -types f .git/refs/heads/*]
226 set line [read $fd 40]
227 if {[regexp {^[0-9a-f]{40}} $line id]} {
228 set head [file tail $f]
229 set headids($head) $line
230 lappend idheads($line) $head
237 proc error_popup msg {
241 message $w.m -text $msg -justify center -aspect 400
242 pack $w.m -side top -fill x -padx 20 -pady 20
243 button $w.ok -text OK -command "destroy $w"
244 pack $w.ok -side bottom -fill x
245 bind $w <Visibility> "grab $w; focus $w"
250 global canv canv2 canv3 linespc charspc ctext cflist textfont
251 global findtype findloc findstring fstring geometry
252 global entries sha1entry sha1string sha1but
253 global maincursor textcursor
257 .bar add cascade -label "File" -menu .bar.file
259 .bar.file add command -label "Quit" -command doquit
261 .bar add cascade -label "Help" -menu .bar.help
262 .bar.help add command -label "About gitk" -command about
263 . configure -menu .bar
265 if {![info exists geometry(canv1)]} {
266 set geometry(canv1) [expr 45 * $charspc]
267 set geometry(canv2) [expr 30 * $charspc]
268 set geometry(canv3) [expr 15 * $charspc]
269 set geometry(canvh) [expr 25 * $linespc + 4]
270 set geometry(ctextw) 80
271 set geometry(ctexth) 30
272 set geometry(cflistw) 30
274 panedwindow .ctop -orient vertical
275 if {[info exists geometry(width)]} {
276 .ctop conf -width $geometry(width) -height $geometry(height)
277 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
278 set geometry(ctexth) [expr {($texth - 8) /
279 [font metrics $textfont -linespace]}]
283 pack .ctop.top.bar -side bottom -fill x
284 set cscroll .ctop.top.csb
285 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
286 pack $cscroll -side right -fill y
287 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
288 pack .ctop.top.clist -side top -fill both -expand 1
290 set canv .ctop.top.clist.canv
291 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
293 -yscrollincr $linespc -yscrollcommand "$cscroll set"
294 .ctop.top.clist add $canv
295 set canv2 .ctop.top.clist.canv2
296 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
297 -bg white -bd 0 -yscrollincr $linespc
298 .ctop.top.clist add $canv2
299 set canv3 .ctop.top.clist.canv3
300 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
301 -bg white -bd 0 -yscrollincr $linespc
302 .ctop.top.clist add $canv3
303 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
305 set sha1entry .ctop.top.bar.sha1
306 set entries $sha1entry
307 set sha1but .ctop.top.bar.sha1label
308 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
309 -command gotocommit -width 8
310 $sha1but conf -disabledforeground [$sha1but cget -foreground]
311 pack .ctop.top.bar.sha1label -side left
312 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
313 trace add variable sha1string write sha1change
314 pack $sha1entry -side left -pady 2
315 button .ctop.top.bar.findbut -text "Find" -command dofind
316 pack .ctop.top.bar.findbut -side left
318 set fstring .ctop.top.bar.findstring
319 lappend entries $fstring
320 entry $fstring -width 30 -font $textfont -textvariable findstring
321 pack $fstring -side left -expand 1 -fill x
323 tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
324 set findloc "All fields"
325 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
326 Comments Author Committer
327 pack .ctop.top.bar.findloc -side right
328 pack .ctop.top.bar.findtype -side right
330 panedwindow .ctop.cdet -orient horizontal
332 frame .ctop.cdet.left
333 set ctext .ctop.cdet.left.ctext
334 text $ctext -bg white -state disabled -font $textfont \
335 -width $geometry(ctextw) -height $geometry(ctexth) \
336 -yscrollcommand ".ctop.cdet.left.sb set"
337 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
338 pack .ctop.cdet.left.sb -side right -fill y
339 pack $ctext -side left -fill both -expand 1
340 .ctop.cdet add .ctop.cdet.left
342 $ctext tag conf filesep -font [concat $textfont bold]
343 $ctext tag conf hunksep -back blue -fore white
344 $ctext tag conf d0 -back "#ff8080"
345 $ctext tag conf d1 -back green
346 $ctext tag conf found -back yellow
348 frame .ctop.cdet.right
349 set cflist .ctop.cdet.right.cfiles
350 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
351 -yscrollcommand ".ctop.cdet.right.sb set"
352 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
353 pack .ctop.cdet.right.sb -side right -fill y
354 pack $cflist -side left -fill both -expand 1
355 .ctop.cdet add .ctop.cdet.right
356 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
358 pack .ctop -side top -fill both -expand 1
360 bindall <1> {selcanvline %x %y}
361 bindall <B1-Motion> {selcanvline %x %y}
362 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
363 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
364 bindall <2> "allcanvs scan mark 0 %y"
365 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
366 bind . <Key-Up> "selnextline -1"
367 bind . <Key-Down> "selnextline 1"
368 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
369 bind . <Key-Next> "allcanvs yview scroll 1 pages"
370 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
371 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
372 bindkey <Key-space> "$ctext yview scroll 1 pages"
373 bindkey p "selnextline -1"
374 bindkey n "selnextline 1"
375 bindkey b "$ctext yview scroll -1 pages"
376 bindkey d "$ctext yview scroll 18 units"
377 bindkey u "$ctext yview scroll -18 units"
381 bind . <Control-q> doquit
382 bind . <Control-f> dofind
383 bind . <Control-g> findnext
384 bind . <Control-r> findprev
385 bind . <Control-equal> {incrfont 1}
386 bind . <Control-KP_Add> {incrfont 1}
387 bind . <Control-minus> {incrfont -1}
388 bind . <Control-KP_Subtract> {incrfont -1}
389 bind $cflist <<ListboxSelect>> listboxsel
390 bind . <Destroy> {savestuff %W}
391 bind . <Button-1> "click %W"
392 bind $fstring <Key-Return> dofind
393 bind $sha1entry <Key-Return> gotocommit
395 set maincursor [. cget -cursor]
396 set textcursor [$ctext cget -cursor]
398 set linectxmenu .linectxmenu
399 menu $linectxmenu -tearoff 0
400 $linectxmenu add command -label "Select" -command lineselect
403 # when we make a key binding for the toplevel, make sure
404 # it doesn't get triggered when that key is pressed in the
405 # find string entry widget.
406 proc bindkey {ev script} {
409 set escript [bind Entry $ev]
410 if {$escript == {}} {
411 set escript [bind Entry <Key>]
414 bind $e $ev "$escript; break"
418 # set the focus back to the toplevel for any click outside
429 global canv canv2 canv3 ctext cflist mainfont textfont
431 if {$stuffsaved} return
432 if {![winfo viewable .]} return
434 set f [open "~/.gitk-new" w]
435 puts $f "set mainfont {$mainfont}"
436 puts $f "set textfont {$textfont}"
437 puts $f "set geometry(width) [winfo width .ctop]"
438 puts $f "set geometry(height) [winfo height .ctop]"
439 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
440 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
441 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
442 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
443 set wid [expr {([winfo width $ctext] - 8) \
444 / [font measure $textfont "0"]}]
445 puts $f "set geometry(ctextw) $wid"
446 set wid [expr {([winfo width $cflist] - 11) \
447 / [font measure [$cflist cget -font] "0"]}]
448 puts $f "set geometry(cflistw) $wid"
450 file rename -force "~/.gitk-new" "~/.gitk"
455 proc resizeclistpanes {win w} {
457 if [info exists oldwidth($win)] {
458 set s0 [$win sash coord 0]
459 set s1 [$win sash coord 1]
461 set sash0 [expr {int($w/2 - 2)}]
462 set sash1 [expr {int($w*5/6 - 2)}]
464 set factor [expr {1.0 * $w / $oldwidth($win)}]
465 set sash0 [expr {int($factor * [lindex $s0 0])}]
466 set sash1 [expr {int($factor * [lindex $s1 0])}]
470 if {$sash1 < $sash0 + 20} {
471 set sash1 [expr $sash0 + 20]
473 if {$sash1 > $w - 10} {
474 set sash1 [expr $w - 10]
475 if {$sash0 > $sash1 - 20} {
476 set sash0 [expr $sash1 - 20]
480 $win sash place 0 $sash0 [lindex $s0 1]
481 $win sash place 1 $sash1 [lindex $s1 1]
483 set oldwidth($win) $w
486 proc resizecdetpanes {win w} {
488 if [info exists oldwidth($win)] {
489 set s0 [$win sash coord 0]
491 set sash0 [expr {int($w*3/4 - 2)}]
493 set factor [expr {1.0 * $w / $oldwidth($win)}]
494 set sash0 [expr {int($factor * [lindex $s0 0])}]
498 if {$sash0 > $w - 15} {
499 set sash0 [expr $w - 15]
502 $win sash place 0 $sash0 [lindex $s0 1]
504 set oldwidth($win) $w
508 global canv canv2 canv3
514 proc bindall {event action} {
515 global canv canv2 canv3
516 bind $canv $event $action
517 bind $canv2 $event $action
518 bind $canv3 $event $action
523 if {[winfo exists $w]} {
528 wm title $w "About gitk"
532 Copyright © 2005 Paul Mackerras
534 Use and redistribute under the terms of the GNU General Public License
536 (CVS $Revision: 1.24 $)} \
537 -justify center -aspect 400
538 pack $w.m -side top -fill x -padx 20 -pady 20
539 button $w.ok -text Close -command "destroy $w"
540 pack $w.ok -side bottom
543 proc assigncolor {id} {
544 global commitinfo colormap commcolors colors nextcolor
545 global parents nparents children nchildren
546 if [info exists colormap($id)] return
547 set ncolors [llength $colors]
548 if {$nparents($id) == 1 && $nchildren($id) == 1} {
549 set child [lindex $children($id) 0]
550 if {[info exists colormap($child)]
551 && $nparents($child) == 1} {
552 set colormap($id) $colormap($child)
557 foreach child $children($id) {
558 if {[info exists colormap($child)]
559 && [lsearch -exact $badcolors $colormap($child)] < 0} {
560 lappend badcolors $colormap($child)
562 if {[info exists parents($child)]} {
563 foreach p $parents($child) {
564 if {[info exists colormap($p)]
565 && [lsearch -exact $badcolors $colormap($p)] < 0} {
566 lappend badcolors $colormap($p)
571 if {[llength $badcolors] >= $ncolors} {
574 for {set i 0} {$i <= $ncolors} {incr i} {
575 set c [lindex $colors $nextcolor]
576 if {[incr nextcolor] >= $ncolors} {
579 if {[lsearch -exact $badcolors $c]} break
585 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
587 global nchildren ncleft
594 set lthickness [expr {int($linespc / 9) + 1}]
596 foreach id [array names nchildren] {
597 set ncleft($id) $nchildren($id)
601 proc bindline {t id} {
604 $canv bind $t <Button-3> "linemenu %X %Y $id"
605 $canv bind $t <Enter> "lineenter %x %y $id"
606 $canv bind $t <Motion> "linemotion %x %y $id"
607 $canv bind $t <Leave> "lineleave $id"
610 proc drawcommitline {level} {
611 global parents children nparents nchildren todo
612 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
613 global datemode cdate
614 global lineid linehtag linentag linedtag commitinfo
615 global colormap numcommits currentparents dupparents
616 global oldlevel oldnlines oldtodo
617 global idtags idline idheads
618 global lineno lthickness glines
623 set id [lindex $todo $level]
624 set lineid($lineno) $id
625 set idline($id) $lineno
626 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
627 if {![info exists commitinfo($id)]} {
629 if {![info exists commitinfo($id)]} {
630 set commitinfo($id) {"No commit information available"}
634 set currentparents {}
636 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
637 foreach p $parents($id) {
638 if {[lsearch -exact $currentparents $p] < 0} {
639 lappend currentparents $p
641 # remember that this parent was listed twice
642 lappend dupparents $p
646 set x [expr $canvx0 + $level * $linespc]
648 set canvy [expr $canvy + $linespc]
649 allcanvs conf -scrollregion \
650 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
651 if {[info exists glines($id)]} {
652 lappend glines($id) $x $y1
653 set t [$canv create line $glines($id) \
654 -width $lthickness -fill $colormap($id)]
658 set orad [expr {$linespc / 3}]
659 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
660 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
661 -fill $ofill -outline black -width 1]
663 set xt [expr $canvx0 + [llength $todo] * $linespc]
664 if {$nparents($id) > 2} {
665 set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
669 if {[info exists idtags($id)]} {
670 set marks $idtags($id)
671 set ntags [llength $marks]
673 if {[info exists idheads($id)]} {
674 set marks [concat $marks $idheads($id)]
677 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
678 set yt [expr $y1 - 0.5 * $linespc]
679 set yb [expr $yt + $linespc - 1]
683 set wid [font measure $mainfont $tag]
686 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
688 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
689 -width $lthickness -fill black]
691 foreach tag $marks x $xvals wid $wvals {
692 set xl [expr $x + $delta]
693 set xr [expr $x + $delta + $wid + $lthickness]
694 if {[incr ntags -1] >= 0} {
696 $canv create polygon $x [expr $yt + $delta] $xl $yt\
697 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
698 -width 1 -outline black -fill yellow
701 set xl [expr $xl - $delta/2]
702 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
703 -width 1 -outline black -fill green
705 $canv create text $xl $y1 -anchor w -text $tag \
709 set headline [lindex $commitinfo($id) 0]
710 set name [lindex $commitinfo($id) 1]
711 set date [lindex $commitinfo($id) 2]
712 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
713 -text $headline -font $mainfont ]
714 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
715 -text $name -font $namefont]
716 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
717 -text $date -font $mainfont]
720 proc updatetodo {level noshortcut} {
721 global datemode currentparents ncleft todo
722 global glines oldlevel oldtodo oldnlines
723 global canvx0 canvy linespc glines
726 foreach p $currentparents {
727 if {![info exists commitinfo($p)]} {
731 set x [expr $canvx0 + $level * $linespc]
732 set y [expr $canvy - $linespc]
733 if {!$noshortcut && [llength $currentparents] == 1} {
734 set p [lindex $currentparents 0]
735 if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
737 set glines($p) [list $x $y]
738 set todo [lreplace $todo $level $level $p]
745 set oldnlines [llength $todo]
746 set todo [lreplace $todo $level $level]
748 foreach p $currentparents {
750 set k [lsearch -exact $todo $p]
753 set todo [linsert $todo $i $p]
761 global canv glines canvx0 canvy linespc
762 global oldlevel oldtodo todo currentparents dupparents
763 global lthickness linespc canvy colormap
765 set y1 [expr $canvy - $linespc]
768 foreach id $oldtodo {
770 if {$id == {}} continue
771 set xi [expr {$canvx0 + $i * $linespc}]
772 if {$i == $oldlevel} {
773 foreach p $currentparents {
774 set j [lsearch -exact $todo $p]
775 set coords [list $xi $y1]
776 set xj [expr {$canvx0 + $j * $linespc}]
778 lappend coords [expr $xj + $linespc] $y1
779 } elseif {$j > $i + 1} {
780 lappend coords [expr $xj - $linespc] $y1
782 if {[lsearch -exact $dupparents $p] >= 0} {
783 # draw a double-width line to indicate the doubled parent
784 lappend coords $xj $y2
785 set t [$canv create line $coords \
786 -width [expr 2*$lthickness] -fill $colormap($p)]
789 if {![info exists glines($p)]} {
790 set glines($p) [list $xj $y2]
793 # normal case, no parent duplicated
794 if {![info exists glines($p)]} {
796 lappend coords $xj $y2
798 set glines($p) $coords
800 lappend coords $xj $y2
801 set t [$canv create line $coords \
802 -width $lthickness -fill $colormap($p)]
808 } elseif {[lindex $todo $i] != $id} {
809 set j [lsearch -exact $todo $id]
810 set xj [expr {$canvx0 + $j * $linespc}]
811 lappend glines($id) $xi $y1 $xj $y2
817 global parents children nchildren ncleft todo
818 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
819 global datemode cdate
820 global lineid linehtag linentag linedtag commitinfo
821 global currentparents oldlevel oldnlines oldtodo
822 global lineno lthickness
824 # remove the null entry if present
825 set nullentry [lsearch -exact $todo {}]
826 if {$nullentry >= 0} {
827 set todo [lreplace $todo $nullentry $nullentry]
830 # choose which one to do next time around
831 set todol [llength $todo]
834 for {set k $todol} {[incr k -1] >= 0} {} {
835 set p [lindex $todo $k]
836 if {$ncleft($p) == 0} {
838 if {$latest == {} || $cdate($p) > $latest} {
840 set latest $cdate($p)
850 puts "ERROR: none of the pending commits can be done yet:"
858 # If we are reducing, put in a null entry
859 if {$todol < $oldnlines} {
860 if {$nullentry >= 0} {
863 && [lindex $oldtodo $i] == [lindex $todo $i]} {
873 set todo [linsert $todo $i {}]
882 proc drawcommit {id} {
883 global phase todo nchildren datemode nextupdate
886 if {$phase != "incrdraw"} {
893 updatetodo 0 $datemode
895 if {$nchildren($id) == 0} {
897 lappend startcommits $id
900 set level [decidenext]
901 if {$id != [lindex $todo $level]} {
906 drawcommitline $level
907 if {[updatetodo $level $datemode]} {
908 set level [decidenext]
910 set id [lindex $todo $level]
911 if {![info exists commitlisted($id)]} {
914 if {[clock clicks -milliseconds] >= $nextupdate} {
922 proc finishcommits {} {
925 global ctext maincursor textcursor
927 if {$phase != "incrdraw"} {
929 $canv create text 3 3 -anchor nw -text "No commits selected" \
930 -font $mainfont -tags textitems
935 set level [decidenext]
936 drawrest $level [llength $startcommits]
937 . config -cursor $maincursor
938 $ctext config -cursor $textcursor
942 global nextupdate startmsecs startcommits todo
944 if {$startcommits == {}} return
945 set startmsecs [clock clicks -milliseconds]
946 set nextupdate [expr $startmsecs + 100]
948 set todo [lindex $startcommits 0]
952 proc drawrest {level startix} {
953 global phase stopped redisplaying selectedline
954 global datemode currentparents todo
956 global nextupdate startmsecs startcommits idline
960 set startid [lindex $startcommits $startix]
962 if {$startid != {}} {
963 set startline $idline($startid)
967 drawcommitline $level
968 set hard [updatetodo $level $datemode]
969 if {$numcommits == $startline} {
970 lappend todo $startid
973 set startid [lindex $startcommits $startix]
975 if {$startid != {}} {
976 set startline $idline($startid)
980 set level [decidenext]
981 if {$level < 0} break
984 if {[clock clicks -milliseconds] >= $nextupdate} {
991 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
992 #puts "overall $drawmsecs ms for $numcommits commits"
994 if {$stopped == 0 && [info exists selectedline]} {
995 selectline $selectedline
1006 proc findmatches {f} {
1007 global findtype foundstring foundstrlen
1008 if {$findtype == "Regexp"} {
1009 set matches [regexp -indices -all -inline $foundstring $f]
1011 if {$findtype == "IgnCase"} {
1012 set str [string tolower $f]
1018 while {[set j [string first $foundstring $str $i]] >= 0} {
1019 lappend matches [list $j [expr $j+$foundstrlen-1]]
1020 set i [expr $j + $foundstrlen]
1027 global findtype findloc findstring markedmatches commitinfo
1028 global numcommits lineid linehtag linentag linedtag
1029 global mainfont namefont canv canv2 canv3 selectedline
1030 global matchinglines foundstring foundstrlen
1033 set matchinglines {}
1034 set fldtypes {Headline Author Date Committer CDate Comment}
1035 if {$findtype == "IgnCase"} {
1036 set foundstring [string tolower $findstring]
1038 set foundstring $findstring
1040 set foundstrlen [string length $findstring]
1041 if {$foundstrlen == 0} return
1042 if {![info exists selectedline]} {
1045 set oldsel $selectedline
1048 for {set l 0} {$l < $numcommits} {incr l} {
1050 set info $commitinfo($id)
1052 foreach f $info ty $fldtypes {
1053 if {$findloc != "All fields" && $findloc != $ty} {
1056 set matches [findmatches $f]
1057 if {$matches == {}} continue
1059 if {$ty == "Headline"} {
1060 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1061 } elseif {$ty == "Author"} {
1062 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1063 } elseif {$ty == "Date"} {
1064 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1068 lappend matchinglines $l
1069 if {!$didsel && $l > $oldsel} {
1075 if {$matchinglines == {}} {
1077 } elseif {!$didsel} {
1078 findselectline [lindex $matchinglines 0]
1082 proc findselectline {l} {
1083 global findloc commentend ctext
1085 if {$findloc == "All fields" || $findloc == "Comments"} {
1086 # highlight the matches in the comments
1087 set f [$ctext get 1.0 $commentend]
1088 set matches [findmatches $f]
1089 foreach match $matches {
1090 set start [lindex $match 0]
1091 set end [expr [lindex $match 1] + 1]
1092 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1098 global matchinglines selectedline
1099 if {![info exists matchinglines]} {
1103 if {![info exists selectedline]} return
1104 foreach l $matchinglines {
1105 if {$l > $selectedline} {
1114 global matchinglines selectedline
1115 if {![info exists matchinglines]} {
1119 if {![info exists selectedline]} return
1121 foreach l $matchinglines {
1122 if {$l >= $selectedline} break
1126 findselectline $prev
1132 proc markmatches {canv l str tag matches font} {
1133 set bbox [$canv bbox $tag]
1134 set x0 [lindex $bbox 0]
1135 set y0 [lindex $bbox 1]
1136 set y1 [lindex $bbox 3]
1137 foreach match $matches {
1138 set start [lindex $match 0]
1139 set end [lindex $match 1]
1140 if {$start > $end} continue
1141 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1142 set xlen [font measure $font [string range $str 0 [expr $end]]]
1143 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1144 -outline {} -tags matches -fill yellow]
1149 proc unmarkmatches {} {
1150 global matchinglines
1151 allcanvs delete matches
1152 catch {unset matchinglines}
1155 proc selcanvline {x y} {
1156 global canv canvy0 ctext linespc selectedline
1157 global lineid linehtag linentag linedtag
1158 set ymax [lindex [$canv cget -scrollregion] 3]
1159 if {$ymax == {}} return
1160 set yfrac [lindex [$canv yview] 0]
1161 set y [expr {$y + $yfrac * $ymax}]
1162 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1166 if {[info exists selectedline] && $selectedline == $l} return
1171 proc selectline {l} {
1172 global canv canv2 canv3 ctext commitinfo selectedline
1173 global lineid linehtag linentag linedtag
1174 global canvy0 linespc nparents treepending
1175 global cflist treediffs currentid sha1entry
1176 global commentend seenfile idtags
1178 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1180 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1181 -tags secsel -fill [$canv cget -selectbackground]]
1183 $canv2 delete secsel
1184 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1185 -tags secsel -fill [$canv2 cget -selectbackground]]
1187 $canv3 delete secsel
1188 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1189 -tags secsel -fill [$canv3 cget -selectbackground]]
1191 set y [expr {$canvy0 + $l * $linespc}]
1192 set ymax [lindex [$canv cget -scrollregion] 3]
1193 set ytop [expr {$y - $linespc - 1}]
1194 set ybot [expr {$y + $linespc + 1}]
1195 set wnow [$canv yview]
1196 set wtop [expr [lindex $wnow 0] * $ymax]
1197 set wbot [expr [lindex $wnow 1] * $ymax]
1198 set wh [expr {$wbot - $wtop}]
1200 if {$ytop < $wtop} {
1201 if {$ybot < $wtop} {
1202 set newtop [expr {$y - $wh / 2.0}]
1205 if {$newtop > $wtop - $linespc} {
1206 set newtop [expr {$wtop - $linespc}]
1209 } elseif {$ybot > $wbot} {
1210 if {$ytop > $wbot} {
1211 set newtop [expr {$y - $wh / 2.0}]
1213 set newtop [expr {$ybot - $wh}]
1214 if {$newtop < $wtop + $linespc} {
1215 set newtop [expr {$wtop + $linespc}]
1219 if {$newtop != $wtop} {
1223 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1229 $sha1entry delete 0 end
1230 $sha1entry insert 0 $id
1231 $sha1entry selection from 0
1232 $sha1entry selection to end
1234 $ctext conf -state normal
1235 $ctext delete 0.0 end
1236 set info $commitinfo($id)
1237 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1238 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1239 if {[info exists idtags($id)]} {
1240 $ctext insert end "Tags:"
1241 foreach tag $idtags($id) {
1242 $ctext insert end " $tag"
1244 $ctext insert end "\n"
1246 $ctext insert end "\n"
1247 $ctext insert end [lindex $info 5]
1248 $ctext insert end "\n"
1249 $ctext tag delete Comments
1250 $ctext tag remove found 1.0 end
1251 $ctext conf -state disabled
1252 set commentend [$ctext index "end - 1c"]
1254 $cflist delete 0 end
1255 if {$nparents($id) == 1} {
1256 if {![info exists treediffs($id)]} {
1257 if {![info exists treepending]} {
1264 catch {unset seenfile}
1267 proc selnextline {dir} {
1269 if {![info exists selectedline]} return
1270 set l [expr $selectedline + $dir]
1275 proc addtocflist {id} {
1276 global currentid treediffs cflist treepending
1277 if {$id != $currentid} {
1278 gettreediffs $currentid
1281 $cflist insert end "All files"
1282 foreach f $treediffs($currentid) {
1283 $cflist insert end $f
1288 proc gettreediffs {id} {
1289 global treediffs parents treepending
1291 set treediffs($id) {}
1292 set p [lindex $parents($id) 0]
1293 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1294 fconfigure $gdtf -blocking 0
1295 fileevent $gdtf readable "gettreediffline $gdtf $id"
1298 proc gettreediffline {gdtf id} {
1299 global treediffs treepending
1300 set n [gets $gdtf line]
1302 if {![eof $gdtf]} return
1308 set file [lindex $line 5]
1309 lappend treediffs($id) $file
1312 proc getblobdiffs {id} {
1313 global parents diffopts blobdifffd env curdifftag curtagstart
1314 global diffindex difffilestart
1315 set p [lindex $parents($id) 0]
1316 set env(GIT_DIFF_OPTS) $diffopts
1317 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1318 puts "error getting diffs: $err"
1321 fconfigure $bdf -blocking 0
1322 set blobdifffd($id) $bdf
1323 set curdifftag Comments
1326 catch {unset difffilestart}
1327 fileevent $bdf readable "getblobdiffline $bdf $id"
1330 proc getblobdiffline {bdf id} {
1331 global currentid blobdifffd ctext curdifftag curtagstart seenfile
1332 global diffnexthead diffnextnote diffindex difffilestart
1333 set n [gets $bdf line]
1337 if {$id == $currentid && $bdf == $blobdifffd($id)} {
1338 $ctext tag add $curdifftag $curtagstart end
1339 set seenfile($curdifftag) 1
1344 if {$id != $currentid || $bdf != $blobdifffd($id)} {
1347 $ctext conf -state normal
1348 if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
1349 # start of a new file
1350 $ctext insert end "\n"
1351 $ctext tag add $curdifftag $curtagstart end
1352 set seenfile($curdifftag) 1
1353 set curtagstart [$ctext index "end - 1c"]
1355 if {[info exists diffnexthead]} {
1356 set fname $diffnexthead
1357 set header "$diffnexthead ($diffnextnote)"
1360 set difffilestart($diffindex) [$ctext index "end - 1c"]
1362 set curdifftag "f:$fname"
1363 $ctext tag delete $curdifftag
1364 set l [expr {(78 - [string length $header]) / 2}]
1365 set pad [string range "----------------------------------------" 1 $l]
1366 $ctext insert end "$pad $header $pad\n" filesep
1367 } elseif {[string range $line 0 2] == "+++"} {
1368 # no need to do anything with this
1369 } elseif {[regexp {^Created: (.*) \((mode: *[0-7]*)\)} $line match fn m]} {
1370 set diffnexthead $fn
1371 set diffnextnote "created, mode $m"
1372 } elseif {[string range $line 0 8] == "Deleted: "} {
1373 set diffnexthead [string range $line 9 end]
1374 set diffnextnote "deleted"
1375 } elseif {[regexp {^diff --git a/(.*) b/} $line match fn]} {
1376 # save the filename in case the next thing is "new file mode ..."
1377 set diffnexthead $fn
1378 set diffnextnote "modified"
1379 } elseif {[regexp {^new file mode ([0-7]+)} $line match m]} {
1380 set diffnextnote "new file, mode $m"
1381 } elseif {[string range $line 0 11] == "deleted file"} {
1382 set diffnextnote "deleted"
1383 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1384 $line match f1l f1c f2l f2c rest]} {
1385 $ctext insert end "\t" hunksep
1386 $ctext insert end " $f1l " d0 " $f2l " d1
1387 $ctext insert end " $rest \n" hunksep
1389 set x [string range $line 0 0]
1390 if {$x == "-" || $x == "+"} {
1391 set tag [expr {$x == "+"}]
1392 set line [string range $line 1 end]
1393 $ctext insert end "$line\n" d$tag
1394 } elseif {$x == " "} {
1395 set line [string range $line 1 end]
1396 $ctext insert end "$line\n"
1397 } elseif {$x == "\\"} {
1398 # e.g. "\ No newline at end of file"
1399 $ctext insert end "$line\n" filesep
1401 # Something else we don't recognize
1402 if {$curdifftag != "Comments"} {
1403 $ctext insert end "\n"
1404 $ctext tag add $curdifftag $curtagstart end
1405 set seenfile($curdifftag) 1
1406 set curtagstart [$ctext index "end - 1c"]
1407 set curdifftag Comments
1409 $ctext insert end "$line\n" filesep
1412 $ctext conf -state disabled
1416 global difffilestart ctext
1417 set here [$ctext index @0,0]
1418 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1419 if {[$ctext compare $difffilestart($i) > $here]} {
1420 $ctext yview $difffilestart($i)
1426 proc listboxsel {} {
1427 global ctext cflist currentid treediffs seenfile
1428 if {![info exists currentid]} return
1429 set sel [$cflist curselection]
1430 if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
1432 $ctext tag conf Comments -elide 0
1433 foreach f $treediffs($currentid) {
1434 if [info exists seenfile(f:$f)] {
1435 $ctext tag conf "f:$f" -elide 0
1439 # just show selected files
1440 $ctext tag conf Comments -elide 1
1442 foreach f $treediffs($currentid) {
1443 set elide [expr {[lsearch -exact $sel $i] < 0}]
1444 if [info exists seenfile(f:$f)] {
1445 $ctext tag conf "f:$f" -elide $elide
1453 global linespc charspc canvx0 canvy0 mainfont
1454 set linespc [font metrics $mainfont -linespace]
1455 set charspc [font measure $mainfont "m"]
1456 set canvy0 [expr 3 + 0.5 * $linespc]
1457 set canvx0 [expr 3 + 0.5 * $linespc]
1461 global selectedline stopped redisplaying phase
1462 if {$stopped > 1} return
1463 if {$phase == "getcommits"} return
1465 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1472 proc incrfont {inc} {
1473 global mainfont namefont textfont selectedline ctext canv phase
1474 global stopped entries
1476 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1477 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1478 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1480 $ctext conf -font $textfont
1481 $ctext tag conf filesep -font [concat $textfont bold]
1482 foreach e $entries {
1483 $e conf -font $mainfont
1485 if {$phase == "getcommits"} {
1486 $canv itemconf textitems -font $mainfont
1491 proc sha1change {n1 n2 op} {
1492 global sha1string currentid sha1but
1493 if {$sha1string == {}
1494 || ([info exists currentid] && $sha1string == $currentid)} {
1499 if {[$sha1but cget -state] == $state} return
1500 if {$state == "normal"} {
1501 $sha1but conf -state normal -relief raised -text "Goto: "
1503 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1507 proc gotocommit {} {
1508 global sha1string currentid idline tagids
1509 if {$sha1string == {}
1510 || ([info exists currentid] && $sha1string == $currentid)} return
1511 if {[info exists tagids($sha1string)]} {
1512 set id $tagids($sha1string)
1514 set id [string tolower $sha1string]
1516 if {[info exists idline($id)]} {
1517 selectline $idline($id)
1520 if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
1525 error_popup "$type $sha1string is not known"
1528 proc linemenu {x y id} {
1529 global linectxmenu linemenuid
1531 $linectxmenu post $x $y
1534 proc lineselect {} {
1535 global linemenuid idline
1536 if {[info exists linemenuid] && [info exists idline($linemenuid)]} {
1537 selectline $idline($linemenuid)
1541 proc lineenter {x y id} {
1542 global hoverx hovery hoverid hovertimer
1543 global commitinfo canv
1545 if {![info exists commitinfo($id)]} return
1549 if {[info exists hovertimer]} {
1550 after cancel $hovertimer
1552 set hovertimer [after 500 linehover]
1556 proc linemotion {x y id} {
1557 global hoverx hovery hoverid hovertimer
1559 if {[info exists hoverid] && $id == $hoverid} {
1562 if {[info exists hovertimer]} {
1563 after cancel $hovertimer
1565 set hovertimer [after 500 linehover]
1569 proc lineleave {id} {
1570 global hoverid hovertimer canv
1572 if {[info exists hoverid] && $id == $hoverid} {
1574 if {[info exists hovertimer]} {
1575 after cancel $hovertimer
1583 global hoverx hovery hoverid hovertimer
1584 global canv linespc lthickness
1585 global commitinfo mainfont
1587 set text [lindex $commitinfo($hoverid) 0]
1588 set ymax [lindex [$canv cget -scrollregion] 3]
1589 if {$ymax == {}} return
1590 set yfrac [lindex [$canv yview] 0]
1591 set x [expr {$hoverx + 2 * $linespc}]
1592 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
1593 set x0 [expr {$x - 2 * $lthickness}]
1594 set y0 [expr {$y - 2 * $lthickness}]
1595 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
1596 set y1 [expr {$y + $linespc + 2 * $lthickness}]
1597 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
1598 -fill \#ffff80 -outline black -width 1 -tags hover]
1600 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
1613 set diffopts "-U 5 -p"
1615 set mainfont {Helvetica 9}
1616 set textfont {Courier 9}
1618 set colors {green red blue magenta darkgrey brown orange}
1620 catch {source ~/.gitk}
1622 set namefont $mainfont
1624 lappend namefont bold
1629 switch -regexp -- $arg {
1631 "^-b" { set boldnames 1 }
1632 "^-d" { set datemode 1 }
1634 lappend revtreeargs $arg
1639 set noreadobj [catch {load libreadobj.so.0.0}]
1646 getcommits $revtreeargs