2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 # CVS $Revision: 1.6 $
15 set diffopts "-U 5 -p"
17 set mainfont {Helvetica 9}
18 set namefont $mainfont
19 set textfont {Courier 9}
24 set colors {green red blue magenta darkgrey brown orange}
25 set colorbycommitter false
27 catch {source ~/.gitk}
30 switch -regexp -- $arg {
32 "^-b" { set boldnames 1 }
33 "^-c" { set colorbycommitter 1 }
34 "^-d" { set datemode 1 }
36 puts stderr "unrecognized option $arg"
40 lappend revtreeargs $arg
45 proc getcommits {rargs} {
46 global commits parents cdate nparents children nchildren
51 foreach c [split [eval exec git-rev-tree $rargs] "\n"] {
58 set id [lindex [split $f :] 0]
59 if {![info exists nchildren($id)]} {
70 lappend parents($cid) $id
73 lappend children($id) $cid
81 proc readcommit {id} {
90 foreach line [split [exec git-cat-file commit $id] "\n"] {
95 set tag [lindex $line 0]
96 if {$tag == "author"} {
97 set x [expr {[llength $line] - 2}]
98 set audate [lindex $line $x]
99 set auname [lrange $line 1 [expr {$x - 1}]]
100 } elseif {$tag == "committer"} {
101 set x [expr {[llength $line] - 2}]
102 set comdate [lindex $line $x]
103 set comname [lrange $line 1 [expr {$x - 1}]]
107 if {$comment == {}} {
116 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
118 if {$comdate != {}} {
119 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
121 set commitinfo($id) [list $headline $auname $audate \
122 $comname $comdate $comment]
126 global canv canv2 canv3 linespc charspc ctext cflist textfont
127 panedwindow .ctop -orient vertical
128 panedwindow .ctop.clist -orient horizontal -sashpad 0 -handlesize 4
129 .ctop add .ctop.clist
130 set canv .ctop.clist.canv
131 set cscroll .ctop.clist.dates.csb
132 set height [expr 25 * $linespc + 4]
133 canvas $canv -height $height -width [expr 45 * $charspc] \
135 -yscrollincr $linespc -yscrollcommand "$cscroll set"
136 .ctop.clist add $canv
137 set canv2 .ctop.clist.canv2
138 canvas $canv2 -height $height -width [expr 30 * $charspc] \
139 -bg white -bd 0 -yscrollincr $linespc
140 .ctop.clist add $canv2
141 frame .ctop.clist.dates
142 .ctop.clist add .ctop.clist.dates
143 set canv3 .ctop.clist.dates.canv3
144 canvas $canv3 -height $height -width [expr 15 * $charspc] \
145 -bg white -bd 0 -yscrollincr $linespc
146 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
147 pack .ctop.clist.dates.csb -side right -fill y
148 pack $canv3 -side left -fill both -expand 1
150 panedwindow .ctop.cdet -orient horizontal
152 frame .ctop.cdet.left
153 set ctext .ctop.cdet.left.ctext
154 text $ctext -bg white -state disabled -font $textfont -height 32 \
155 -yscrollcommand ".ctop.cdet.left.sb set"
156 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
157 pack .ctop.cdet.left.sb -side right -fill y
158 pack $ctext -side left -fill both -expand 1
159 .ctop.cdet add .ctop.cdet.left
161 $ctext tag conf filesep -font [concat $textfont bold]
162 $ctext tag conf hunksep -back blue -fore white
163 $ctext tag conf d0 -back "#ff8080"
164 $ctext tag conf d1 -back green
166 frame .ctop.cdet.right
167 set cflist .ctop.cdet.right.cfiles
168 listbox $cflist -width 30 -bg white -selectmode extended \
169 -yscrollcommand ".ctop.cdet.right.sb set"
170 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
171 pack .ctop.cdet.right.sb -side right -fill y
172 pack $cflist -side left -fill both -expand 1
173 .ctop.cdet add .ctop.cdet.right
175 pack .ctop -side top -fill both -expand 1
177 bindall <1> {selcanvline %x %y}
178 bindall <B1-Motion> {selcanvline %x %y}
179 bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
180 bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
181 bindall <2> "allcanvs scan mark 0 %y"
182 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
183 bind . <Key-Up> "selnextline -1"
184 bind . <Key-Down> "selnextline 1"
185 bind . p "selnextline -1"
186 bind . n "selnextline 1"
187 bind . <Key-Prior> "allcanvs yview scroll -1 p"
188 bind . <Key-Next> "allcanvs yview scroll 1 p"
189 bind . <Key-Delete> "$ctext yview scroll -1 p"
190 bind . <Key-BackSpace> "$ctext yview scroll -1 p"
191 bind . <Key-space> "$ctext yview scroll 1 p"
192 bind . b "$ctext yview scroll -1 p"
193 bind . d "$ctext yview scroll 18 u"
194 bind . u "$ctext yview scroll -18 u"
195 bind . Q "set stopped 1; destroy ."
196 bind $cflist <<ListboxSelect>> listboxsel
200 global canv canv2 canv3
206 proc bindall {event action} {
207 global canv canv2 canv3
208 bind $canv $event $action
209 bind $canv2 $event $action
210 bind $canv3 $event $action
213 proc truncatetofit {str width font} {
214 if {[font measure $font $str] <= $width} {
218 set bad [string length $str]
220 while {$best < $bad - 1} {
221 set try [expr {int(($best + $bad) / 2)}]
222 set tmp "[string range $str 0 [expr $try-1]]..."
223 if {[font measure $font $tmp] <= $width} {
232 proc assigncolor {id} {
233 global commitinfo colormap commcolors colors nextcolor
234 global colorbycommitter
235 global parents nparents children nchildren
236 if [info exists colormap($id)] return
237 set ncolors [llength $colors]
238 if {$colorbycommitter} {
239 if {![info exists commitinfo($id)]} {
242 set comm [lindex $commitinfo($id) 3]
243 if {![info exists commcolors($comm)]} {
244 set commcolors($comm) [lindex $colors $nextcolor]
245 if {[incr nextcolor] >= $ncolors} {
249 set colormap($id) $commcolors($comm)
251 if {$nparents($id) == 1 && $nchildren($id) == 1} {
252 set child [lindex $children($id) 0]
253 if {[info exists colormap($child)]
254 && $nparents($child) == 1} {
255 set colormap($id) $colormap($child)
260 foreach child $children($id) {
261 if {[info exists colormap($child)]
262 && [lsearch -exact $badcolors $colormap($child)] < 0} {
263 lappend badcolors $colormap($child)
265 if {[info exists parents($child)]} {
266 foreach p $parents($child) {
267 if {[info exists colormap($p)]
268 && [lsearch -exact $badcolors $colormap($p)] < 0} {
269 lappend badcolors $colormap($p)
274 if {[llength $badcolors] >= $ncolors} {
277 for {set i 0} {$i <= $ncolors} {incr i} {
278 set c [lindex $colors $nextcolor]
279 if {[incr nextcolor] >= $ncolors} {
282 if {[lsearch -exact $badcolors $c]} break
288 proc drawgraph {start} {
289 global parents children nparents nchildren commits
290 global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
291 global datemode cdate
292 global lineid linehtag linentag linedtag commitinfo
293 global nextcolor colormap
297 foreach id $commits {
298 set ncleft($id) $nchildren($id)
300 set todo [list $start]
303 set linestarty(0) $canvy0
308 allcanvs conf -scrollregion [list 0 0 0 $canvy]
311 set nlines [llength $todo]
312 set id [lindex $todo $level]
313 set lineid($lineno) $id
315 foreach p $parents($id) {
316 if {[info exists ncleft($p)]} {
318 lappend actualparents $p
321 if {![info exists commitinfo($id)]} {
324 set x [expr $canvx0 + $level * $linespc]
325 set y2 [expr $canvy + $linespc]
326 if {$linestarty($level) < $canvy} {
327 set t [$canv create line $x $linestarty($level) $x $canvy \
328 -width 2 -fill $colormap($id)]
330 set linestarty($level) $canvy
332 set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
333 [expr $x + 3] [expr $canvy + 3] \
334 -fill blue -outline black -width 1]
336 set xt [expr $canvx0 + $nlines * $linespc]
337 set headline [lindex $commitinfo($id) 0]
338 set name [lindex $commitinfo($id) 1]
339 set date [lindex $commitinfo($id) 2]
340 set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
341 -text $headline -font $mainfont ]
342 set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
343 -text $name -font $namefont]
344 set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
345 -text $date -font $mainfont]
346 if {!$datemode && [llength $actualparents] == 1} {
347 set p [lindex $actualparents 0]
348 if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
350 set todo [lreplace $todo $level $level $p]
358 for {set i 0} {$i < $nlines} {incr i} {
359 if {[lindex $todo $i] == {}} continue
360 set oldstarty($i) $linestarty($i)
362 lappend lines [list $i [lindex $todo $i]]
366 if {$nullentry >= 0} {
367 set todo [lreplace $todo $nullentry $nullentry]
368 if {$nullentry < $level} {
373 set todo [lreplace $todo $level $level]
374 if {$nullentry > $level} {
378 foreach p $actualparents {
379 set k [lsearch -exact $todo $p]
382 set todo [linsert $todo $i $p]
383 if {$nullentry >= $i} {
387 lappend lines [list $oldlevel $p]
390 # choose which one to do next time around
391 set todol [llength $todo]
394 for {set k $todol} {[incr k -1] >= 0} {} {
395 set p [lindex $todo $k]
396 if {$p == {}} continue
397 if {$ncleft($p) == 0} {
399 if {$latest == {} || $cdate($p) > $latest} {
401 set latest $cdate($p)
411 puts "ERROR: none of the pending commits can be done yet:"
419 # If we are reducing, put in a null entry
420 if {$todol < $nlines} {
421 if {$nullentry >= 0} {
424 && [lindex $oldtodo $i] == [lindex $todo $i]} {
437 set todo [linsert $todo $nullentry {}]
448 set dst [lindex $l 1]
449 set j [lsearch -exact $todo $dst]
451 set linestarty($i) $oldstarty($i)
454 set xi [expr {$canvx0 + $i * $linespc}]
455 set xj [expr {$canvx0 + $j * $linespc}]
457 if {$oldstarty($i) < $canvy} {
458 lappend coords $xi $oldstarty($i)
460 lappend coords $xi $canvy
462 lappend coords [expr $xj + $linespc] $canvy
463 } elseif {$j > $i + 1} {
464 lappend coords [expr $xj - $linespc] $canvy
466 lappend coords $xj $y2
467 set t [$canv create line $coords -width 2 -fill $colormap($dst)]
469 if {![info exists linestarty($j)]} {
470 set linestarty($j) $y2
476 proc selcanvline {x y} {
477 global canv canvy0 ctext linespc selectedline
478 global lineid linehtag linentag linedtag
479 set ymax [lindex [$canv cget -scrollregion] 3]
480 set yfrac [lindex [$canv yview] 0]
481 set y [expr {$y + $yfrac * $ymax}]
482 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
486 if {[info exists selectedline] && $selectedline == $l} return
490 proc selectline {l} {
491 global canv canv2 canv3 ctext commitinfo selectedline
492 global lineid linehtag linentag linedtag
493 global canvy canvy0 linespc nparents treepending
494 global cflist treediffs currentid
495 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
497 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
498 -tags secsel -fill [$canv cget -selectbackground]]
501 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
502 -tags secsel -fill [$canv2 cget -selectbackground]]
505 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
506 -tags secsel -fill [$canv3 cget -selectbackground]]
508 set y [expr {$canvy0 + $l * $linespc}]
509 set ytop [expr {($y - $linespc / 2.0) / $canvy}]
510 set ybot [expr {($y + $linespc / 2.0) / $canvy}]
511 set wnow [$canv yview]
512 if {$ytop < [lindex $wnow 0]} {
513 allcanvs yview moveto $ytop
514 } elseif {$ybot > [lindex $wnow 1]} {
515 set wh [expr {[lindex $wnow 1] - [lindex $wnow 0]}]
516 allcanvs yview moveto [expr {$ybot - $wh}]
521 $ctext conf -state normal
522 $ctext delete 0.0 end
523 set info $commitinfo($id)
524 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
525 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
526 $ctext insert end "\n"
527 $ctext insert end [lindex $info 5]
528 $ctext insert end "\n"
529 $ctext tag delete Comments
530 $ctext conf -state disabled
534 if {$nparents($id) == 1} {
535 if {![info exists treediffs($id)]} {
536 if {![info exists treepending]} {
545 proc selnextline {dir} {
547 if {![info exists selectedline]} return
548 set l [expr $selectedline + $dir]
552 proc addtocflist {id} {
553 global currentid treediffs cflist treepending
554 if {$id != $currentid} {
555 gettreediffs $currentid
558 $cflist insert end "All files"
559 foreach f $treediffs($currentid) {
560 $cflist insert end $f
565 proc gettreediffs {id} {
566 global treediffs parents treepending
568 set treediffs($id) {}
569 set p [lindex $parents($id) 0]
570 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
571 fconfigure $gdtf -blocking 0
572 fileevent $gdtf readable "gettreediffline $gdtf $id"
575 proc gettreediffline {gdtf id} {
576 global treediffs treepending
577 set n [gets $gdtf line]
579 if {![eof $gdtf]} return
585 set type [lindex $line 1]
586 set file [lindex $line 3]
587 if {$type == "blob"} {
588 lappend treediffs($id) $file
592 proc getblobdiffs {id} {
593 global parents diffopts blobdifffd env curdifftag curtagstart
594 set p [lindex $parents($id) 0]
595 set env(GIT_DIFF_OPTS) $diffopts
596 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
597 puts "error getting diffs: $err"
600 fconfigure $bdf -blocking 0
601 set blobdifffd($id) $bdf
602 set curdifftag Comments
604 fileevent $bdf readable "getblobdiffline $bdf $id"
607 proc getblobdiffline {bdf id} {
608 global currentid blobdifffd ctext curdifftag curtagstart
609 set n [gets $bdf line]
613 if {$id == $currentid && $bdf == $blobdifffd($id)} {
614 $ctext tag add $curdifftag $curtagstart end
619 if {$id != $currentid || $bdf != $blobdifffd($id)} {
622 $ctext conf -state normal
623 if {[regexp {^---[ \t]+([^/])+/(.*)} $line match s1 fname]} {
624 # start of a new file
625 $ctext insert end "\n"
626 $ctext tag add $curdifftag $curtagstart end
627 set curtagstart [$ctext index "end - 1c"]
628 set curdifftag "f:$fname"
629 $ctext tag delete $curdifftag
630 set l [expr {(78 - [string length $fname]) / 2}]
631 set pad [string range "----------------------------------------" 1 $l]
632 $ctext insert end "$pad $fname $pad\n" filesep
633 } elseif {[string range $line 0 2] == "+++"} {
634 # no need to do anything with this
635 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
636 $line match f1l f1c f2l f2c rest]} {
637 $ctext insert end "\t" hunksep
638 $ctext insert end " $f1l " d0 " $f2l " d1
639 $ctext insert end " $rest \n" hunksep
641 set x [string range $line 0 0]
642 if {$x == "-" || $x == "+"} {
643 set tag [expr {$x == "+"}]
644 set line [string range $line 1 end]
645 $ctext insert end "$line\n" d$tag
646 } elseif {$x == " "} {
647 set line [string range $line 1 end]
648 $ctext insert end "$line\n"
650 # Something else we don't recognize
651 if {$curdifftag != "Comments"} {
652 $ctext insert end "\n"
653 $ctext tag add $curdifftag $curtagstart end
654 set curtagstart [$ctext index "end - 1c"]
655 set curdifftag Comments
657 $ctext insert end "$line\n" filesep
660 $ctext conf -state disabled
664 global ctext cflist currentid treediffs
665 set sel [$cflist curselection]
666 if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
668 $ctext tag conf Comments -elide 0
669 foreach f $treediffs($currentid) {
670 $ctext tag conf "f:$f" -elide 0
673 # just show selected files
674 $ctext tag conf Comments -elide 1
676 foreach f $treediffs($currentid) {
677 set elide [expr {[lsearch -exact $sel $i] < 0}]
678 $ctext tag conf "f:$f" -elide $elide
684 getcommits $revtreeargs
686 set linespc [font metrics $mainfont -linespace]
687 set charspc [font measure $mainfont "m"]
689 set canvy0 [expr 3 + 0.5 * $linespc]
690 set canvx0 [expr 3 + 0.5 * $linespc]
691 set namex [expr 45 * $charspc]
692 set datex [expr 75 * $charspc]
697 foreach id $commits {
698 if {$nchildren($id) == 0} {