Restructure to do incremental drawing
[git.git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
4
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.
9
10 # CVS $Revision: 1.22 $
11
12 proc getcommits {rargs} {
13     global commits commfd phase canv mainfont
14     global startmsecs nextupdate
15
16     if {$rargs == {}} {
17         set rargs HEAD
18     }
19     set commits {}
20     set phase getcommits
21     set startmsecs [clock clicks -milliseconds]
22     set nextupdate [expr $startmsecs + 100]
23     if [catch {set commfd [open "|git-rev-list --merge-order $rargs" r]} err] {
24         puts stderr "Error executing git-rev-list: $err"
25         exit 1
26     }
27     fconfigure $commfd -blocking 0
28     fileevent $commfd readable "getcommitline $commfd"
29     $canv delete all
30     $canv create text 3 3 -anchor nw -text "Reading commits..." \
31         -font $mainfont -tags textitems
32 }
33
34 proc getcommitline {commfd}  {
35     global commits parents cdate children nchildren ncleft
36     global commitlisted phase commitinfo nextupdate
37     global stopped redisplaying
38
39     set n [gets $commfd line]
40     if {$n < 0} {
41         if {![eof $commfd]} return
42         # this works around what is apparently a bug in Tcl...
43         fconfigure $commfd -blocking 1
44         if {![catch {close $commfd} err]} {
45             after idle finishcommits
46             return
47         }
48         if {[string range $err 0 4] == "usage"} {
49             set err \
50 {Gitk: error reading commits: bad arguments to git-rev-list.
51 (Note: arguments to gitk are passed to git-rev-list
52 to allow selection of commits to be displayed.)}
53         } else {
54             set err "Error reading commits: $err"
55         }
56         error_popup $err
57         exit 1
58     }
59     if {![regexp {^[0-9a-f]{40}$} $line id]} {
60         error_popup "Can't parse git-rev-list output: {$line}"
61         exit 1
62     }
63     lappend commits $id
64     set commitlisted($id) 1
65     if {![info exists commitinfo($id)]} {
66         readcommit $id
67     }
68     foreach p $parents($id) {
69         if {[info exists commitlisted($p)]} {
70             puts "oops, parent $p before child $id"
71         }
72     }
73     drawcommit $id
74     if {[clock clicks -milliseconds] >= $nextupdate} {
75         doupdate
76     }
77     while {$redisplaying} {
78         set redisplaying 0
79         if {$stopped == 1} {
80             set stopped 0
81             set phase "getcommits"
82             foreach id $commits {
83                 drawcommit $id
84                 if {$stopped} break
85                 if {[clock clicks -milliseconds] >= $nextupdate} {
86                     doupdate
87                 }
88             }
89         }
90     }
91 }
92
93 proc doupdate {} {
94     global commfd nextupdate
95
96     incr nextupdate 100
97     fileevent $commfd readable {}
98     update
99     fileevent $commfd readable "getcommitline $commfd"
100 }
101
102 proc readcommit {id} {
103     global commitinfo children nchildren parents nparents cdate ncleft
104     global noreadobj
105
106     set inhdr 1
107     set comment {}
108     set headline {}
109     set auname {}
110     set audate {}
111     set comname {}
112     set comdate {}
113     if {![info exists nchildren($id)]} {
114         set children($id) {}
115         set nchildren($id) 0
116         set ncleft($id) 0
117     }
118     set parents($id) {}
119     set nparents($id) 0
120     if {$noreadobj} {
121         if [catch {set contents [exec git-cat-file commit $id]}] return
122     } else {
123         if [catch {set x [readobj $id]}] return
124         if {[lindex $x 0] != "commit"} return
125         set contents [lindex $x 1]
126     }
127     foreach line [split $contents "\n"] {
128         if {$inhdr} {
129             if {$line == {}} {
130                 set inhdr 0
131             } else {
132                 set tag [lindex $line 0]
133                 if {$tag == "parent"} {
134                     set p [lindex $line 1]
135                     if {![info exists nchildren($p)]} {
136                         set children($p) {}
137                         set nchildren($p) 0
138                         set ncleft($p) 0
139                     }
140                     lappend parents($id) $p
141                     incr nparents($id)
142                     if {[lsearch -exact $children($p) $id] < 0} {
143                         lappend children($p) $id
144                         incr nchildren($p)
145                         incr ncleft($p)
146                     } else {
147                         puts "child $id already in $p's list??"
148                     }
149                 } elseif {$tag == "author"} {
150                     set x [expr {[llength $line] - 2}]
151                     set audate [lindex $line $x]
152                     set auname [lrange $line 1 [expr {$x - 1}]]
153                 } elseif {$tag == "committer"} {
154                     set x [expr {[llength $line] - 2}]
155                     set comdate [lindex $line $x]
156                     set comname [lrange $line 1 [expr {$x - 1}]]
157                 }
158             }
159         } else {
160             if {$comment == {}} {
161                 set headline $line
162             } else {
163                 append comment "\n"
164             }
165             append comment $line
166         }
167     }
168     if {$audate != {}} {
169         set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
170     }
171     if {$comdate != {}} {
172         set cdate($id) $comdate
173         set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
174     }
175     set commitinfo($id) [list $headline $auname $audate \
176                              $comname $comdate $comment]
177 }
178
179 proc readrefs {} {
180     global tagids idtags headids idheads
181     set tags [glob -nocomplain -types f .git/refs/tags/*]
182     foreach f $tags {
183         catch {
184             set fd [open $f r]
185             set line [read $fd]
186             if {[regexp {^[0-9a-f]{40}} $line id]} {
187                 set direct [file tail $f]
188                 set tagids($direct) $id
189                 lappend idtags($id) $direct
190                 set contents [split [exec git-cat-file tag $id] "\n"]
191                 set obj {}
192                 set type {}
193                 set tag {}
194                 foreach l $contents {
195                     if {$l == {}} break
196                     switch -- [lindex $l 0] {
197                         "object" {set obj [lindex $l 1]}
198                         "type" {set type [lindex $l 1]}
199                         "tag" {set tag [string range $l 4 end]}
200                     }
201                 }
202                 if {$obj != {} && $type == "commit" && $tag != {}} {
203                     set tagids($tag) $obj
204                     lappend idtags($obj) $tag
205                 }
206             }
207             close $fd
208         }
209     }
210     set heads [glob -nocomplain -types f .git/refs/heads/*]
211     foreach f $heads {
212         catch {
213             set fd [open $f r]
214             set line [read $fd 40]
215             if {[regexp {^[0-9a-f]{40}} $line id]} {
216                 set head [file tail $f]
217                 set headids($head) $line
218                 lappend idheads($line) $head
219             }
220             close $fd
221         }
222     }
223 }
224
225 proc error_popup msg {
226     set w .error
227     toplevel $w
228     wm transient $w .
229     message $w.m -text $msg -justify center -aspect 400
230     pack $w.m -side top -fill x -padx 20 -pady 20
231     button $w.ok -text OK -command "destroy $w"
232     pack $w.ok -side bottom -fill x
233     bind $w <Visibility> "grab $w; focus $w"
234     tkwait window $w
235 }
236
237 proc makewindow {} {
238     global canv canv2 canv3 linespc charspc ctext cflist textfont
239     global findtype findloc findstring fstring geometry
240     global entries sha1entry sha1string sha1but
241
242     menu .bar
243     .bar add cascade -label "File" -menu .bar.file
244     menu .bar.file
245     .bar.file add command -label "Quit" -command doquit
246     menu .bar.help
247     .bar add cascade -label "Help" -menu .bar.help
248     .bar.help add command -label "About gitk" -command about
249     . configure -menu .bar
250
251     if {![info exists geometry(canv1)]} {
252         set geometry(canv1) [expr 45 * $charspc]
253         set geometry(canv2) [expr 30 * $charspc]
254         set geometry(canv3) [expr 15 * $charspc]
255         set geometry(canvh) [expr 25 * $linespc + 4]
256         set geometry(ctextw) 80
257         set geometry(ctexth) 30
258         set geometry(cflistw) 30
259     }
260     panedwindow .ctop -orient vertical
261     if {[info exists geometry(width)]} {
262         .ctop conf -width $geometry(width) -height $geometry(height)
263         set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
264         set geometry(ctexth) [expr {($texth - 8) /
265                                     [font metrics $textfont -linespace]}]
266     }
267     frame .ctop.top
268     frame .ctop.top.bar
269     pack .ctop.top.bar -side bottom -fill x
270     set cscroll .ctop.top.csb
271     scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
272     pack $cscroll -side right -fill y
273     panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
274     pack .ctop.top.clist -side top -fill both -expand 1
275     .ctop add .ctop.top
276     set canv .ctop.top.clist.canv
277     canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
278         -bg white -bd 0 \
279         -yscrollincr $linespc -yscrollcommand "$cscroll set"
280     .ctop.top.clist add $canv
281     set canv2 .ctop.top.clist.canv2
282     canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
283         -bg white -bd 0 -yscrollincr $linespc
284     .ctop.top.clist add $canv2
285     set canv3 .ctop.top.clist.canv3
286     canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
287         -bg white -bd 0 -yscrollincr $linespc
288     .ctop.top.clist add $canv3
289     bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
290
291     set sha1entry .ctop.top.bar.sha1
292     set entries $sha1entry
293     set sha1but .ctop.top.bar.sha1label
294     button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
295         -command gotocommit -width 8
296     $sha1but conf -disabledforeground [$sha1but cget -foreground]
297     pack .ctop.top.bar.sha1label -side left
298     entry $sha1entry -width 40 -font $textfont -textvariable sha1string
299     trace add variable sha1string write sha1change
300     pack $sha1entry -side left -pady 2
301     button .ctop.top.bar.findbut -text "Find" -command dofind
302     pack .ctop.top.bar.findbut -side left
303     set findstring {}
304     set fstring .ctop.top.bar.findstring
305     lappend entries $fstring
306     entry $fstring -width 30 -font $textfont -textvariable findstring
307     pack $fstring -side left -expand 1 -fill x
308     set findtype Exact
309     tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
310     set findloc "All fields"
311     tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
312         Comments Author Committer
313     pack .ctop.top.bar.findloc -side right
314     pack .ctop.top.bar.findtype -side right
315
316     panedwindow .ctop.cdet -orient horizontal
317     .ctop add .ctop.cdet
318     frame .ctop.cdet.left
319     set ctext .ctop.cdet.left.ctext
320     text $ctext -bg white -state disabled -font $textfont \
321         -width $geometry(ctextw) -height $geometry(ctexth) \
322         -yscrollcommand ".ctop.cdet.left.sb set"
323     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
324     pack .ctop.cdet.left.sb -side right -fill y
325     pack $ctext -side left -fill both -expand 1
326     .ctop.cdet add .ctop.cdet.left
327
328     $ctext tag conf filesep -font [concat $textfont bold]
329     $ctext tag conf hunksep -back blue -fore white
330     $ctext tag conf d0 -back "#ff8080"
331     $ctext tag conf d1 -back green
332     $ctext tag conf found -back yellow
333
334     frame .ctop.cdet.right
335     set cflist .ctop.cdet.right.cfiles
336     listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
337         -yscrollcommand ".ctop.cdet.right.sb set"
338     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
339     pack .ctop.cdet.right.sb -side right -fill y
340     pack $cflist -side left -fill both -expand 1
341     .ctop.cdet add .ctop.cdet.right
342     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
343
344     pack .ctop -side top -fill both -expand 1
345
346     bindall <1> {selcanvline %x %y}
347     bindall <B1-Motion> {selcanvline %x %y}
348     bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
349     bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
350     bindall <2> "allcanvs scan mark 0 %y"
351     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
352     bind . <Key-Up> "selnextline -1"
353     bind . <Key-Down> "selnextline 1"
354     bind . <Key-Prior> "allcanvs yview scroll -1 pages"
355     bind . <Key-Next> "allcanvs yview scroll 1 pages"
356     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
357     bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
358     bindkey <Key-space> "$ctext yview scroll 1 pages"
359     bindkey p "selnextline -1"
360     bindkey n "selnextline 1"
361     bindkey b "$ctext yview scroll -1 pages"
362     bindkey d "$ctext yview scroll 18 units"
363     bindkey u "$ctext yview scroll -18 units"
364     bindkey / findnext
365     bindkey ? findprev
366     bindkey f nextfile
367     bind . <Control-q> doquit
368     bind . <Control-f> dofind
369     bind . <Control-g> findnext
370     bind . <Control-r> findprev
371     bind . <Control-equal> {incrfont 1}
372     bind . <Control-KP_Add> {incrfont 1}
373     bind . <Control-minus> {incrfont -1}
374     bind . <Control-KP_Subtract> {incrfont -1}
375     bind $cflist <<ListboxSelect>> listboxsel
376     bind . <Destroy> {savestuff %W}
377     bind . <Button-1> "click %W"
378     bind $fstring <Key-Return> dofind
379     bind $sha1entry <Key-Return> gotocommit
380 }
381
382 # when we make a key binding for the toplevel, make sure
383 # it doesn't get triggered when that key is pressed in the
384 # find string entry widget.
385 proc bindkey {ev script} {
386     global entries
387     bind . $ev $script
388     set escript [bind Entry $ev]
389     if {$escript == {}} {
390         set escript [bind Entry <Key>]
391     }
392     foreach e $entries {
393         bind $e $ev "$escript; break"
394     }
395 }
396
397 # set the focus back to the toplevel for any click outside
398 # the entry widgets
399 proc click {w} {
400     global entries
401     foreach e $entries {
402         if {$w == $e} return
403     }
404     focus .
405 }
406
407 proc savestuff {w} {
408     global canv canv2 canv3 ctext cflist mainfont textfont
409     global stuffsaved
410     if {$stuffsaved} return
411     if {![winfo viewable .]} return
412     catch {
413         set f [open "~/.gitk-new" w]
414         puts $f "set mainfont {$mainfont}"
415         puts $f "set textfont {$textfont}"
416         puts $f "set geometry(width) [winfo width .ctop]"
417         puts $f "set geometry(height) [winfo height .ctop]"
418         puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
419         puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
420         puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
421         puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
422         set wid [expr {([winfo width $ctext] - 8) \
423                            / [font measure $textfont "0"]}]
424         puts $f "set geometry(ctextw) $wid"
425         set wid [expr {([winfo width $cflist] - 11) \
426                            / [font measure [$cflist cget -font] "0"]}]
427         puts $f "set geometry(cflistw) $wid"
428         close $f
429         file rename -force "~/.gitk-new" "~/.gitk"
430     }
431     set stuffsaved 1
432 }
433
434 proc resizeclistpanes {win w} {
435     global oldwidth
436     if [info exists oldwidth($win)] {
437         set s0 [$win sash coord 0]
438         set s1 [$win sash coord 1]
439         if {$w < 60} {
440             set sash0 [expr {int($w/2 - 2)}]
441             set sash1 [expr {int($w*5/6 - 2)}]
442         } else {
443             set factor [expr {1.0 * $w / $oldwidth($win)}]
444             set sash0 [expr {int($factor * [lindex $s0 0])}]
445             set sash1 [expr {int($factor * [lindex $s1 0])}]
446             if {$sash0 < 30} {
447                 set sash0 30
448             }
449             if {$sash1 < $sash0 + 20} {
450                 set sash1 [expr $sash0 + 20]
451             }
452             if {$sash1 > $w - 10} {
453                 set sash1 [expr $w - 10]
454                 if {$sash0 > $sash1 - 20} {
455                     set sash0 [expr $sash1 - 20]
456                 }
457             }
458         }
459         $win sash place 0 $sash0 [lindex $s0 1]
460         $win sash place 1 $sash1 [lindex $s1 1]
461     }
462     set oldwidth($win) $w
463 }
464
465 proc resizecdetpanes {win w} {
466     global oldwidth
467     if [info exists oldwidth($win)] {
468         set s0 [$win sash coord 0]
469         if {$w < 60} {
470             set sash0 [expr {int($w*3/4 - 2)}]
471         } else {
472             set factor [expr {1.0 * $w / $oldwidth($win)}]
473             set sash0 [expr {int($factor * [lindex $s0 0])}]
474             if {$sash0 < 45} {
475                 set sash0 45
476             }
477             if {$sash0 > $w - 15} {
478                 set sash0 [expr $w - 15]
479             }
480         }
481         $win sash place 0 $sash0 [lindex $s0 1]
482     }
483     set oldwidth($win) $w
484 }
485
486 proc allcanvs args {
487     global canv canv2 canv3
488     eval $canv $args
489     eval $canv2 $args
490     eval $canv3 $args
491 }
492
493 proc bindall {event action} {
494     global canv canv2 canv3
495     bind $canv $event $action
496     bind $canv2 $event $action
497     bind $canv3 $event $action
498 }
499
500 proc about {} {
501     set w .about
502     if {[winfo exists $w]} {
503         raise $w
504         return
505     }
506     toplevel $w
507     wm title $w "About gitk"
508     message $w.m -text {
509 Gitk version 1.1
510
511 Copyright Â© 2005 Paul Mackerras
512
513 Use and redistribute under the terms of the GNU General Public License
514
515 (CVS $Revision: 1.22 $)} \
516             -justify center -aspect 400
517     pack $w.m -side top -fill x -padx 20 -pady 20
518     button $w.ok -text Close -command "destroy $w"
519     pack $w.ok -side bottom
520 }
521
522 proc assigncolor {id} {
523     global commitinfo colormap commcolors colors nextcolor
524     global parents nparents children nchildren
525     if [info exists colormap($id)] return
526     set ncolors [llength $colors]
527     if {$nparents($id) == 1 && $nchildren($id) == 1} {
528         set child [lindex $children($id) 0]
529         if {[info exists colormap($child)]
530             && $nparents($child) == 1} {
531             set colormap($id) $colormap($child)
532             return
533         }
534     }
535     set badcolors {}
536     foreach child $children($id) {
537         if {[info exists colormap($child)]
538             && [lsearch -exact $badcolors $colormap($child)] < 0} {
539             lappend badcolors $colormap($child)
540         }
541         if {[info exists parents($child)]} {
542             foreach p $parents($child) {
543                 if {[info exists colormap($p)]
544                     && [lsearch -exact $badcolors $colormap($p)] < 0} {
545                     lappend badcolors $colormap($p)
546                 }
547             }
548         }
549     }
550     if {[llength $badcolors] >= $ncolors} {
551         set badcolors {}
552     }
553     for {set i 0} {$i <= $ncolors} {incr i} {
554         set c [lindex $colors $nextcolor]
555         if {[incr nextcolor] >= $ncolors} {
556             set nextcolor 0
557         }
558         if {[lsearch -exact $badcolors $c]} break
559     }
560     set colormap($id) $c
561 }
562
563 proc initgraph {} {
564     global canvy canvy0 lineno numcommits lthickness nextcolor linespc
565     global linestarty
566     global nchildren ncleft
567
568     allcanvs delete all
569     set nextcolor 0
570     set canvy $canvy0
571     set lineno -1
572     set numcommits 0
573     set lthickness [expr {int($linespc / 9) + 1}]
574     catch {unset linestarty}
575     foreach id [array names nchildren] {
576         set ncleft($id) $nchildren($id)
577     }
578 }
579
580 proc drawcommitline {level} {
581     global parents children nparents nchildren ncleft todo
582     global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
583     global datemode cdate
584     global lineid linehtag linentag linedtag commitinfo
585     global colormap numcommits currentparents
586     global oldlevel oldnlines oldtodo
587     global idtags idline idheads
588     global lineno lthickness linestarty
589     global commitlisted
590
591     incr numcommits
592     incr lineno
593     set id [lindex $todo $level]
594     set lineid($lineno) $id
595     set idline($id) $lineno
596     set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
597     if {![info exists commitinfo($id)]} {
598         readcommit $id
599         if {![info exists commitinfo($id)]} {
600             set commitinfo($id) {"No commit information available"}
601             set nparents($id) 0
602         }
603     }
604     set currentparents {}
605     if {[info exists commitlisted($id)] && [info exists parents($id)]} {
606         set currentparents $parents($id)
607     }
608     set x [expr $canvx0 + $level * $linespc]
609     set y1 $canvy
610     set canvy [expr $canvy + $linespc]
611     allcanvs conf -scrollregion \
612         [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
613     if {[info exists linestarty($id)] && $linestarty($id) < $y1} {
614         set t [$canv create line $x $linestarty($id) $x $y1 \
615                    -width $lthickness -fill $colormap($id)]
616         $canv lower $t
617     }
618     set orad [expr {$linespc / 3}]
619     set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
620                [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
621                -fill $ofill -outline black -width 1]
622     $canv raise $t
623     set xt [expr $canvx0 + [llength $todo] * $linespc]
624     if {$nparents($id) > 2} {
625         set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
626     }
627     set marks {}
628     set ntags 0
629     if {[info exists idtags($id)]} {
630         set marks $idtags($id)
631         set ntags [llength $marks]
632     }
633     if {[info exists idheads($id)]} {
634         set marks [concat $marks $idheads($id)]
635     }
636     if {$marks != {}} {
637         set delta [expr {int(0.5 * ($linespc - $lthickness))}]
638         set yt [expr $y1 - 0.5 * $linespc]
639         set yb [expr $yt + $linespc - 1]
640         set xvals {}
641         set wvals {}
642         foreach tag $marks {
643             set wid [font measure $mainfont $tag]
644             lappend xvals $xt
645             lappend wvals $wid
646             set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
647         }
648         set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
649                    -width $lthickness -fill black]
650         $canv lower $t
651         foreach tag $marks x $xvals wid $wvals {
652             set xl [expr $x + $delta]
653             set xr [expr $x + $delta + $wid + $lthickness]
654             if {[incr ntags -1] >= 0} {
655                 # draw a tag
656                 $canv create polygon $x [expr $yt + $delta] $xl $yt\
657                     $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
658                     -width 1 -outline black -fill yellow
659             } else {
660                 # draw a head
661                 set xl [expr $xl - $delta/2]
662                 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
663                     -width 1 -outline black -fill green
664             }
665             $canv create text $xl $y1 -anchor w -text $tag \
666                 -font $mainfont
667         }
668     }
669     set headline [lindex $commitinfo($id) 0]
670     set name [lindex $commitinfo($id) 1]
671     set date [lindex $commitinfo($id) 2]
672     set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
673                                -text $headline -font $mainfont ]
674     set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
675                                -text $name -font $namefont]
676     set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
677                                -text $date -font $mainfont]
678 }
679
680 proc updatetodo {level noshortcut} {
681     global datemode currentparents ncleft todo
682     global linestarty oldlevel oldtodo oldnlines
683     global canvy linespc
684     global commitinfo
685
686     foreach p $currentparents {
687         if {![info exists commitinfo($p)]} {
688             readcommit $p
689         }
690     }
691     if {!$noshortcut && [llength $currentparents] == 1} {
692         set p [lindex $currentparents 0]
693         if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
694             assigncolor $p
695             set linestarty($p) [expr $canvy - $linespc]
696             set todo [lreplace $todo $level $level $p]
697             return 0
698         }
699     }
700
701     set oldlevel $level
702     set oldtodo $todo
703     set oldnlines [llength $todo]
704     set todo [lreplace $todo $level $level]
705     set i $level
706     foreach p $currentparents {
707         incr ncleft($p) -1
708         set k [lsearch -exact $todo $p]
709         if {$k < 0} {
710             assigncolor $p
711             set todo [linsert $todo $i $p]
712             incr i
713         }
714     }
715     return 1
716 }
717
718 proc drawslants {} {
719     global canv linestarty canvx0 canvy linespc
720     global oldlevel oldtodo todo currentparents
721     global lthickness linespc canvy colormap
722
723     set y1 [expr $canvy - $linespc]
724     set y2 $canvy
725     set i -1
726     foreach id $oldtodo {
727         incr i
728         if {$id == {}} continue
729         set xi [expr {$canvx0 + $i * $linespc}]
730         if {$i == $oldlevel} {
731             foreach p $currentparents {
732                 set j [lsearch -exact $todo $p]
733                 if {$i == $j && ![info exists linestarty($p)]} {
734                     set linestarty($p) $y1
735                 } else {
736                     set xj [expr {$canvx0 + $j * $linespc}]
737                     set coords [list $xi $y1]
738                     if {$j < $i - 1} {
739                         lappend coords [expr $xj + $linespc] $y1
740                     } elseif {$j > $i + 1} {
741                         lappend coords [expr $xj - $linespc] $y1
742                     }
743                     lappend coords $xj $y2
744                     set t [$canv create line $coords -width $lthickness \
745                                -fill $colormap($p)]
746                     $canv lower $t
747                     if {![info exists linestarty($p)]} {
748                         set linestarty($p) $y2
749                     }
750                 }
751             }
752         } elseif {[lindex $todo $i] != $id} {
753             set j [lsearch -exact $todo $id]
754             set xj [expr {$canvx0 + $j * $linespc}]
755             set coords {}
756             if {[info exists linestarty($id)] && $linestarty($id) < $y1} {
757                 lappend coords $xi $linestarty($id)
758             }
759             lappend coords $xi $y1 $xj $y2
760             set t [$canv create line $coords -width $lthickness \
761                        -fill $colormap($id)]
762             $canv lower $t
763             set linestarty($id) $y2
764         }
765     }
766 }
767
768 proc decidenext {} {
769     global parents children nchildren ncleft todo
770     global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
771     global datemode cdate
772     global lineid linehtag linentag linedtag commitinfo
773     global currentparents oldlevel oldnlines oldtodo
774     global lineno lthickness
775
776     # remove the null entry if present
777     set nullentry [lsearch -exact $todo {}]
778     if {$nullentry >= 0} {
779         set todo [lreplace $todo $nullentry $nullentry]
780     }
781
782     # choose which one to do next time around
783     set todol [llength $todo]
784     set level -1
785     set latest {}
786     for {set k $todol} {[incr k -1] >= 0} {} {
787         set p [lindex $todo $k]
788         if {$ncleft($p) == 0} {
789             if {$datemode} {
790                 if {$latest == {} || $cdate($p) > $latest} {
791                     set level $k
792                     set latest $cdate($p)
793                 }
794             } else {
795                 set level $k
796                 break
797             }
798         }
799     }
800     if {$level < 0} {
801         if {$todo != {}} {
802             puts "ERROR: none of the pending commits can be done yet:"
803             foreach p $todo {
804                 puts "  $p"
805             }
806         }
807         return -1
808     }
809
810     # If we are reducing, put in a null entry
811     if {$todol < $oldnlines} {
812         if {$nullentry >= 0} {
813             set i $nullentry
814             while {$i < $todol
815                    && [lindex $oldtodo $i] == [lindex $todo $i]} {
816                 incr i
817             }
818         } else {
819             set i $oldlevel
820             if {$level >= $i} {
821                 incr i
822             }
823         }
824         if {$i < $todol} {
825             set todo [linsert $todo $i {}]
826             if {$level >= $i} {
827                 incr level
828             }
829         }
830     }
831     return $level
832 }
833
834 proc drawcommit {id} {
835     global phase todo nchildren datemode nextupdate
836     global startcommits
837
838     if {$phase != "incrdraw"} {
839         set phase incrdraw
840         set todo $id
841         set startcommits $id
842         initgraph
843         assigncolor $id
844         drawcommitline 0
845         updatetodo 0 $datemode
846     } else {
847         if {$nchildren($id) == 0} {
848             lappend todo $id
849             lappend startcommits $id
850             assigncolor $id
851         }
852         set level [decidenext]
853         if {$id != [lindex $todo $level]} {
854             return
855         }
856         while 1 {
857             drawslants
858             drawcommitline $level
859             if {[updatetodo $level $datemode]} {
860                 set level [decidenext]
861             }
862             set id [lindex $todo $level]
863             if {![info exists commitlisted($id)]} {
864                 break
865             }
866             if {[clock clicks -milliseconds] >= $nextupdate} {
867                 doupdate
868                 if {$stopped} break
869             }
870         }
871     }
872 }
873
874 proc finishcommits {} {
875     global phase
876     global startcommits
877
878     if {$phase != "incrdraw"} {
879         $canv delete all
880         $canv create text 3 3 -anchor nw -text "No commits selected" \
881             -font $mainfont -tags textitems
882         set phase {}
883         return
884     }
885     drawslants
886     set level [decidenext]
887     drawrest $level [llength $startcommits]
888 }
889
890 proc drawgraph {} {
891     global nextupdate startmsecs startcommits todo
892
893     if {$startcommits == {}} return
894     set startmsecs [clock clicks -milliseconds]
895     set nextupdate [expr $startmsecs + 100]
896     initgraph
897     set todo [lindex $startcommits 0]
898     drawrest 0 1
899 }
900
901 proc drawrest {level startix} {
902     global phase stopped redisplaying selectedline
903     global datemode currentparents todo
904     global numcommits
905     global nextupdate startmsecs startcommits idline
906
907     set phase drawgraph
908     set startid [lindex $startcommits $startix]
909     set startline -1
910     if {$startid != {}} {
911         set startline $idline($startid)
912     }
913     while 1 {
914         if {$stopped} break
915         drawcommitline $level
916         set hard [updatetodo $level $datemode]
917         if {$numcommits == $startline} {
918             lappend todo $startid
919             set hard 1
920             incr startix
921             set startid [lindex $startcommits $startix]
922             set startline -1
923             if {$startid != {}} {
924                 set startline $idline($startid)
925             }
926         }
927         if {$hard} {
928             set level [decidenext]
929             if {$level < 0} break
930             drawslants
931         }
932         if {[clock clicks -milliseconds] >= $nextupdate} {
933             update
934             incr nextupdate 100
935         }
936     }
937     set phase {}
938     set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
939     puts "overall $drawmsecs ms for $numcommits commits"
940     if {$redisplaying} {
941         if {$stopped == 0 && [info exists selectedline]} {
942             selectline $selectedline
943         }
944         if {$stopped == 1} {
945             set stopped 0
946             after idle drawgraph
947         } else {
948             set redisplaying 0
949         }
950     }
951 }
952
953 proc findmatches {f} {
954     global findtype foundstring foundstrlen
955     if {$findtype == "Regexp"} {
956         set matches [regexp -indices -all -inline $foundstring $f]
957     } else {
958         if {$findtype == "IgnCase"} {
959             set str [string tolower $f]
960         } else {
961             set str $f
962         }
963         set matches {}
964         set i 0
965         while {[set j [string first $foundstring $str $i]] >= 0} {
966             lappend matches [list $j [expr $j+$foundstrlen-1]]
967             set i [expr $j + $foundstrlen]
968         }
969     }
970     return $matches
971 }
972
973 proc dofind {} {
974     global findtype findloc findstring markedmatches commitinfo
975     global numcommits lineid linehtag linentag linedtag
976     global mainfont namefont canv canv2 canv3 selectedline
977     global matchinglines foundstring foundstrlen
978     unmarkmatches
979     focus .
980     set matchinglines {}
981     set fldtypes {Headline Author Date Committer CDate Comment}
982     if {$findtype == "IgnCase"} {
983         set foundstring [string tolower $findstring]
984     } else {
985         set foundstring $findstring
986     }
987     set foundstrlen [string length $findstring]
988     if {$foundstrlen == 0} return
989     if {![info exists selectedline]} {
990         set oldsel -1
991     } else {
992         set oldsel $selectedline
993     }
994     set didsel 0
995     for {set l 0} {$l < $numcommits} {incr l} {
996         set id $lineid($l)
997         set info $commitinfo($id)
998         set doesmatch 0
999         foreach f $info ty $fldtypes {
1000             if {$findloc != "All fields" && $findloc != $ty} {
1001                 continue
1002             }
1003             set matches [findmatches $f]
1004             if {$matches == {}} continue
1005             set doesmatch 1
1006             if {$ty == "Headline"} {
1007                 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1008             } elseif {$ty == "Author"} {
1009                 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1010             } elseif {$ty == "Date"} {
1011                 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1012             }
1013         }
1014         if {$doesmatch} {
1015             lappend matchinglines $l
1016             if {!$didsel && $l > $oldsel} {
1017                 findselectline $l
1018                 set didsel 1
1019             }
1020         }
1021     }
1022     if {$matchinglines == {}} {
1023         bell
1024     } elseif {!$didsel} {
1025         findselectline [lindex $matchinglines 0]
1026     }
1027 }
1028
1029 proc findselectline {l} {
1030     global findloc commentend ctext
1031     selectline $l
1032     if {$findloc == "All fields" || $findloc == "Comments"} {
1033         # highlight the matches in the comments
1034         set f [$ctext get 1.0 $commentend]
1035         set matches [findmatches $f]
1036         foreach match $matches {
1037             set start [lindex $match 0]
1038             set end [expr [lindex $match 1] + 1]
1039             $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1040         }
1041     }
1042 }
1043
1044 proc findnext {} {
1045     global matchinglines selectedline
1046     if {![info exists matchinglines]} {
1047         dofind
1048         return
1049     }
1050     if {![info exists selectedline]} return
1051     foreach l $matchinglines {
1052         if {$l > $selectedline} {
1053             findselectline $l
1054             return
1055         }
1056     }
1057     bell
1058 }
1059
1060 proc findprev {} {
1061     global matchinglines selectedline
1062     if {![info exists matchinglines]} {
1063         dofind
1064         return
1065     }
1066     if {![info exists selectedline]} return
1067     set prev {}
1068     foreach l $matchinglines {
1069         if {$l >= $selectedline} break
1070         set prev $l
1071     }
1072     if {$prev != {}} {
1073         findselectline $prev
1074     } else {
1075         bell
1076     }
1077 }
1078
1079 proc markmatches {canv l str tag matches font} {
1080     set bbox [$canv bbox $tag]
1081     set x0 [lindex $bbox 0]
1082     set y0 [lindex $bbox 1]
1083     set y1 [lindex $bbox 3]
1084     foreach match $matches {
1085         set start [lindex $match 0]
1086         set end [lindex $match 1]
1087         if {$start > $end} continue
1088         set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1089         set xlen [font measure $font [string range $str 0 [expr $end]]]
1090         set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1091                    -outline {} -tags matches -fill yellow]
1092         $canv lower $t
1093     }
1094 }
1095
1096 proc unmarkmatches {} {
1097     global matchinglines
1098     allcanvs delete matches
1099     catch {unset matchinglines}
1100 }
1101
1102 proc selcanvline {x y} {
1103     global canv canvy0 ctext linespc selectedline
1104     global lineid linehtag linentag linedtag
1105     set ymax [lindex [$canv cget -scrollregion] 3]
1106     if {$ymax == {}} return
1107     set yfrac [lindex [$canv yview] 0]
1108     set y [expr {$y + $yfrac * $ymax}]
1109     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1110     if {$l < 0} {
1111         set l 0
1112     }
1113     if {[info exists selectedline] && $selectedline == $l} return
1114     unmarkmatches
1115     selectline $l
1116 }
1117
1118 proc selectline {l} {
1119     global canv canv2 canv3 ctext commitinfo selectedline
1120     global lineid linehtag linentag linedtag
1121     global canvy0 linespc nparents treepending
1122     global cflist treediffs currentid sha1entry
1123     global commentend seenfile idtags
1124     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1125     $canv delete secsel
1126     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1127                -tags secsel -fill [$canv cget -selectbackground]]
1128     $canv lower $t
1129     $canv2 delete secsel
1130     set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1131                -tags secsel -fill [$canv2 cget -selectbackground]]
1132     $canv2 lower $t
1133     $canv3 delete secsel
1134     set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1135                -tags secsel -fill [$canv3 cget -selectbackground]]
1136     $canv3 lower $t
1137     set y [expr {$canvy0 + $l * $linespc}]
1138     set ymax [lindex [$canv cget -scrollregion] 3]
1139     set ytop [expr {$y - $linespc - 1}]
1140     set ybot [expr {$y + $linespc + 1}]
1141     set wnow [$canv yview]
1142     set wtop [expr [lindex $wnow 0] * $ymax]
1143     set wbot [expr [lindex $wnow 1] * $ymax]
1144     set wh [expr {$wbot - $wtop}]
1145     set newtop $wtop
1146     if {$ytop < $wtop} {
1147         if {$ybot < $wtop} {
1148             set newtop [expr {$y - $wh / 2.0}]
1149         } else {
1150             set newtop $ytop
1151             if {$newtop > $wtop - $linespc} {
1152                 set newtop [expr {$wtop - $linespc}]
1153             }
1154         }
1155     } elseif {$ybot > $wbot} {
1156         if {$ytop > $wbot} {
1157             set newtop [expr {$y - $wh / 2.0}]
1158         } else {
1159             set newtop [expr {$ybot - $wh}]
1160             if {$newtop < $wtop + $linespc} {
1161                 set newtop [expr {$wtop + $linespc}]
1162             }
1163         }
1164     }
1165     if {$newtop != $wtop} {
1166         if {$newtop < 0} {
1167             set newtop 0
1168         }
1169         allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1170     }
1171     set selectedline $l
1172
1173     set id $lineid($l)
1174     set currentid $id
1175     $sha1entry delete 0 end
1176     $sha1entry insert 0 $id
1177     $sha1entry selection from 0
1178     $sha1entry selection to end
1179
1180     $ctext conf -state normal
1181     $ctext delete 0.0 end
1182     set info $commitinfo($id)
1183     $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
1184     $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
1185     if {[info exists idtags($id)]} {
1186         $ctext insert end "Tags:"
1187         foreach tag $idtags($id) {
1188             $ctext insert end " $tag"
1189         }
1190         $ctext insert end "\n"
1191     }
1192     $ctext insert end "\n"
1193     $ctext insert end [lindex $info 5]
1194     $ctext insert end "\n"
1195     $ctext tag delete Comments
1196     $ctext tag remove found 1.0 end
1197     $ctext conf -state disabled
1198     set commentend [$ctext index "end - 1c"]
1199
1200     $cflist delete 0 end
1201     if {$nparents($id) == 1} {
1202         if {![info exists treediffs($id)]} {
1203             if {![info exists treepending]} {
1204                 gettreediffs $id
1205             }
1206         } else {
1207             addtocflist $id
1208         }
1209     }
1210     catch {unset seenfile}
1211 }
1212
1213 proc selnextline {dir} {
1214     global selectedline
1215     if {![info exists selectedline]} return
1216     set l [expr $selectedline + $dir]
1217     unmarkmatches
1218     selectline $l
1219 }
1220
1221 proc addtocflist {id} {
1222     global currentid treediffs cflist treepending
1223     if {$id != $currentid} {
1224         gettreediffs $currentid
1225         return
1226     }
1227     $cflist insert end "All files"
1228     foreach f $treediffs($currentid) {
1229         $cflist insert end $f
1230     }
1231     getblobdiffs $id
1232 }
1233
1234 proc gettreediffs {id} {
1235     global treediffs parents treepending
1236     set treepending $id
1237     set treediffs($id) {}
1238     set p [lindex $parents($id) 0]
1239     if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1240     fconfigure $gdtf -blocking 0
1241     fileevent $gdtf readable "gettreediffline $gdtf $id"
1242 }
1243
1244 proc gettreediffline {gdtf id} {
1245     global treediffs treepending
1246     set n [gets $gdtf line]
1247     if {$n < 0} {
1248         if {![eof $gdtf]} return
1249         close $gdtf
1250         unset treepending
1251         addtocflist $id
1252         return
1253     }
1254     set file [lindex $line 5]
1255     lappend treediffs($id) $file
1256 }
1257
1258 proc getblobdiffs {id} {
1259     global parents diffopts blobdifffd env curdifftag curtagstart
1260     global diffindex difffilestart
1261     set p [lindex $parents($id) 0]
1262     set env(GIT_DIFF_OPTS) $diffopts
1263     if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1264         puts "error getting diffs: $err"
1265         return
1266     }
1267     fconfigure $bdf -blocking 0
1268     set blobdifffd($id) $bdf
1269     set curdifftag Comments
1270     set curtagstart 0.0
1271     set diffindex 0
1272     catch {unset difffilestart}
1273     fileevent $bdf readable "getblobdiffline $bdf $id"
1274 }
1275
1276 proc getblobdiffline {bdf id} {
1277     global currentid blobdifffd ctext curdifftag curtagstart seenfile
1278     global diffnexthead diffnextnote diffindex difffilestart
1279     set n [gets $bdf line]
1280     if {$n < 0} {
1281         if {[eof $bdf]} {
1282             close $bdf
1283             if {$id == $currentid && $bdf == $blobdifffd($id)} {
1284                 $ctext tag add $curdifftag $curtagstart end
1285                 set seenfile($curdifftag) 1
1286             }
1287         }
1288         return
1289     }
1290     if {$id != $currentid || $bdf != $blobdifffd($id)} {
1291         return
1292     }
1293     $ctext conf -state normal
1294     if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
1295         # start of a new file
1296         $ctext insert end "\n"
1297         $ctext tag add $curdifftag $curtagstart end
1298         set seenfile($curdifftag) 1
1299         set curtagstart [$ctext index "end - 1c"]
1300         set header $fname
1301         if {[info exists diffnexthead]} {
1302             set fname $diffnexthead
1303             set header "$diffnexthead ($diffnextnote)"
1304             unset diffnexthead
1305         }
1306         set difffilestart($diffindex) [$ctext index "end - 1c"]
1307         incr diffindex
1308         set curdifftag "f:$fname"
1309         $ctext tag delete $curdifftag
1310         set l [expr {(78 - [string length $header]) / 2}]
1311         set pad [string range "----------------------------------------" 1 $l]
1312         $ctext insert end "$pad $header $pad\n" filesep
1313     } elseif {[string range $line 0 2] == "+++"} {
1314         # no need to do anything with this
1315     } elseif {[regexp {^Created: (.*) \((mode: *[0-7]*)\)} $line match fn m]} {
1316         set diffnexthead $fn
1317         set diffnextnote "created, mode $m"
1318     } elseif {[string range $line 0 8] == "Deleted: "} {
1319         set diffnexthead [string range $line 9 end]
1320         set diffnextnote "deleted"
1321     } elseif {[regexp {^diff --git a/(.*) b/} $line match fn]} {
1322         # save the filename in case the next thing is "new file mode ..."
1323         set diffnexthead $fn
1324         set diffnextnote "modified"
1325     } elseif {[regexp {^new file mode ([0-7]+)} $line match m]} {
1326         set diffnextnote "new file, mode $m"
1327     } elseif {[string range $line 0 11] == "deleted file"} {
1328         set diffnextnote "deleted"
1329     } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1330                    $line match f1l f1c f2l f2c rest]} {
1331         $ctext insert end "\t" hunksep
1332         $ctext insert end "    $f1l    " d0 "    $f2l    " d1
1333         $ctext insert end "    $rest \n" hunksep
1334     } else {
1335         set x [string range $line 0 0]
1336         if {$x == "-" || $x == "+"} {
1337             set tag [expr {$x == "+"}]
1338             set line [string range $line 1 end]
1339             $ctext insert end "$line\n" d$tag
1340         } elseif {$x == " "} {
1341             set line [string range $line 1 end]
1342             $ctext insert end "$line\n"
1343         } elseif {$x == "\\"} {
1344             # e.g. "\ No newline at end of file"
1345             $ctext insert end "$line\n" filesep
1346         } else {
1347             # Something else we don't recognize
1348             if {$curdifftag != "Comments"} {
1349                 $ctext insert end "\n"
1350                 $ctext tag add $curdifftag $curtagstart end
1351                 set seenfile($curdifftag) 1
1352                 set curtagstart [$ctext index "end - 1c"]
1353                 set curdifftag Comments
1354             }
1355             $ctext insert end "$line\n" filesep
1356         }
1357     }
1358     $ctext conf -state disabled
1359 }
1360
1361 proc nextfile {} {
1362     global difffilestart ctext
1363     set here [$ctext index @0,0]
1364     for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1365         if {[$ctext compare $difffilestart($i) > $here]} {
1366             $ctext yview $difffilestart($i)
1367             break
1368         }
1369     }
1370 }
1371
1372 proc listboxsel {} {
1373     global ctext cflist currentid treediffs seenfile
1374     if {![info exists currentid]} return
1375     set sel [$cflist curselection]
1376     if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
1377         # show everything
1378         $ctext tag conf Comments -elide 0
1379         foreach f $treediffs($currentid) {
1380             if [info exists seenfile(f:$f)] {
1381                 $ctext tag conf "f:$f" -elide 0
1382             }
1383         }
1384     } else {
1385         # just show selected files
1386         $ctext tag conf Comments -elide 1
1387         set i 1
1388         foreach f $treediffs($currentid) {
1389             set elide [expr {[lsearch -exact $sel $i] < 0}]
1390             if [info exists seenfile(f:$f)] {
1391                 $ctext tag conf "f:$f" -elide $elide
1392             }
1393             incr i
1394         }
1395     }
1396 }
1397
1398 proc setcoords {} {
1399     global linespc charspc canvx0 canvy0 mainfont
1400     set linespc [font metrics $mainfont -linespace]
1401     set charspc [font measure $mainfont "m"]
1402     set canvy0 [expr 3 + 0.5 * $linespc]
1403     set canvx0 [expr 3 + 0.5 * $linespc]
1404 }
1405
1406 proc redisplay {} {
1407     global selectedline stopped redisplaying phase
1408     if {$stopped > 1} return
1409     if {$phase == "getcommits"} return
1410     set redisplaying 1
1411     if {$phase == "drawgraph" || $phase == "incrdraw"} {
1412         set stopped 1
1413     } else {
1414         drawgraph
1415     }
1416 }
1417
1418 proc incrfont {inc} {
1419     global mainfont namefont textfont selectedline ctext canv phase
1420     global stopped entries
1421     unmarkmatches
1422     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1423     set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1424     set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1425     setcoords
1426     $ctext conf -font $textfont
1427     $ctext tag conf filesep -font [concat $textfont bold]
1428     foreach e $entries {
1429         $e conf -font $mainfont
1430     }
1431     if {$phase == "getcommits"} {
1432         $canv itemconf textitems -font $mainfont
1433     }
1434     redisplay
1435 }
1436
1437 proc sha1change {n1 n2 op} {
1438     global sha1string currentid sha1but
1439     if {$sha1string == {}
1440         || ([info exists currentid] && $sha1string == $currentid)} {
1441         set state disabled
1442     } else {
1443         set state normal
1444     }
1445     if {[$sha1but cget -state] == $state} return
1446     if {$state == "normal"} {
1447         $sha1but conf -state normal -relief raised -text "Goto: "
1448     } else {
1449         $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1450     }
1451 }
1452
1453 proc gotocommit {} {
1454     global sha1string currentid idline tagids
1455     if {$sha1string == {}
1456         || ([info exists currentid] && $sha1string == $currentid)} return
1457     if {[info exists tagids($sha1string)]} {
1458         set id $tagids($sha1string)
1459     } else {
1460         set id [string tolower $sha1string]
1461     }
1462     if {[info exists idline($id)]} {
1463         selectline $idline($id)
1464         return
1465     }
1466     if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
1467         set type "SHA1 id"
1468     } else {
1469         set type "Tag"
1470     }
1471     error_popup "$type $sha1string is not known"
1472 }
1473
1474 proc doquit {} {
1475     global stopped
1476     set stopped 100
1477     destroy .
1478 }
1479
1480 # defaults...
1481 set datemode 0
1482 set boldnames 0
1483 set diffopts "-U 5 -p"
1484
1485 set mainfont {Helvetica 9}
1486 set textfont {Courier 9}
1487
1488 set colors {green red blue magenta darkgrey brown orange}
1489
1490 catch {source ~/.gitk}
1491
1492 set namefont $mainfont
1493 if {$boldnames} {
1494     lappend namefont bold
1495 }
1496
1497 set revtreeargs {}
1498 foreach arg $argv {
1499     switch -regexp -- $arg {
1500         "^$" { }
1501         "^-b" { set boldnames 1 }
1502         "^-d" { set datemode 1 }
1503         default {
1504             lappend revtreeargs $arg
1505         }
1506     }
1507 }
1508
1509 set noreadobj [load libreadobj.so.0.0]
1510 set noreadobj 0
1511 set stopped 0
1512 set redisplaying 0
1513 set stuffsaved 0
1514 setcoords
1515 makewindow
1516 readrefs
1517 getcommits $revtreeargs