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