1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec 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.24 $
11
12proc getcommits {rargs} {
13 global commits commfd phase canv mainfont
14 global startmsecs nextupdate
15 global ctext maincursor textcursor nlines
16
17 set commits {}
18 set phase getcommits
19 set startmsecs [clock clicks -milliseconds]
20 set nextupdate [expr $startmsecs + 100]
21 if [catch {
22 set parse_args [concat --default HEAD --merge-order $rargs]
23 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
24 }] {
25 if {$rargs == {}} {
26 set rargs HEAD
27 }
28 set parsed_args [concat --merge-order $rargs]
29 }
30 if [catch {
31 set commfd [open "|git-rev-list $parsed_args" r]
32 } err] {
33 puts stderr "Error executing git-rev-list: $err"
34 exit 1
35 }
36 set nlines 0
37 fconfigure $commfd -blocking 0
38 fileevent $commfd readable "getcommitline $commfd"
39 $canv delete all
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
44}
45
46proc getcommitline {commfd} {
47 global commits parents cdate children nchildren
48 global commitlisted phase commitinfo nextupdate
49 global stopped redisplaying nlines
50
51 set n [gets $commfd line]
52 if {$n < 0} {
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
58 return
59 }
60 if {[string range $err 0 4] == "usage"} {
61 set err \
62{Gitk: error reading commits: bad arguments to git-rev-list.
63(Note: arguments to gitk are passed to git-rev-list
64to allow selection of commits to be displayed.)}
65 } else {
66 set err "Error reading commits: $err"
67 }
68 error_popup $err
69 exit 1
70 }
71 incr nlines
72 if {![regexp {^[0-9a-f]{40}$} $line id]} {
73 error_popup "Can't parse git-rev-list output: {$line}"
74 exit 1
75 }
76 lappend commits $id
77 set commitlisted($id) 1
78 if {![info exists commitinfo($id)]} {
79 readcommit $id
80 }
81 foreach p $parents($id) {
82 if {[info exists commitlisted($p)]} {
83 puts "oops, parent $p before child $id"
84 }
85 }
86 drawcommit $id
87 if {[clock clicks -milliseconds] >= $nextupdate} {
88 doupdate
89 }
90 while {$redisplaying} {
91 set redisplaying 0
92 if {$stopped == 1} {
93 set stopped 0
94 set phase "getcommits"
95 foreach id $commits {
96 drawcommit $id
97 if {$stopped} break
98 if {[clock clicks -milliseconds] >= $nextupdate} {
99 doupdate
100 }
101 }
102 }
103 }
104}
105
106proc doupdate {} {
107 global commfd nextupdate
108
109 incr nextupdate 100
110 fileevent $commfd readable {}
111 update
112 fileevent $commfd readable "getcommitline $commfd"
113}
114
115proc readcommit {id} {
116 global commitinfo children nchildren parents nparents cdate ncleft
117 global noreadobj
118
119 set inhdr 1
120 set comment {}
121 set headline {}
122 set auname {}
123 set audate {}
124 set comname {}
125 set comdate {}
126 if {![info exists nchildren($id)]} {
127 set children($id) {}
128 set nchildren($id) 0
129 set ncleft($id) 0
130 }
131 set parents($id) {}
132 set nparents($id) 0
133 if {$noreadobj} {
134 if [catch {set contents [exec git-cat-file commit $id]}] return
135 } else {
136 if [catch {set x [readobj $id]}] return
137 if {[lindex $x 0] != "commit"} return
138 set contents [lindex $x 1]
139 }
140 foreach line [split $contents "\n"] {
141 if {$inhdr} {
142 if {$line == {}} {
143 set inhdr 0
144 } else {
145 set tag [lindex $line 0]
146 if {$tag == "parent"} {
147 set p [lindex $line 1]
148 if {![info exists nchildren($p)]} {
149 set children($p) {}
150 set nchildren($p) 0
151 set ncleft($p) 0
152 }
153 lappend parents($id) $p
154 incr nparents($id)
155 # sometimes we get a commit that lists a parent twice...
156 if {[lsearch -exact $children($p) $id] < 0} {
157 lappend children($p) $id
158 incr nchildren($p)
159 incr ncleft($p)
160 }
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}]]
169 }
170 }
171 } else {
172 if {$comment == {}} {
173 set headline $line
174 } else {
175 append comment "\n"
176 }
177 append comment $line
178 }
179 }
180 if {$audate != {}} {
181 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
182 }
183 if {$comdate != {}} {
184 set cdate($id) $comdate
185 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
186 }
187 set commitinfo($id) [list $headline $auname $audate \
188 $comname $comdate $comment]
189}
190
191proc readrefs {} {
192 global tagids idtags headids idheads
193 set tags [glob -nocomplain -types f .git/refs/tags/*]
194 foreach f $tags {
195 catch {
196 set fd [open $f r]
197 set line [read $fd]
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"]
203 set obj {}
204 set type {}
205 set tag {}
206 foreach l $contents {
207 if {$l == {}} break
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]}
212 }
213 }
214 if {$obj != {} && $type == "commit" && $tag != {}} {
215 set tagids($tag) $obj
216 lappend idtags($obj) $tag
217 }
218 }
219 close $fd
220 }
221 }
222 set heads [glob -nocomplain -types f .git/refs/heads/*]
223 foreach f $heads {
224 catch {
225 set fd [open $f r]
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
231 }
232 close $fd
233 }
234 }
235}
236
237proc error_popup msg {
238 set w .error
239 toplevel $w
240 wm transient $w .
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"
246 tkwait window $w
247}
248
249proc makewindow {} {
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
254 global linectxmenu
255
256 menu .bar
257 .bar add cascade -label "File" -menu .bar.file
258 menu .bar.file
259 .bar.file add command -label "Quit" -command doquit
260 menu .bar.help
261 .bar add cascade -label "Help" -menu .bar.help
262 .bar.help add command -label "About gitk" -command about
263 . configure -menu .bar
264
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
273 }
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]}]
280 }
281 frame .ctop.top
282 frame .ctop.top.bar
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
289 .ctop add .ctop.top
290 set canv .ctop.top.clist.canv
291 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
292 -bg white -bd 0 \
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}
304
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
317 set findstring {}
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
322 set findtype Exact
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
329
330 panedwindow .ctop.cdet -orient horizontal
331 .ctop add .ctop.cdet
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
341
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
347
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}
357
358 pack .ctop -side top -fill both -expand 1
359
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"
378 bindkey / findnext
379 bindkey ? findprev
380 bindkey f nextfile
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
394
395 set maincursor [. cget -cursor]
396 set textcursor [$ctext cget -cursor]
397
398 set linectxmenu .linectxmenu
399 menu $linectxmenu -tearoff 0
400 $linectxmenu add command -label "Select" -command lineselect
401}
402
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.
406proc bindkey {ev script} {
407 global entries
408 bind . $ev $script
409 set escript [bind Entry $ev]
410 if {$escript == {}} {
411 set escript [bind Entry <Key>]
412 }
413 foreach e $entries {
414 bind $e $ev "$escript; break"
415 }
416}
417
418# set the focus back to the toplevel for any click outside
419# the entry widgets
420proc click {w} {
421 global entries
422 foreach e $entries {
423 if {$w == $e} return
424 }
425 focus .
426}
427
428proc savestuff {w} {
429 global canv canv2 canv3 ctext cflist mainfont textfont
430 global stuffsaved
431 if {$stuffsaved} return
432 if {![winfo viewable .]} return
433 catch {
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"
449 close $f
450 file rename -force "~/.gitk-new" "~/.gitk"
451 }
452 set stuffsaved 1
453}
454
455proc resizeclistpanes {win w} {
456 global oldwidth
457 if [info exists oldwidth($win)] {
458 set s0 [$win sash coord 0]
459 set s1 [$win sash coord 1]
460 if {$w < 60} {
461 set sash0 [expr {int($w/2 - 2)}]
462 set sash1 [expr {int($w*5/6 - 2)}]
463 } else {
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])}]
467 if {$sash0 < 30} {
468 set sash0 30
469 }
470 if {$sash1 < $sash0 + 20} {
471 set sash1 [expr $sash0 + 20]
472 }
473 if {$sash1 > $w - 10} {
474 set sash1 [expr $w - 10]
475 if {$sash0 > $sash1 - 20} {
476 set sash0 [expr $sash1 - 20]
477 }
478 }
479 }
480 $win sash place 0 $sash0 [lindex $s0 1]
481 $win sash place 1 $sash1 [lindex $s1 1]
482 }
483 set oldwidth($win) $w
484}
485
486proc resizecdetpanes {win w} {
487 global oldwidth
488 if [info exists oldwidth($win)] {
489 set s0 [$win sash coord 0]
490 if {$w < 60} {
491 set sash0 [expr {int($w*3/4 - 2)}]
492 } else {
493 set factor [expr {1.0 * $w / $oldwidth($win)}]
494 set sash0 [expr {int($factor * [lindex $s0 0])}]
495 if {$sash0 < 45} {
496 set sash0 45
497 }
498 if {$sash0 > $w - 15} {
499 set sash0 [expr $w - 15]
500 }
501 }
502 $win sash place 0 $sash0 [lindex $s0 1]
503 }
504 set oldwidth($win) $w
505}
506
507proc allcanvs args {
508 global canv canv2 canv3
509 eval $canv $args
510 eval $canv2 $args
511 eval $canv3 $args
512}
513
514proc bindall {event action} {
515 global canv canv2 canv3
516 bind $canv $event $action
517 bind $canv2 $event $action
518 bind $canv3 $event $action
519}
520
521proc about {} {
522 set w .about
523 if {[winfo exists $w]} {
524 raise $w
525 return
526 }
527 toplevel $w
528 wm title $w "About gitk"
529 message $w.m -text {
530Gitk version 1.1
531
532Copyright © 2005 Paul Mackerras
533
534Use and redistribute under the terms of the GNU General Public License
535
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
541}
542
543proc 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)
553 return
554 }
555 }
556 set badcolors {}
557 foreach child $children($id) {
558 if {[info exists colormap($child)]
559 && [lsearch -exact $badcolors $colormap($child)] < 0} {
560 lappend badcolors $colormap($child)
561 }
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)
567 }
568 }
569 }
570 }
571 if {[llength $badcolors] >= $ncolors} {
572 set badcolors {}
573 }
574 for {set i 0} {$i <= $ncolors} {incr i} {
575 set c [lindex $colors $nextcolor]
576 if {[incr nextcolor] >= $ncolors} {
577 set nextcolor 0
578 }
579 if {[lsearch -exact $badcolors $c]} break
580 }
581 set colormap($id) $c
582}
583
584proc initgraph {} {
585 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
586 global glines
587 global nchildren ncleft
588
589 allcanvs delete all
590 set nextcolor 0
591 set canvy $canvy0
592 set lineno -1
593 set numcommits 0
594 set lthickness [expr {int($linespc / 9) + 1}]
595 catch {unset glines}
596 foreach id [array names nchildren] {
597 set ncleft($id) $nchildren($id)
598 }
599}
600
601proc bindline {t id} {
602 global canv
603
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"
608}
609
610proc 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
619 global commitlisted
620
621 incr numcommits
622 incr lineno
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)]} {
628 readcommit $id
629 if {![info exists commitinfo($id)]} {
630 set commitinfo($id) {"No commit information available"}
631 set nparents($id) 0
632 }
633 }
634 set currentparents {}
635 set dupparents {}
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
640 } else {
641 # remember that this parent was listed twice
642 lappend dupparents $p
643 }
644 }
645 }
646 set x [expr $canvx0 + $level * $linespc]
647 set y1 $canvy
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)]
655 $canv lower $t
656 bindline $t $id
657 }
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]
662 $canv raise $t
663 set xt [expr $canvx0 + [llength $todo] * $linespc]
664 if {$nparents($id) > 2} {
665 set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
666 }
667 set marks {}
668 set ntags 0
669 if {[info exists idtags($id)]} {
670 set marks $idtags($id)
671 set ntags [llength $marks]
672 }
673 if {[info exists idheads($id)]} {
674 set marks [concat $marks $idheads($id)]
675 }
676 if {$marks != {}} {
677 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
678 set yt [expr $y1 - 0.5 * $linespc]
679 set yb [expr $yt + $linespc - 1]
680 set xvals {}
681 set wvals {}
682 foreach tag $marks {
683 set wid [font measure $mainfont $tag]
684 lappend xvals $xt
685 lappend wvals $wid
686 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
687 }
688 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
689 -width $lthickness -fill black]
690 $canv lower $t
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} {
695 # draw a tag
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
699 } else {
700 # draw a head
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
704 }
705 $canv create text $xl $y1 -anchor w -text $tag \
706 -font $mainfont
707 }
708 }
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]
718}
719
720proc updatetodo {level noshortcut} {
721 global datemode currentparents ncleft todo
722 global glines oldlevel oldtodo oldnlines
723 global canvx0 canvy linespc glines
724 global commitinfo
725
726 foreach p $currentparents {
727 if {![info exists commitinfo($p)]} {
728 readcommit $p
729 }
730 }
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} {
736 assigncolor $p
737 set glines($p) [list $x $y]
738 set todo [lreplace $todo $level $level $p]
739 return 0
740 }
741 }
742
743 set oldlevel $level
744 set oldtodo $todo
745 set oldnlines [llength $todo]
746 set todo [lreplace $todo $level $level]
747 set i $level
748 foreach p $currentparents {
749 incr ncleft($p) -1
750 set k [lsearch -exact $todo $p]
751 if {$k < 0} {
752 assigncolor $p
753 set todo [linsert $todo $i $p]
754 incr i
755 }
756 }
757 return 1
758}
759
760proc drawslants {} {
761 global canv glines canvx0 canvy linespc
762 global oldlevel oldtodo todo currentparents dupparents
763 global lthickness linespc canvy colormap
764
765 set y1 [expr $canvy - $linespc]
766 set y2 $canvy
767 set i -1
768 foreach id $oldtodo {
769 incr i
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}]
777 if {$j < $i - 1} {
778 lappend coords [expr $xj + $linespc] $y1
779 } elseif {$j > $i + 1} {
780 lappend coords [expr $xj - $linespc] $y1
781 }
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)]
787 $canv lower $t
788 bindline $t $p
789 if {![info exists glines($p)]} {
790 set glines($p) [list $xj $y2]
791 }
792 } else {
793 # normal case, no parent duplicated
794 if {![info exists glines($p)]} {
795 if {$i != $j} {
796 lappend coords $xj $y2
797 }
798 set glines($p) $coords
799 } else {
800 lappend coords $xj $y2
801 set t [$canv create line $coords \
802 -width $lthickness -fill $colormap($p)]
803 $canv lower $t
804 bindline $t $p
805 }
806 }
807 }
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
812 }
813 }
814}
815
816proc decidenext {} {
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
823
824 # remove the null entry if present
825 set nullentry [lsearch -exact $todo {}]
826 if {$nullentry >= 0} {
827 set todo [lreplace $todo $nullentry $nullentry]
828 }
829
830 # choose which one to do next time around
831 set todol [llength $todo]
832 set level -1
833 set latest {}
834 for {set k $todol} {[incr k -1] >= 0} {} {
835 set p [lindex $todo $k]
836 if {$ncleft($p) == 0} {
837 if {$datemode} {
838 if {$latest == {} || $cdate($p) > $latest} {
839 set level $k
840 set latest $cdate($p)
841 }
842 } else {
843 set level $k
844 break
845 }
846 }
847 }
848 if {$level < 0} {
849 if {$todo != {}} {
850 puts "ERROR: none of the pending commits can be done yet:"
851 foreach p $todo {
852 puts " $p"
853 }
854 }
855 return -1
856 }
857
858 # If we are reducing, put in a null entry
859 if {$todol < $oldnlines} {
860 if {$nullentry >= 0} {
861 set i $nullentry
862 while {$i < $todol
863 && [lindex $oldtodo $i] == [lindex $todo $i]} {
864 incr i
865 }
866 } else {
867 set i $oldlevel
868 if {$level >= $i} {
869 incr i
870 }
871 }
872 if {$i < $todol} {
873 set todo [linsert $todo $i {}]
874 if {$level >= $i} {
875 incr level
876 }
877 }
878 }
879 return $level
880}
881
882proc drawcommit {id} {
883 global phase todo nchildren datemode nextupdate
884 global startcommits
885
886 if {$phase != "incrdraw"} {
887 set phase incrdraw
888 set todo $id
889 set startcommits $id
890 initgraph
891 assigncolor $id
892 drawcommitline 0
893 updatetodo 0 $datemode
894 } else {
895 if {$nchildren($id) == 0} {
896 lappend todo $id
897 lappend startcommits $id
898 assigncolor $id
899 }
900 set level [decidenext]
901 if {$id != [lindex $todo $level]} {
902 return
903 }
904 while 1 {
905 drawslants
906 drawcommitline $level
907 if {[updatetodo $level $datemode]} {
908 set level [decidenext]
909 }
910 set id [lindex $todo $level]
911 if {![info exists commitlisted($id)]} {
912 break
913 }
914 if {[clock clicks -milliseconds] >= $nextupdate} {
915 doupdate
916 if {$stopped} break
917 }
918 }
919 }
920}
921
922proc finishcommits {} {
923 global phase
924 global startcommits
925 global ctext maincursor textcursor
926
927 if {$phase != "incrdraw"} {
928 $canv delete all
929 $canv create text 3 3 -anchor nw -text "No commits selected" \
930 -font $mainfont -tags textitems
931 set phase {}
932 return
933 }
934 drawslants
935 set level [decidenext]
936 drawrest $level [llength $startcommits]
937 . config -cursor $maincursor
938 $ctext config -cursor $textcursor
939}
940
941proc drawgraph {} {
942 global nextupdate startmsecs startcommits todo
943
944 if {$startcommits == {}} return
945 set startmsecs [clock clicks -milliseconds]
946 set nextupdate [expr $startmsecs + 100]
947 initgraph
948 set todo [lindex $startcommits 0]
949 drawrest 0 1
950}
951
952proc drawrest {level startix} {
953 global phase stopped redisplaying selectedline
954 global datemode currentparents todo
955 global numcommits
956 global nextupdate startmsecs startcommits idline
957
958 if {$level >= 0} {
959 set phase drawgraph
960 set startid [lindex $startcommits $startix]
961 set startline -1
962 if {$startid != {}} {
963 set startline $idline($startid)
964 }
965 while 1 {
966 if {$stopped} break
967 drawcommitline $level
968 set hard [updatetodo $level $datemode]
969 if {$numcommits == $startline} {
970 lappend todo $startid
971 set hard 1
972 incr startix
973 set startid [lindex $startcommits $startix]
974 set startline -1
975 if {$startid != {}} {
976 set startline $idline($startid)
977 }
978 }
979 if {$hard} {
980 set level [decidenext]
981 if {$level < 0} break
982 drawslants
983 }
984 if {[clock clicks -milliseconds] >= $nextupdate} {
985 update
986 incr nextupdate 100
987 }
988 }
989 }
990 set phase {}
991 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
992 #puts "overall $drawmsecs ms for $numcommits commits"
993 if {$redisplaying} {
994 if {$stopped == 0 && [info exists selectedline]} {
995 selectline $selectedline
996 }
997 if {$stopped == 1} {
998 set stopped 0
999 after idle drawgraph
1000 } else {
1001 set redisplaying 0
1002 }
1003 }
1004}
1005
1006proc findmatches {f} {
1007 global findtype foundstring foundstrlen
1008 if {$findtype == "Regexp"} {
1009 set matches [regexp -indices -all -inline $foundstring $f]
1010 } else {
1011 if {$findtype == "IgnCase"} {
1012 set str [string tolower $f]
1013 } else {
1014 set str $f
1015 }
1016 set matches {}
1017 set i 0
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]
1021 }
1022 }
1023 return $matches
1024}
1025
1026proc dofind {} {
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
1031 unmarkmatches
1032 focus .
1033 set matchinglines {}
1034 set fldtypes {Headline Author Date Committer CDate Comment}
1035 if {$findtype == "IgnCase"} {
1036 set foundstring [string tolower $findstring]
1037 } else {
1038 set foundstring $findstring
1039 }
1040 set foundstrlen [string length $findstring]
1041 if {$foundstrlen == 0} return
1042 if {![info exists selectedline]} {
1043 set oldsel -1
1044 } else {
1045 set oldsel $selectedline
1046 }
1047 set didsel 0
1048 for {set l 0} {$l < $numcommits} {incr l} {
1049 set id $lineid($l)
1050 set info $commitinfo($id)
1051 set doesmatch 0
1052 foreach f $info ty $fldtypes {
1053 if {$findloc != "All fields" && $findloc != $ty} {
1054 continue
1055 }
1056 set matches [findmatches $f]
1057 if {$matches == {}} continue
1058 set doesmatch 1
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
1065 }
1066 }
1067 if {$doesmatch} {
1068 lappend matchinglines $l
1069 if {!$didsel && $l > $oldsel} {
1070 findselectline $l
1071 set didsel 1
1072 }
1073 }
1074 }
1075 if {$matchinglines == {}} {
1076 bell
1077 } elseif {!$didsel} {
1078 findselectline [lindex $matchinglines 0]
1079 }
1080}
1081
1082proc findselectline {l} {
1083 global findloc commentend ctext
1084 selectline $l
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"
1093 }
1094 }
1095}
1096
1097proc findnext {} {
1098 global matchinglines selectedline
1099 if {![info exists matchinglines]} {
1100 dofind
1101 return
1102 }
1103 if {![info exists selectedline]} return
1104 foreach l $matchinglines {
1105 if {$l > $selectedline} {
1106 findselectline $l
1107 return
1108 }
1109 }
1110 bell
1111}
1112
1113proc findprev {} {
1114 global matchinglines selectedline
1115 if {![info exists matchinglines]} {
1116 dofind
1117 return
1118 }
1119 if {![info exists selectedline]} return
1120 set prev {}
1121 foreach l $matchinglines {
1122 if {$l >= $selectedline} break
1123 set prev $l
1124 }
1125 if {$prev != {}} {
1126 findselectline $prev
1127 } else {
1128 bell
1129 }
1130}
1131
1132proc 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]
1145 $canv lower $t
1146 }
1147}
1148
1149proc unmarkmatches {} {
1150 global matchinglines
1151 allcanvs delete matches
1152 catch {unset matchinglines}
1153}
1154
1155proc 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)}]
1163 if {$l < 0} {
1164 set l 0
1165 }
1166 if {[info exists selectedline] && $selectedline == $l} return
1167 unmarkmatches
1168 selectline $l
1169}
1170
1171proc 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
1177 $canv delete hover
1178 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1179 $canv delete secsel
1180 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1181 -tags secsel -fill [$canv cget -selectbackground]]
1182 $canv lower $t
1183 $canv2 delete secsel
1184 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1185 -tags secsel -fill [$canv2 cget -selectbackground]]
1186 $canv2 lower $t
1187 $canv3 delete secsel
1188 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1189 -tags secsel -fill [$canv3 cget -selectbackground]]
1190 $canv3 lower $t
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}]
1199 set newtop $wtop
1200 if {$ytop < $wtop} {
1201 if {$ybot < $wtop} {
1202 set newtop [expr {$y - $wh / 2.0}]
1203 } else {
1204 set newtop $ytop
1205 if {$newtop > $wtop - $linespc} {
1206 set newtop [expr {$wtop - $linespc}]
1207 }
1208 }
1209 } elseif {$ybot > $wbot} {
1210 if {$ytop > $wbot} {
1211 set newtop [expr {$y - $wh / 2.0}]
1212 } else {
1213 set newtop [expr {$ybot - $wh}]
1214 if {$newtop < $wtop + $linespc} {
1215 set newtop [expr {$wtop + $linespc}]
1216 }
1217 }
1218 }
1219 if {$newtop != $wtop} {
1220 if {$newtop < 0} {
1221 set newtop 0
1222 }
1223 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1224 }
1225 set selectedline $l
1226
1227 set id $lineid($l)
1228 set currentid $id
1229 $sha1entry delete 0 end
1230 $sha1entry insert 0 $id
1231 $sha1entry selection from 0
1232 $sha1entry selection to end
1233
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"
1243 }
1244 $ctext insert end "\n"
1245 }
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"]
1253
1254 $cflist delete 0 end
1255 if {$nparents($id) == 1} {
1256 if {![info exists treediffs($id)]} {
1257 if {![info exists treepending]} {
1258 gettreediffs $id
1259 }
1260 } else {
1261 addtocflist $id
1262 }
1263 }
1264 catch {unset seenfile}
1265}
1266
1267proc selnextline {dir} {
1268 global selectedline
1269 if {![info exists selectedline]} return
1270 set l [expr $selectedline + $dir]
1271 unmarkmatches
1272 selectline $l
1273}
1274
1275proc addtocflist {id} {
1276 global currentid treediffs cflist treepending
1277 if {$id != $currentid} {
1278 gettreediffs $currentid
1279 return
1280 }
1281 $cflist insert end "All files"
1282 foreach f $treediffs($currentid) {
1283 $cflist insert end $f
1284 }
1285 getblobdiffs $id
1286}
1287
1288proc gettreediffs {id} {
1289 global treediffs parents treepending
1290 set treepending $id
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"
1296}
1297
1298proc gettreediffline {gdtf id} {
1299 global treediffs treepending
1300 set n [gets $gdtf line]
1301 if {$n < 0} {
1302 if {![eof $gdtf]} return
1303 close $gdtf
1304 unset treepending
1305 addtocflist $id
1306 return
1307 }
1308 set file [lindex $line 5]
1309 lappend treediffs($id) $file
1310}
1311
1312proc 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"
1319 return
1320 }
1321 fconfigure $bdf -blocking 0
1322 set blobdifffd($id) $bdf
1323 set curdifftag Comments
1324 set curtagstart 0.0
1325 set diffindex 0
1326 catch {unset difffilestart}
1327 fileevent $bdf readable "getblobdiffline $bdf $id"
1328}
1329
1330proc getblobdiffline {bdf id} {
1331 global currentid blobdifffd ctext curdifftag curtagstart seenfile
1332 global diffnexthead diffnextnote diffindex difffilestart
1333 set n [gets $bdf line]
1334 if {$n < 0} {
1335 if {[eof $bdf]} {
1336 close $bdf
1337 if {$id == $currentid && $bdf == $blobdifffd($id)} {
1338 $ctext tag add $curdifftag $curtagstart end
1339 set seenfile($curdifftag) 1
1340 }
1341 }
1342 return
1343 }
1344 if {$id != $currentid || $bdf != $blobdifffd($id)} {
1345 return
1346 }
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"]
1354 set header $fname
1355 if {[info exists diffnexthead]} {
1356 set fname $diffnexthead
1357 set header "$diffnexthead ($diffnextnote)"
1358 unset diffnexthead
1359 }
1360 set difffilestart($diffindex) [$ctext index "end - 1c"]
1361 incr diffindex
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
1388 } else {
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
1400 } else {
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
1408 }
1409 $ctext insert end "$line\n" filesep
1410 }
1411 }
1412 $ctext conf -state disabled
1413}
1414
1415proc nextfile {} {
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)
1421 break
1422 }
1423 }
1424}
1425
1426proc 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} {
1431 # show everything
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
1436 }
1437 }
1438 } else {
1439 # just show selected files
1440 $ctext tag conf Comments -elide 1
1441 set i 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
1446 }
1447 incr i
1448 }
1449 }
1450}
1451
1452proc setcoords {} {
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]
1458}
1459
1460proc redisplay {} {
1461 global selectedline stopped redisplaying phase
1462 if {$stopped > 1} return
1463 if {$phase == "getcommits"} return
1464 set redisplaying 1
1465 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1466 set stopped 1
1467 } else {
1468 drawgraph
1469 }
1470}
1471
1472proc incrfont {inc} {
1473 global mainfont namefont textfont selectedline ctext canv phase
1474 global stopped entries
1475 unmarkmatches
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}]]
1479 setcoords
1480 $ctext conf -font $textfont
1481 $ctext tag conf filesep -font [concat $textfont bold]
1482 foreach e $entries {
1483 $e conf -font $mainfont
1484 }
1485 if {$phase == "getcommits"} {
1486 $canv itemconf textitems -font $mainfont
1487 }
1488 redisplay
1489}
1490
1491proc sha1change {n1 n2 op} {
1492 global sha1string currentid sha1but
1493 if {$sha1string == {}
1494 || ([info exists currentid] && $sha1string == $currentid)} {
1495 set state disabled
1496 } else {
1497 set state normal
1498 }
1499 if {[$sha1but cget -state] == $state} return
1500 if {$state == "normal"} {
1501 $sha1but conf -state normal -relief raised -text "Goto: "
1502 } else {
1503 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1504 }
1505}
1506
1507proc 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)
1513 } else {
1514 set id [string tolower $sha1string]
1515 }
1516 if {[info exists idline($id)]} {
1517 selectline $idline($id)
1518 return
1519 }
1520 if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
1521 set type "SHA1 id"
1522 } else {
1523 set type "Tag"
1524 }
1525 error_popup "$type $sha1string is not known"
1526}
1527
1528proc linemenu {x y id} {
1529 global linectxmenu linemenuid
1530 set linemenuid $id
1531 $linectxmenu post $x $y
1532}
1533
1534proc lineselect {} {
1535 global linemenuid idline
1536 if {[info exists linemenuid] && [info exists idline($linemenuid)]} {
1537 selectline $idline($linemenuid)
1538 }
1539}
1540
1541proc lineenter {x y id} {
1542 global hoverx hovery hoverid hovertimer
1543 global commitinfo canv
1544
1545 if {![info exists commitinfo($id)]} return
1546 set hoverx $x
1547 set hovery $y
1548 set hoverid $id
1549 if {[info exists hovertimer]} {
1550 after cancel $hovertimer
1551 }
1552 set hovertimer [after 500 linehover]
1553 $canv delete hover
1554}
1555
1556proc linemotion {x y id} {
1557 global hoverx hovery hoverid hovertimer
1558
1559 if {[info exists hoverid] && $id == $hoverid} {
1560 set hoverx $x
1561 set hovery $y
1562 if {[info exists hovertimer]} {
1563 after cancel $hovertimer
1564 }
1565 set hovertimer [after 500 linehover]
1566 }
1567}
1568
1569proc lineleave {id} {
1570 global hoverid hovertimer canv
1571
1572 if {[info exists hoverid] && $id == $hoverid} {
1573 $canv delete hover
1574 if {[info exists hovertimer]} {
1575 after cancel $hovertimer
1576 unset hovertimer
1577 }
1578 unset hoverid
1579 }
1580}
1581
1582proc linehover {} {
1583 global hoverx hovery hoverid hovertimer
1584 global canv linespc lthickness
1585 global commitinfo mainfont
1586
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]
1599 $canv raise $t
1600 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
1601 $canv raise $t
1602}
1603
1604proc doquit {} {
1605 global stopped
1606 set stopped 100
1607 destroy .
1608}
1609
1610# defaults...
1611set datemode 0
1612set boldnames 0
1613set diffopts "-U 5 -p"
1614
1615set mainfont {Helvetica 9}
1616set textfont {Courier 9}
1617
1618set colors {green red blue magenta darkgrey brown orange}
1619
1620catch {source ~/.gitk}
1621
1622set namefont $mainfont
1623if {$boldnames} {
1624 lappend namefont bold
1625}
1626
1627set revtreeargs {}
1628foreach arg $argv {
1629 switch -regexp -- $arg {
1630 "^$" { }
1631 "^-b" { set boldnames 1 }
1632 "^-d" { set datemode 1 }
1633 default {
1634 lappend revtreeargs $arg
1635 }
1636 }
1637}
1638
1639set noreadobj [catch {load libreadobj.so.0.0}]
1640set stopped 0
1641set redisplaying 0
1642set stuffsaved 0
1643setcoords
1644makewindow
1645readrefs
1646getcommits $revtreeargs