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.10 $
11
12proc getcommits {rargs} {
13 global commits commfd phase canv mainfont
14 if {$rargs == {}} {
15 set rargs HEAD
16 }
17 set commits {}
18 set phase getcommits
19 if [catch {set commfd [open "|git-rev-tree $rargs" r]} err] {
20 puts stder "Error executing git-rev-tree: $err"
21 exit 1
22 }
23 fconfigure $commfd -blocking 0
24 fileevent $commfd readable "getcommitline $commfd"
25 $canv delete all
26 $canv create text 3 3 -anchor nw -text "Reading commits..." \
27 -font $mainfont -tags textitems
28}
29
30proc getcommitline {commfd} {
31 global commits parents cdate nparents children nchildren
32 set n [gets $commfd line]
33 if {$n < 0} {
34 if {![eof $commfd]} return
35 if {![catch {close $commfd} err]} {
36 after idle drawgraph
37 return
38 }
39 if {[string range $err 0 4] == "usage"} {
40 puts stderr "Error reading commits: bad arguments to git-rev-tree"
41 puts stderr "Note: arguments to gitk are passed to git-rev-tree"
42 puts stderr " to allow selection of commits to be displayed"
43 } else {
44 puts stderr "Error reading commits: $err"
45 }
46 exit 1
47 }
48
49 set i 0
50 set cid {}
51 foreach f $line {
52 if {$i == 0} {
53 set d $f
54 } else {
55 set id [lindex [split $f :] 0]
56 if {![info exists nchildren($id)]} {
57 set children($id) {}
58 set nchildren($id) 0
59 }
60 if {$i == 1} {
61 set cid $id
62 lappend commits $id
63 set parents($id) {}
64 set cdate($id) $d
65 set nparents($id) 0
66 } else {
67 lappend parents($cid) $id
68 incr nparents($cid)
69 incr nchildren($id)
70 lappend children($id) $cid
71 }
72 }
73 incr i
74 }
75}
76
77proc readcommit {id} {
78 global commitinfo
79 set inhdr 1
80 set comment {}
81 set headline {}
82 set auname {}
83 set audate {}
84 set comname {}
85 set comdate {}
86 foreach line [split [exec git-cat-file commit $id] "\n"] {
87 if {$inhdr} {
88 if {$line == {}} {
89 set inhdr 0
90 } else {
91 set tag [lindex $line 0]
92 if {$tag == "author"} {
93 set x [expr {[llength $line] - 2}]
94 set audate [lindex $line $x]
95 set auname [lrange $line 1 [expr {$x - 1}]]
96 } elseif {$tag == "committer"} {
97 set x [expr {[llength $line] - 2}]
98 set comdate [lindex $line $x]
99 set comname [lrange $line 1 [expr {$x - 1}]]
100 }
101 }
102 } else {
103 if {$comment == {}} {
104 set headline $line
105 } else {
106 append comment "\n"
107 }
108 append comment $line
109 }
110 }
111 if {$audate != {}} {
112 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
113 }
114 if {$comdate != {}} {
115 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
116 }
117 set commitinfo($id) [list $headline $auname $audate \
118 $comname $comdate $comment]
119}
120
121proc makewindow {} {
122 global canv canv2 canv3 linespc charspc ctext cflist textfont
123 global sha1entry findtype findloc findstring
124
125 menu .bar
126 .bar add cascade -label "File" -menu .bar.file
127 menu .bar.file
128 .bar.file add command -label "Quit" -command doquit
129 menu .bar.help
130 .bar add cascade -label "Help" -menu .bar.help
131 .bar.help add command -label "About gitk" -command about
132 . configure -menu .bar
133
134 panedwindow .ctop -orient vertical
135 frame .ctop.top
136 frame .ctop.top.bar
137 pack .ctop.top.bar -side bottom -fill x
138 set cscroll .ctop.top.csb
139 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
140 pack $cscroll -side right -fill y
141 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
142 pack .ctop.top.clist -side top -fill both -expand 1
143 .ctop add .ctop.top
144 set canv .ctop.top.clist.canv
145 set height [expr 25 * $linespc + 4]
146 canvas $canv -height $height -width [expr 45 * $charspc] \
147 -bg white -bd 0 \
148 -yscrollincr $linespc -yscrollcommand "$cscroll set"
149 .ctop.top.clist add $canv
150 set canv2 .ctop.top.clist.canv2
151 canvas $canv2 -height $height -width [expr 30 * $charspc] \
152 -bg white -bd 0 -yscrollincr $linespc
153 .ctop.top.clist add $canv2
154 set canv3 .ctop.top.clist.canv3
155 canvas $canv3 -height $height -width [expr 15 * $charspc] \
156 -bg white -bd 0 -yscrollincr $linespc
157 .ctop.top.clist add $canv3
158
159 set sha1entry .ctop.top.bar.sha1
160 label .ctop.top.bar.sha1label -text "SHA1 ID: "
161 pack .ctop.top.bar.sha1label -side left
162 entry $sha1entry -width 40 -font $textfont -state readonly
163 pack $sha1entry -side left -pady 2
164 button .ctop.top.bar.findbut -text "Find" -command dofind
165 pack .ctop.top.bar.findbut -side left
166 set findstring {}
167 entry .ctop.top.bar.findstring -width 30 -font $textfont \
168 -textvariable findstring
169 pack .ctop.top.bar.findstring -side left -expand 1 -fill x
170 set findtype Exact
171 tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
172 set findloc "All fields"
173 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
174 Comments Author Committer
175 pack .ctop.top.bar.findloc -side right
176 pack .ctop.top.bar.findtype -side right
177
178 panedwindow .ctop.cdet -orient horizontal
179 .ctop add .ctop.cdet
180 frame .ctop.cdet.left
181 set ctext .ctop.cdet.left.ctext
182 text $ctext -bg white -state disabled -font $textfont -height 32 \
183 -yscrollcommand ".ctop.cdet.left.sb set"
184 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
185 pack .ctop.cdet.left.sb -side right -fill y
186 pack $ctext -side left -fill both -expand 1
187 .ctop.cdet add .ctop.cdet.left
188
189 $ctext tag conf filesep -font [concat $textfont bold]
190 $ctext tag conf hunksep -back blue -fore white
191 $ctext tag conf d0 -back "#ff8080"
192 $ctext tag conf d1 -back green
193
194 frame .ctop.cdet.right
195 set cflist .ctop.cdet.right.cfiles
196 listbox $cflist -width 30 -bg white -selectmode extended \
197 -yscrollcommand ".ctop.cdet.right.sb set"
198 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
199 pack .ctop.cdet.right.sb -side right -fill y
200 pack $cflist -side left -fill both -expand 1
201 .ctop.cdet add .ctop.cdet.right
202
203 pack .ctop -side top -fill both -expand 1
204
205 bindall <1> {selcanvline %x %y}
206 bindall <B1-Motion> {selcanvline %x %y}
207 bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
208 bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
209 bindall <2> "allcanvs scan mark 0 %y"
210 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
211 bind . <Key-Up> "selnextline -1"
212 bind . <Key-Down> "selnextline 1"
213 bind . p "selnextline -1"
214 bind . n "selnextline 1"
215 bind . <Key-Prior> "allcanvs yview scroll -1 p"
216 bind . <Key-Next> "allcanvs yview scroll 1 p"
217 bind . <Key-Delete> "$ctext yview scroll -1 p"
218 bind . <Key-BackSpace> "$ctext yview scroll -1 p"
219 bind . <Key-space> "$ctext yview scroll 1 p"
220 bind . b "$ctext yview scroll -1 p"
221 bind . d "$ctext yview scroll 18 u"
222 bind . u "$ctext yview scroll -18 u"
223 bind . Q doquit
224 bind . <Control-q> doquit
225 bind . <Control-f> dofind
226 bind . <Control-g> findnext
227 bind . <Control-r> findprev
228 bind . <Control-equal> {incrfont 1}
229 bind . <Control-KP_Add> {incrfont 1}
230 bind . <Control-minus> {incrfont -1}
231 bind . <Control-KP_Subtract> {incrfont -1}
232 bind $cflist <<ListboxSelect>> listboxsel
233}
234
235proc allcanvs args {
236 global canv canv2 canv3
237 eval $canv $args
238 eval $canv2 $args
239 eval $canv3 $args
240}
241
242proc bindall {event action} {
243 global canv canv2 canv3
244 bind $canv $event $action
245 bind $canv2 $event $action
246 bind $canv3 $event $action
247}
248
249proc about {} {
250 set w .about
251 if {[winfo exists $w]} {
252 raise $w
253 return
254 }
255 toplevel $w
256 wm title $w "About gitk"
257 message $w.m -text {
258Gitk version 0.91
259
260Copyright © 2005 Paul Mackerras
261
262Use and redistribute under the terms of the GNU General Public License
263
264(CVS $Revision: 1.10 $)} \
265 -justify center -aspect 400
266 pack $w.m -side top -fill x -padx 20 -pady 20
267 button $w.ok -text Close -command "destroy $w"
268 pack $w.ok -side bottom
269}
270
271proc truncatetofit {str width font} {
272 if {[font measure $font $str] <= $width} {
273 return $str
274 }
275 set best 0
276 set bad [string length $str]
277 set tmp $str
278 while {$best < $bad - 1} {
279 set try [expr {int(($best + $bad) / 2)}]
280 set tmp "[string range $str 0 [expr $try-1]]..."
281 if {[font measure $font $tmp] <= $width} {
282 set best $try
283 } else {
284 set bad $try
285 }
286 }
287 return $tmp
288}
289
290proc assigncolor {id} {
291 global commitinfo colormap commcolors colors nextcolor
292 global colorbycommitter
293 global parents nparents children nchildren
294 if [info exists colormap($id)] return
295 set ncolors [llength $colors]
296 if {$colorbycommitter} {
297 if {![info exists commitinfo($id)]} {
298 readcommit $id
299 }
300 set comm [lindex $commitinfo($id) 3]
301 if {![info exists commcolors($comm)]} {
302 set commcolors($comm) [lindex $colors $nextcolor]
303 if {[incr nextcolor] >= $ncolors} {
304 set nextcolor 0
305 }
306 }
307 set colormap($id) $commcolors($comm)
308 } else {
309 if {$nparents($id) == 1 && $nchildren($id) == 1} {
310 set child [lindex $children($id) 0]
311 if {[info exists colormap($child)]
312 && $nparents($child) == 1} {
313 set colormap($id) $colormap($child)
314 return
315 }
316 }
317 set badcolors {}
318 foreach child $children($id) {
319 if {[info exists colormap($child)]
320 && [lsearch -exact $badcolors $colormap($child)] < 0} {
321 lappend badcolors $colormap($child)
322 }
323 if {[info exists parents($child)]} {
324 foreach p $parents($child) {
325 if {[info exists colormap($p)]
326 && [lsearch -exact $badcolors $colormap($p)] < 0} {
327 lappend badcolors $colormap($p)
328 }
329 }
330 }
331 }
332 if {[llength $badcolors] >= $ncolors} {
333 set badcolors {}
334 }
335 for {set i 0} {$i <= $ncolors} {incr i} {
336 set c [lindex $colors $nextcolor]
337 if {[incr nextcolor] >= $ncolors} {
338 set nextcolor 0
339 }
340 if {[lsearch -exact $badcolors $c]} break
341 }
342 set colormap($id) $c
343 }
344}
345
346proc drawgraph {} {
347 global parents children nparents nchildren commits
348 global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
349 global datemode cdate
350 global lineid linehtag linentag linedtag commitinfo
351 global nextcolor colormap numcommits
352 global stopped phase redisplaying selectedline
353
354 allcanvs delete all
355 set start {}
356 foreach id $commits {
357 if {$nchildren($id) == 0} {
358 lappend start $id
359 }
360 set ncleft($id) $nchildren($id)
361 }
362 if {$start == {}} {
363 $canv create text 3 3 -anchor nw -font $mainfont \
364 -text "ERROR: No starting commits found"
365 set phase {}
366 return
367 }
368
369 set nextcolor 0
370 foreach id $start {
371 assigncolor $id
372 }
373 set todo $start
374 set level [expr [llength $todo] - 1]
375 set y2 $canvy0
376 set nullentry -1
377 set lineno -1
378 set numcommits 0
379 set phase drawgraph
380 while 1 {
381 set canvy $y2
382 allcanvs conf -scrollregion [list 0 0 0 $canvy]
383 update
384 if {$stopped} break
385 incr numcommits
386 incr lineno
387 set nlines [llength $todo]
388 set id [lindex $todo $level]
389 set lineid($lineno) $id
390 set actualparents {}
391 foreach p $parents($id) {
392 if {[info exists ncleft($p)]} {
393 incr ncleft($p) -1
394 lappend actualparents $p
395 }
396 }
397 if {![info exists commitinfo($id)]} {
398 readcommit $id
399 }
400 set x [expr $canvx0 + $level * $linespc]
401 set y2 [expr $canvy + $linespc]
402 if {[info exists linestarty($level)] && $linestarty($level) < $canvy} {
403 set t [$canv create line $x $linestarty($level) $x $canvy \
404 -width 2 -fill $colormap($id)]
405 $canv lower $t
406 }
407 set linestarty($level) $canvy
408 set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
409 [expr $x + 3] [expr $canvy + 3] \
410 -fill blue -outline black -width 1]
411 $canv raise $t
412 set xt [expr $canvx0 + $nlines * $linespc]
413 set headline [lindex $commitinfo($id) 0]
414 set name [lindex $commitinfo($id) 1]
415 set date [lindex $commitinfo($id) 2]
416 set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
417 -text $headline -font $mainfont ]
418 set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
419 -text $name -font $namefont]
420 set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
421 -text $date -font $mainfont]
422 if {!$datemode && [llength $actualparents] == 1} {
423 set p [lindex $actualparents 0]
424 if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
425 assigncolor $p
426 set todo [lreplace $todo $level $level $p]
427 continue
428 }
429 }
430
431 set oldtodo $todo
432 set oldlevel $level
433 set lines {}
434 for {set i 0} {$i < $nlines} {incr i} {
435 if {[lindex $todo $i] == {}} continue
436 if {[info exists linestarty($i)]} {
437 set oldstarty($i) $linestarty($i)
438 unset linestarty($i)
439 }
440 if {$i != $level} {
441 lappend lines [list $i [lindex $todo $i]]
442 }
443 }
444 if {$nullentry >= 0} {
445 set todo [lreplace $todo $nullentry $nullentry]
446 if {$nullentry < $level} {
447 incr level -1
448 }
449 }
450
451 set todo [lreplace $todo $level $level]
452 if {$nullentry > $level} {
453 incr nullentry -1
454 }
455 set i $level
456 foreach p $actualparents {
457 set k [lsearch -exact $todo $p]
458 if {$k < 0} {
459 assigncolor $p
460 set todo [linsert $todo $i $p]
461 if {$nullentry >= $i} {
462 incr nullentry
463 }
464 }
465 lappend lines [list $oldlevel $p]
466 }
467
468 # choose which one to do next time around
469 set todol [llength $todo]
470 set level -1
471 set latest {}
472 for {set k $todol} {[incr k -1] >= 0} {} {
473 set p [lindex $todo $k]
474 if {$p == {}} continue
475 if {$ncleft($p) == 0} {
476 if {$datemode} {
477 if {$latest == {} || $cdate($p) > $latest} {
478 set level $k
479 set latest $cdate($p)
480 }
481 } else {
482 set level $k
483 break
484 }
485 }
486 }
487 if {$level < 0} {
488 if {$todo != {}} {
489 puts "ERROR: none of the pending commits can be done yet:"
490 foreach p $todo {
491 puts " $p"
492 }
493 }
494 break
495 }
496
497 # If we are reducing, put in a null entry
498 if {$todol < $nlines} {
499 if {$nullentry >= 0} {
500 set i $nullentry
501 while {$i < $todol
502 && [lindex $oldtodo $i] == [lindex $todo $i]} {
503 incr i
504 }
505 } else {
506 set i $oldlevel
507 if {$level >= $i} {
508 incr i
509 }
510 }
511 if {$i >= $todol} {
512 set nullentry -1
513 } else {
514 set nullentry $i
515 set todo [linsert $todo $nullentry {}]
516 if {$level >= $i} {
517 incr level
518 }
519 }
520 } else {
521 set nullentry -1
522 }
523
524 foreach l $lines {
525 set i [lindex $l 0]
526 set dst [lindex $l 1]
527 set j [lsearch -exact $todo $dst]
528 if {$i == $j} {
529 if {[info exists oldstarty($i)]} {
530 set linestarty($i) $oldstarty($i)
531 }
532 continue
533 }
534 set xi [expr {$canvx0 + $i * $linespc}]
535 set xj [expr {$canvx0 + $j * $linespc}]
536 set coords {}
537 if {[info exists oldstarty($i)] && $oldstarty($i) < $canvy} {
538 lappend coords $xi $oldstarty($i)
539 }
540 lappend coords $xi $canvy
541 if {$j < $i - 1} {
542 lappend coords [expr $xj + $linespc] $canvy
543 } elseif {$j > $i + 1} {
544 lappend coords [expr $xj - $linespc] $canvy
545 }
546 lappend coords $xj $y2
547 set t [$canv create line $coords -width 2 -fill $colormap($dst)]
548 $canv lower $t
549 if {![info exists linestarty($j)]} {
550 set linestarty($j) $y2
551 }
552 }
553 }
554 set phase {}
555 if {$redisplaying} {
556 if {$stopped == 0 && [info exists selectedline]} {
557 selectline $selectedline
558 }
559 if {$stopped == 1} {
560 set stopped 0
561 after idle drawgraph
562 } else {
563 set redisplaying 0
564 }
565 }
566}
567
568proc dofind {} {
569 global findtype findloc findstring markedmatches commitinfo
570 global numcommits lineid linehtag linentag linedtag
571 global mainfont namefont canv canv2 canv3 selectedline
572 global matchinglines
573 unmarkmatches
574 set matchinglines {}
575 set fldtypes {Headline Author Date Committer CDate Comment}
576 if {$findtype == "IgnCase"} {
577 set fstr [string tolower $findstring]
578 } else {
579 set fstr $findstring
580 }
581 set mlen [string length $findstring]
582 if {$mlen == 0} return
583 if {![info exists selectedline]} {
584 set oldsel -1
585 } else {
586 set oldsel $selectedline
587 }
588 set didsel 0
589 for {set l 0} {$l < $numcommits} {incr l} {
590 set id $lineid($l)
591 set info $commitinfo($id)
592 set doesmatch 0
593 foreach f $info ty $fldtypes {
594 if {$findloc != "All fields" && $findloc != $ty} {
595 continue
596 }
597 if {$findtype == "Regexp"} {
598 set matches [regexp -indices -all -inline $fstr $f]
599 } else {
600 if {$findtype == "IgnCase"} {
601 set str [string tolower $f]
602 } else {
603 set str $f
604 }
605 set matches {}
606 set i 0
607 while {[set j [string first $fstr $str $i]] >= 0} {
608 lappend matches [list $j [expr $j+$mlen-1]]
609 set i [expr $j + $mlen]
610 }
611 }
612 if {$matches == {}} continue
613 set doesmatch 1
614 if {$ty == "Headline"} {
615 markmatches $canv $l $f $linehtag($l) $matches $mainfont
616 } elseif {$ty == "Author"} {
617 markmatches $canv2 $l $f $linentag($l) $matches $namefont
618 } elseif {$ty == "Date"} {
619 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
620 }
621 }
622 if {$doesmatch} {
623 lappend matchinglines $l
624 if {!$didsel && $l > $oldsel} {
625 selectline $l
626 set didsel 1
627 }
628 }
629 }
630 if {$matchinglines == {}} {
631 bell
632 } elseif {!$didsel} {
633 selectline [lindex $matchinglines 0]
634 }
635}
636
637proc findnext {} {
638 global matchinglines selectedline
639 if {![info exists matchinglines]} {
640 dofind
641 return
642 }
643 if {![info exists selectedline]} return
644 foreach l $matchinglines {
645 if {$l > $selectedline} {
646 selectline $l
647 return
648 }
649 }
650 bell
651}
652
653proc findprev {} {
654 global matchinglines selectedline
655 if {![info exists matchinglines]} {
656 dofind
657 return
658 }
659 if {![info exists selectedline]} return
660 set prev {}
661 foreach l $matchinglines {
662 if {$l >= $selectedline} break
663 set prev $l
664 }
665 if {$prev != {}} {
666 selectline $prev
667 } else {
668 bell
669 }
670}
671
672proc markmatches {canv l str tag matches font} {
673 set bbox [$canv bbox $tag]
674 set x0 [lindex $bbox 0]
675 set y0 [lindex $bbox 1]
676 set y1 [lindex $bbox 3]
677 foreach match $matches {
678 set start [lindex $match 0]
679 set end [lindex $match 1]
680 if {$start > $end} continue
681 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
682 set xlen [font measure $font [string range $str 0 [expr $end]]]
683 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
684 -outline {} -tags matches -fill yellow]
685 $canv lower $t
686 }
687}
688
689proc unmarkmatches {} {
690 global matchinglines
691 allcanvs delete matches
692 catch {unset matchinglines}
693}
694
695proc selcanvline {x y} {
696 global canv canvy0 ctext linespc selectedline
697 global lineid linehtag linentag linedtag
698 set ymax [lindex [$canv cget -scrollregion] 3]
699 set yfrac [lindex [$canv yview] 0]
700 set y [expr {$y + $yfrac * $ymax}]
701 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
702 if {$l < 0} {
703 set l 0
704 }
705 if {[info exists selectedline] && $selectedline == $l} return
706 unmarkmatches
707 selectline $l
708}
709
710proc selectline {l} {
711 global canv canv2 canv3 ctext commitinfo selectedline
712 global lineid linehtag linentag linedtag
713 global canvy canvy0 linespc nparents treepending
714 global cflist treediffs currentid sha1entry
715 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
716 $canv delete secsel
717 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
718 -tags secsel -fill [$canv cget -selectbackground]]
719 $canv lower $t
720 $canv2 delete secsel
721 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
722 -tags secsel -fill [$canv2 cget -selectbackground]]
723 $canv2 lower $t
724 $canv3 delete secsel
725 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
726 -tags secsel -fill [$canv3 cget -selectbackground]]
727 $canv3 lower $t
728 set y [expr {$canvy0 + $l * $linespc}]
729 set ytop [expr {($y - $linespc / 2.0) / $canvy}]
730 set ybot [expr {($y + $linespc / 2.0) / $canvy}]
731 set wnow [$canv yview]
732 if {$ytop < [lindex $wnow 0]} {
733 allcanvs yview moveto $ytop
734 } elseif {$ybot > [lindex $wnow 1]} {
735 set wh [expr {[lindex $wnow 1] - [lindex $wnow 0]}]
736 allcanvs yview moveto [expr {$ybot - $wh}]
737 }
738 set selectedline $l
739
740 set id $lineid($l)
741 $sha1entry conf -state normal
742 $sha1entry delete 0 end
743 $sha1entry insert 0 $id
744 $sha1entry selection from 0
745 $sha1entry selection to end
746 $sha1entry conf -state readonly
747
748 $ctext conf -state normal
749 $ctext delete 0.0 end
750 set info $commitinfo($id)
751 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
752 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
753 $ctext insert end "\n"
754 $ctext insert end [lindex $info 5]
755 $ctext insert end "\n"
756 $ctext tag delete Comments
757 $ctext conf -state disabled
758
759 $cflist delete 0 end
760 set currentid $id
761 if {$nparents($id) == 1} {
762 if {![info exists treediffs($id)]} {
763 if {![info exists treepending]} {
764 gettreediffs $id
765 }
766 } else {
767 addtocflist $id
768 }
769 }
770}
771
772proc selnextline {dir} {
773 global selectedline
774 if {![info exists selectedline]} return
775 set l [expr $selectedline + $dir]
776 unmarkmatches
777 selectline $l
778}
779
780proc addtocflist {id} {
781 global currentid treediffs cflist treepending
782 if {$id != $currentid} {
783 gettreediffs $currentid
784 return
785 }
786 $cflist insert end "All files"
787 foreach f $treediffs($currentid) {
788 $cflist insert end $f
789 }
790 getblobdiffs $id
791}
792
793proc gettreediffs {id} {
794 global treediffs parents treepending
795 set treepending $id
796 set treediffs($id) {}
797 set p [lindex $parents($id) 0]
798 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
799 fconfigure $gdtf -blocking 0
800 fileevent $gdtf readable "gettreediffline $gdtf $id"
801}
802
803proc gettreediffline {gdtf id} {
804 global treediffs treepending
805 set n [gets $gdtf line]
806 if {$n < 0} {
807 if {![eof $gdtf]} return
808 close $gdtf
809 unset treepending
810 addtocflist $id
811 return
812 }
813 set type [lindex $line 1]
814 set file [lindex $line 3]
815 if {$type == "blob"} {
816 lappend treediffs($id) $file
817 }
818}
819
820proc getblobdiffs {id} {
821 global parents diffopts blobdifffd env curdifftag curtagstart
822 set p [lindex $parents($id) 0]
823 set env(GIT_DIFF_OPTS) $diffopts
824 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
825 puts "error getting diffs: $err"
826 return
827 }
828 fconfigure $bdf -blocking 0
829 set blobdifffd($id) $bdf
830 set curdifftag Comments
831 set curtagstart 0.0
832 fileevent $bdf readable "getblobdiffline $bdf $id"
833}
834
835proc getblobdiffline {bdf id} {
836 global currentid blobdifffd ctext curdifftag curtagstart
837 set n [gets $bdf line]
838 if {$n < 0} {
839 if {[eof $bdf]} {
840 close $bdf
841 if {$id == $currentid && $bdf == $blobdifffd($id)} {
842 $ctext tag add $curdifftag $curtagstart end
843 }
844 }
845 return
846 }
847 if {$id != $currentid || $bdf != $blobdifffd($id)} {
848 return
849 }
850 $ctext conf -state normal
851 if {[regexp {^---[ \t]+([^/])+/(.*)} $line match s1 fname]} {
852 # start of a new file
853 $ctext insert end "\n"
854 $ctext tag add $curdifftag $curtagstart end
855 set curtagstart [$ctext index "end - 1c"]
856 set curdifftag "f:$fname"
857 $ctext tag delete $curdifftag
858 set l [expr {(78 - [string length $fname]) / 2}]
859 set pad [string range "----------------------------------------" 1 $l]
860 $ctext insert end "$pad $fname $pad\n" filesep
861 } elseif {[string range $line 0 2] == "+++"} {
862 # no need to do anything with this
863 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
864 $line match f1l f1c f2l f2c rest]} {
865 $ctext insert end "\t" hunksep
866 $ctext insert end " $f1l " d0 " $f2l " d1
867 $ctext insert end " $rest \n" hunksep
868 } else {
869 set x [string range $line 0 0]
870 if {$x == "-" || $x == "+"} {
871 set tag [expr {$x == "+"}]
872 set line [string range $line 1 end]
873 $ctext insert end "$line\n" d$tag
874 } elseif {$x == " "} {
875 set line [string range $line 1 end]
876 $ctext insert end "$line\n"
877 } else {
878 # Something else we don't recognize
879 if {$curdifftag != "Comments"} {
880 $ctext insert end "\n"
881 $ctext tag add $curdifftag $curtagstart end
882 set curtagstart [$ctext index "end - 1c"]
883 set curdifftag Comments
884 }
885 $ctext insert end "$line\n" filesep
886 }
887 }
888 $ctext conf -state disabled
889}
890
891proc listboxsel {} {
892 global ctext cflist currentid treediffs
893 if {![info exists currentid]} return
894 set sel [$cflist curselection]
895 if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
896 # show everything
897 $ctext tag conf Comments -elide 0
898 foreach f $treediffs($currentid) {
899 $ctext tag conf "f:$f" -elide 0
900 }
901 } else {
902 # just show selected files
903 $ctext tag conf Comments -elide 1
904 set i 1
905 foreach f $treediffs($currentid) {
906 set elide [expr {[lsearch -exact $sel $i] < 0}]
907 $ctext tag conf "f:$f" -elide $elide
908 incr i
909 }
910 }
911}
912
913proc setcoords {} {
914 global linespc charspc canvx0 canvy0 mainfont
915 set linespc [font metrics $mainfont -linespace]
916 set charspc [font measure $mainfont "m"]
917 set canvy0 [expr 3 + 0.5 * $linespc]
918 set canvx0 [expr 3 + 0.5 * $linespc]
919}
920
921proc redisplay {} {
922 global selectedline stopped redisplaying phase
923 if {$stopped > 1} return
924 if {$phase == "getcommits"} return
925 set redisplaying 1
926 if {$phase == "drawgraph"} {
927 set stopped 1
928 } else {
929 drawgraph
930 }
931}
932
933proc incrfont {inc} {
934 global mainfont namefont textfont selectedline ctext canv phase
935 global stopped
936 unmarkmatches
937 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
938 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
939 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
940 setcoords
941 $ctext conf -font $textfont
942 $ctext tag conf filesep -font [concat $textfont bold]
943 if {$phase == "getcommits"} {
944 $canv itemconf textitems -font $mainfont
945 }
946 redisplay
947}
948
949proc doquit {} {
950 global stopped
951 set stopped 100
952 destroy .
953}
954
955# defaults...
956set datemode 0
957set boldnames 0
958set diffopts "-U 5 -p"
959
960set mainfont {Helvetica 9}
961set namefont $mainfont
962set textfont {Courier 9}
963if {$boldnames} {
964 lappend namefont bold
965}
966
967set colors {green red blue magenta darkgrey brown orange}
968set colorbycommitter false
969
970catch {source ~/.gitk}
971
972set revtreeargs {}
973foreach arg $argv {
974 switch -regexp -- $arg {
975 "^$" { }
976 "^-b" { set boldnames 1 }
977 "^-c" { set colorbycommitter 1 }
978 "^-d" { set datemode 1 }
979 "^-.*" {
980 puts stderr "unrecognized option $arg"
981 exit 1
982 }
983 default {
984 lappend revtreeargs $arg
985 }
986 }
987}
988
989set stopped 0
990set redisplaying 0
991setcoords
992makewindow
993getcommits $revtreeargs