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 proc getcommits {rargs} {
11 global commits commfd phase canv mainfont env
12 global startmsecs nextupdate
13 global ctext maincursor textcursor leftover
15 # check that we can find a .git directory somewhere...
16 if {[info exists env(GIT_DIR)]} {
17 set gitdir $env(GIT_DIR)
21 if {![file isdirectory $gitdir]} {
22 error_popup "Cannot find the git directory \"$gitdir\"."
27 set startmsecs [clock clicks -milliseconds]
28 set nextupdate [expr $startmsecs + 100]
30 set parse_args [concat --default HEAD $rargs]
31 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
33 # if git-rev-parse failed for some reason...
37 set parsed_args $rargs
40 set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
42 puts stderr "Error executing git-rev-list: $err"
46 fconfigure $commfd -blocking 0 -translation binary
47 fileevent $commfd readable "getcommitlines $commfd"
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
55 proc getcommitlines {commfd} {
56 global commits parents cdate children nchildren
57 global commitlisted phase commitinfo nextupdate
58 global stopped redisplaying leftover
60 set stuff [read $commfd]
62 if {![eof $commfd]} return
63 # set it blocking so we wait for the process to terminate
64 fconfigure $commfd -blocking 1
65 if {![catch {close $commfd} err]} {
66 after idle finishcommits
69 if {[string range $err 0 4] == "usage"} {
71 {Gitk: error reading commits: bad arguments to git-rev-list.
72 (Note: arguments to gitk are passed to git-rev-list
73 to allow selection of commits to be displayed.)}
75 set err "Error reading commits: $err"
82 set i [string first "\0" $stuff $start]
84 append leftover [string range $stuff $start end]
87 set cmit [string range $stuff $start [expr {$i - 1}]]
89 set cmit "$leftover$cmit"
92 set start [expr {$i + 1}]
93 if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
95 if {[string length $shortcmit] > 80} {
96 set shortcmit "[string range $shortcmit 0 80]..."
98 error_popup "Can't parse git-rev-list output: {$shortcmit}"
101 set cmit [string range $cmit 41 end]
103 set commitlisted($id) 1
104 parsecommit $id $cmit 1
106 if {[clock clicks -milliseconds] >= $nextupdate} {
109 while {$redisplaying} {
113 set phase "getcommits"
114 foreach id $commits {
117 if {[clock clicks -milliseconds] >= $nextupdate} {
127 global commfd nextupdate
130 fileevent $commfd readable {}
132 fileevent $commfd readable "getcommitlines $commfd"
135 proc readcommit {id} {
136 if [catch {set contents [exec git-cat-file commit $id]}] return
137 parsecommit $id $contents 0
140 proc parsecommit {id contents listed} {
141 global commitinfo children nchildren parents nparents cdate ncleft
150 if {![info exists nchildren($id)]} {
157 foreach line [split $contents "\n"] {
162 set tag [lindex $line 0]
163 if {$tag == "parent"} {
164 set p [lindex $line 1]
165 if {![info exists nchildren($p)]} {
170 lappend parents($id) $p
172 # sometimes we get a commit that lists a parent twice...
173 if {$listed && [lsearch -exact $children($p) $id] < 0} {
174 lappend children($p) $id
178 } elseif {$tag == "author"} {
179 set x [expr {[llength $line] - 2}]
180 set audate [lindex $line $x]
181 set auname [lrange $line 1 [expr {$x - 1}]]
182 } elseif {$tag == "committer"} {
183 set x [expr {[llength $line] - 2}]
184 set comdate [lindex $line $x]
185 set comname [lrange $line 1 [expr {$x - 1}]]
189 if {$comment == {}} {
190 set headline [string trim $line]
195 # git-rev-list indents the comment by 4 spaces;
196 # if we got this via git-cat-file, add the indentation
203 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
205 if {$comdate != {}} {
206 set cdate($id) $comdate
207 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
209 set commitinfo($id) [list $headline $auname $audate \
210 $comname $comdate $comment]
214 global tagids idtags headids idheads
215 set tags [glob -nocomplain -types f .git/refs/tags/*]
220 if {[regexp {^[0-9a-f]{40}} $line id]} {
221 set direct [file tail $f]
222 set tagids($direct) $id
223 lappend idtags($id) $direct
224 set contents [split [exec git-cat-file tag $id] "\n"]
228 foreach l $contents {
230 switch -- [lindex $l 0] {
231 "object" {set obj [lindex $l 1]}
232 "type" {set type [lindex $l 1]}
233 "tag" {set tag [string range $l 4 end]}
236 if {$obj != {} && $type == "commit" && $tag != {}} {
237 set tagids($tag) $obj
238 lappend idtags($obj) $tag
244 set heads [glob -nocomplain -types f .git/refs/heads/*]
248 set line [read $fd 40]
249 if {[regexp {^[0-9a-f]{40}} $line id]} {
250 set head [file tail $f]
251 set headids($head) $line
252 lappend idheads($line) $head
259 proc error_popup msg {
263 message $w.m -text $msg -justify center -aspect 400
264 pack $w.m -side top -fill x -padx 20 -pady 20
265 button $w.ok -text OK -command "destroy $w"
266 pack $w.ok -side bottom -fill x
267 bind $w <Visibility> "grab $w; focus $w"
272 global canv canv2 canv3 linespc charspc ctext cflist textfont
273 global findtype findtypemenu findloc findstring fstring geometry
274 global entries sha1entry sha1string sha1but
275 global maincursor textcursor
276 global rowctxmenu gaudydiff
279 .bar add cascade -label "File" -menu .bar.file
281 .bar.file add command -label "Quit" -command doquit
283 .bar add cascade -label "Help" -menu .bar.help
284 .bar.help add command -label "About gitk" -command about
285 . configure -menu .bar
287 if {![info exists geometry(canv1)]} {
288 set geometry(canv1) [expr 45 * $charspc]
289 set geometry(canv2) [expr 30 * $charspc]
290 set geometry(canv3) [expr 15 * $charspc]
291 set geometry(canvh) [expr 25 * $linespc + 4]
292 set geometry(ctextw) 80
293 set geometry(ctexth) 30
294 set geometry(cflistw) 30
296 panedwindow .ctop -orient vertical
297 if {[info exists geometry(width)]} {
298 .ctop conf -width $geometry(width) -height $geometry(height)
299 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
300 set geometry(ctexth) [expr {($texth - 8) /
301 [font metrics $textfont -linespace]}]
305 pack .ctop.top.bar -side bottom -fill x
306 set cscroll .ctop.top.csb
307 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
308 pack $cscroll -side right -fill y
309 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
310 pack .ctop.top.clist -side top -fill both -expand 1
312 set canv .ctop.top.clist.canv
313 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
315 -yscrollincr $linespc -yscrollcommand "$cscroll set"
316 .ctop.top.clist add $canv
317 set canv2 .ctop.top.clist.canv2
318 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
319 -bg white -bd 0 -yscrollincr $linespc
320 .ctop.top.clist add $canv2
321 set canv3 .ctop.top.clist.canv3
322 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
323 -bg white -bd 0 -yscrollincr $linespc
324 .ctop.top.clist add $canv3
325 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
327 set sha1entry .ctop.top.bar.sha1
328 set entries $sha1entry
329 set sha1but .ctop.top.bar.sha1label
330 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
331 -command gotocommit -width 8
332 $sha1but conf -disabledforeground [$sha1but cget -foreground]
333 pack .ctop.top.bar.sha1label -side left
334 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
335 trace add variable sha1string write sha1change
336 pack $sha1entry -side left -pady 2
337 button .ctop.top.bar.findbut -text "Find" -command dofind
338 pack .ctop.top.bar.findbut -side left
340 set fstring .ctop.top.bar.findstring
341 lappend entries $fstring
342 entry $fstring -width 30 -font $textfont -textvariable findstring
343 pack $fstring -side left -expand 1 -fill x
345 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
346 findtype Exact IgnCase Regexp]
347 set findloc "All fields"
348 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
349 Comments Author Committer Files Pickaxe
350 pack .ctop.top.bar.findloc -side right
351 pack .ctop.top.bar.findtype -side right
352 # for making sure type==Exact whenever loc==Pickaxe
353 trace add variable findloc write findlocchange
355 panedwindow .ctop.cdet -orient horizontal
357 frame .ctop.cdet.left
358 set ctext .ctop.cdet.left.ctext
359 text $ctext -bg white -state disabled -font $textfont \
360 -width $geometry(ctextw) -height $geometry(ctexth) \
361 -yscrollcommand ".ctop.cdet.left.sb set"
362 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
363 pack .ctop.cdet.left.sb -side right -fill y
364 pack $ctext -side left -fill both -expand 1
365 .ctop.cdet add .ctop.cdet.left
367 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
369 $ctext tag conf hunksep -back blue -fore white
370 $ctext tag conf d0 -back "#ff8080"
371 $ctext tag conf d1 -back green
373 $ctext tag conf hunksep -fore blue
374 $ctext tag conf d0 -fore red
375 $ctext tag conf d1 -fore "#00a000"
376 $ctext tag conf found -back yellow
379 frame .ctop.cdet.right
380 set cflist .ctop.cdet.right.cfiles
381 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
382 -yscrollcommand ".ctop.cdet.right.sb set"
383 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
384 pack .ctop.cdet.right.sb -side right -fill y
385 pack $cflist -side left -fill both -expand 1
386 .ctop.cdet add .ctop.cdet.right
387 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
389 pack .ctop -side top -fill both -expand 1
391 bindall <1> {selcanvline %W %x %y}
392 #bindall <B1-Motion> {selcanvline %W %x %y}
393 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
394 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
395 bindall <2> "allcanvs scan mark 0 %y"
396 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
397 bind . <Key-Up> "selnextline -1"
398 bind . <Key-Down> "selnextline 1"
399 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
400 bind . <Key-Next> "allcanvs yview scroll 1 pages"
401 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
402 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
403 bindkey <Key-space> "$ctext yview scroll 1 pages"
404 bindkey p "selnextline -1"
405 bindkey n "selnextline 1"
406 bindkey b "$ctext yview scroll -1 pages"
407 bindkey d "$ctext yview scroll 18 units"
408 bindkey u "$ctext yview scroll -18 units"
409 bindkey / {findnext 1}
410 bindkey <Key-Return> {findnext 0}
413 bind . <Control-q> doquit
414 bind . <Control-f> dofind
415 bind . <Control-g> {findnext 0}
416 bind . <Control-r> findprev
417 bind . <Control-equal> {incrfont 1}
418 bind . <Control-KP_Add> {incrfont 1}
419 bind . <Control-minus> {incrfont -1}
420 bind . <Control-KP_Subtract> {incrfont -1}
421 bind $cflist <<ListboxSelect>> listboxsel
422 bind . <Destroy> {savestuff %W}
423 bind . <Button-1> "click %W"
424 bind $fstring <Key-Return> dofind
425 bind $sha1entry <Key-Return> gotocommit
426 bind $sha1entry <<PasteSelection>> clearsha1
428 set maincursor [. cget -cursor]
429 set textcursor [$ctext cget -cursor]
431 set rowctxmenu .rowctxmenu
432 menu $rowctxmenu -tearoff 0
433 $rowctxmenu add command -label "Diff this -> selected" \
434 -command {diffvssel 0}
435 $rowctxmenu add command -label "Diff selected -> this" \
436 -command {diffvssel 1}
437 $rowctxmenu add command -label "Make patch" -command mkpatch
438 $rowctxmenu add command -label "Create tag" -command mktag
439 $rowctxmenu add command -label "Write commit to file" -command writecommit
442 # when we make a key binding for the toplevel, make sure
443 # it doesn't get triggered when that key is pressed in the
444 # find string entry widget.
445 proc bindkey {ev script} {
448 set escript [bind Entry $ev]
449 if {$escript == {}} {
450 set escript [bind Entry <Key>]
453 bind $e $ev "$escript; break"
457 # set the focus back to the toplevel for any click outside
468 global canv canv2 canv3 ctext cflist mainfont textfont
470 if {$stuffsaved} return
471 if {![winfo viewable .]} return
473 set f [open "~/.gitk-new" w]
474 puts $f [list set mainfont $mainfont]
475 puts $f [list set textfont $textfont]
476 puts $f [list set findmergefiles $findmergefiles]
477 puts $f [list set gaudydiff $gaudydiff]
478 puts $f "set geometry(width) [winfo width .ctop]"
479 puts $f "set geometry(height) [winfo height .ctop]"
480 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
481 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
482 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
483 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
484 set wid [expr {([winfo width $ctext] - 8) \
485 / [font measure $textfont "0"]}]
486 puts $f "set geometry(ctextw) $wid"
487 set wid [expr {([winfo width $cflist] - 11) \
488 / [font measure [$cflist cget -font] "0"]}]
489 puts $f "set geometry(cflistw) $wid"
491 file rename -force "~/.gitk-new" "~/.gitk"
496 proc resizeclistpanes {win w} {
498 if [info exists oldwidth($win)] {
499 set s0 [$win sash coord 0]
500 set s1 [$win sash coord 1]
502 set sash0 [expr {int($w/2 - 2)}]
503 set sash1 [expr {int($w*5/6 - 2)}]
505 set factor [expr {1.0 * $w / $oldwidth($win)}]
506 set sash0 [expr {int($factor * [lindex $s0 0])}]
507 set sash1 [expr {int($factor * [lindex $s1 0])}]
511 if {$sash1 < $sash0 + 20} {
512 set sash1 [expr $sash0 + 20]
514 if {$sash1 > $w - 10} {
515 set sash1 [expr $w - 10]
516 if {$sash0 > $sash1 - 20} {
517 set sash0 [expr $sash1 - 20]
521 $win sash place 0 $sash0 [lindex $s0 1]
522 $win sash place 1 $sash1 [lindex $s1 1]
524 set oldwidth($win) $w
527 proc resizecdetpanes {win w} {
529 if [info exists oldwidth($win)] {
530 set s0 [$win sash coord 0]
532 set sash0 [expr {int($w*3/4 - 2)}]
534 set factor [expr {1.0 * $w / $oldwidth($win)}]
535 set sash0 [expr {int($factor * [lindex $s0 0])}]
539 if {$sash0 > $w - 15} {
540 set sash0 [expr $w - 15]
543 $win sash place 0 $sash0 [lindex $s0 1]
545 set oldwidth($win) $w
549 global canv canv2 canv3
555 proc bindall {event action} {
556 global canv canv2 canv3
557 bind $canv $event $action
558 bind $canv2 $event $action
559 bind $canv3 $event $action
564 if {[winfo exists $w]} {
569 wm title $w "About gitk"
573 Copyright © 2005 Paul Mackerras
575 Use and redistribute under the terms of the GNU General Public License} \
576 -justify center -aspect 400
577 pack $w.m -side top -fill x -padx 20 -pady 20
578 button $w.ok -text Close -command "destroy $w"
579 pack $w.ok -side bottom
582 proc assigncolor {id} {
583 global commitinfo colormap commcolors colors nextcolor
584 global parents nparents children nchildren
585 global cornercrossings crossings
587 if [info exists colormap($id)] return
588 set ncolors [llength $colors]
589 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
590 set child [lindex $children($id) 0]
591 if {[info exists colormap($child)]
592 && $nparents($child) == 1} {
593 set colormap($id) $colormap($child)
598 if {[info exists cornercrossings($id)]} {
599 foreach x $cornercrossings($id) {
600 if {[info exists colormap($x)]
601 && [lsearch -exact $badcolors $colormap($x)] < 0} {
602 lappend badcolors $colormap($x)
605 if {[llength $badcolors] >= $ncolors} {
609 set origbad $badcolors
610 if {[llength $badcolors] < $ncolors - 1} {
611 if {[info exists crossings($id)]} {
612 foreach x $crossings($id) {
613 if {[info exists colormap($x)]
614 && [lsearch -exact $badcolors $colormap($x)] < 0} {
615 lappend badcolors $colormap($x)
618 if {[llength $badcolors] >= $ncolors} {
619 set badcolors $origbad
622 set origbad $badcolors
624 if {[llength $badcolors] < $ncolors - 1} {
625 foreach child $children($id) {
626 if {[info exists colormap($child)]
627 && [lsearch -exact $badcolors $colormap($child)] < 0} {
628 lappend badcolors $colormap($child)
630 if {[info exists parents($child)]} {
631 foreach p $parents($child) {
632 if {[info exists colormap($p)]
633 && [lsearch -exact $badcolors $colormap($p)] < 0} {
634 lappend badcolors $colormap($p)
639 if {[llength $badcolors] >= $ncolors} {
640 set badcolors $origbad
643 for {set i 0} {$i <= $ncolors} {incr i} {
644 set c [lindex $colors $nextcolor]
645 if {[incr nextcolor] >= $ncolors} {
648 if {[lsearch -exact $badcolors $c]} break
654 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
655 global mainline sidelines
656 global nchildren ncleft
663 set lthickness [expr {int($linespc / 9) + 1}]
664 catch {unset mainline}
665 catch {unset sidelines}
666 foreach id [array names nchildren] {
667 set ncleft($id) $nchildren($id)
671 proc bindline {t id} {
674 $canv bind $t <Enter> "lineenter %x %y $id"
675 $canv bind $t <Motion> "linemotion %x %y $id"
676 $canv bind $t <Leave> "lineleave $id"
677 $canv bind $t <Button-1> "lineclick %x %y $id"
680 proc drawcommitline {level} {
681 global parents children nparents nchildren todo
682 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
683 global lineid linehtag linentag linedtag commitinfo
684 global colormap numcommits currentparents dupparents
685 global oldlevel oldnlines oldtodo
686 global idtags idline idheads
687 global lineno lthickness mainline sidelines
688 global commitlisted rowtextx idpos
692 set id [lindex $todo $level]
693 set lineid($lineno) $id
694 set idline($id) $lineno
695 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
696 if {![info exists commitinfo($id)]} {
698 if {![info exists commitinfo($id)]} {
699 set commitinfo($id) {"No commit information available"}
704 set currentparents {}
706 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
707 foreach p $parents($id) {
708 if {[lsearch -exact $currentparents $p] < 0} {
709 lappend currentparents $p
711 # remember that this parent was listed twice
712 lappend dupparents $p
716 set x [expr $canvx0 + $level * $linespc]
718 set canvy [expr $canvy + $linespc]
719 allcanvs conf -scrollregion \
720 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
721 if {[info exists mainline($id)]} {
722 lappend mainline($id) $x $y1
723 set t [$canv create line $mainline($id) \
724 -width $lthickness -fill $colormap($id)]
728 if {[info exists sidelines($id)]} {
729 foreach ls $sidelines($id) {
730 set coords [lindex $ls 0]
731 set thick [lindex $ls 1]
732 set t [$canv create line $coords -fill $colormap($id) \
733 -width [expr {$thick * $lthickness}]]
738 set orad [expr {$linespc / 3}]
739 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
740 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
741 -fill $ofill -outline black -width 1]
743 $canv bind $t <1> {selcanvline {} %x %y}
744 set xt [expr $canvx0 + [llength $todo] * $linespc]
745 if {[llength $currentparents] > 2} {
746 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
748 set rowtextx($lineno) $xt
749 set idpos($id) [list $x $xt $y1]
750 if {[info exists idtags($id)] || [info exists idheads($id)]} {
751 set xt [drawtags $id $x $xt $y1]
753 set headline [lindex $commitinfo($id) 0]
754 set name [lindex $commitinfo($id) 1]
755 set date [lindex $commitinfo($id) 2]
756 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
757 -text $headline -font $mainfont ]
758 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
759 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
760 -text $name -font $namefont]
761 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
762 -text $date -font $mainfont]
765 proc drawtags {id x xt y1} {
766 global idtags idheads
767 global linespc lthickness
772 if {[info exists idtags($id)]} {
773 set marks $idtags($id)
774 set ntags [llength $marks]
776 if {[info exists idheads($id)]} {
777 set marks [concat $marks $idheads($id)]
783 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
784 set yt [expr $y1 - 0.5 * $linespc]
785 set yb [expr $yt + $linespc - 1]
789 set wid [font measure $mainfont $tag]
792 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
794 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
795 -width $lthickness -fill black -tags tag.$id]
797 foreach tag $marks x $xvals wid $wvals {
798 set xl [expr $x + $delta]
799 set xr [expr $x + $delta + $wid + $lthickness]
800 if {[incr ntags -1] >= 0} {
802 $canv create polygon $x [expr $yt + $delta] $xl $yt\
803 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
804 -width 1 -outline black -fill yellow -tags tag.$id
807 set xl [expr $xl - $delta/2]
808 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
809 -width 1 -outline black -fill green -tags tag.$id
811 $canv create text $xl $y1 -anchor w -text $tag \
812 -font $mainfont -tags tag.$id
817 proc updatetodo {level noshortcut} {
818 global currentparents ncleft todo
819 global mainline oldlevel oldtodo oldnlines
820 global canvx0 canvy linespc mainline
825 set oldnlines [llength $todo]
826 if {!$noshortcut && [llength $currentparents] == 1} {
827 set p [lindex $currentparents 0]
828 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
830 set x [expr $canvx0 + $level * $linespc]
831 set y [expr $canvy - $linespc]
832 set mainline($p) [list $x $y]
833 set todo [lreplace $todo $level $level $p]
838 set todo [lreplace $todo $level $level]
840 foreach p $currentparents {
842 set k [lsearch -exact $todo $p]
844 set todo [linsert $todo $i $p]
851 proc notecrossings {id lo hi corner} {
852 global oldtodo crossings cornercrossings
854 for {set i $lo} {[incr i] < $hi} {} {
855 set p [lindex $oldtodo $i]
856 if {$p == {}} continue
858 if {![info exists cornercrossings($id)]
859 || [lsearch -exact $cornercrossings($id) $p] < 0} {
860 lappend cornercrossings($id) $p
862 if {![info exists cornercrossings($p)]
863 || [lsearch -exact $cornercrossings($p) $id] < 0} {
864 lappend cornercrossings($p) $id
867 if {![info exists crossings($id)]
868 || [lsearch -exact $crossings($id) $p] < 0} {
869 lappend crossings($id) $p
871 if {![info exists crossings($p)]
872 || [lsearch -exact $crossings($p) $id] < 0} {
873 lappend crossings($p) $id
880 global canv mainline sidelines canvx0 canvy linespc
881 global oldlevel oldtodo todo currentparents dupparents
882 global lthickness linespc canvy colormap
884 set y1 [expr $canvy - $linespc]
887 foreach id $oldtodo {
889 if {$id == {}} continue
890 set xi [expr {$canvx0 + $i * $linespc}]
891 if {$i == $oldlevel} {
892 foreach p $currentparents {
893 set j [lsearch -exact $todo $p]
894 set coords [list $xi $y1]
895 set xj [expr {$canvx0 + $j * $linespc}]
897 lappend coords [expr $xj + $linespc] $y1
898 notecrossings $p $j $i [expr {$j + 1}]
899 } elseif {$j > $i + 1} {
900 lappend coords [expr $xj - $linespc] $y1
901 notecrossings $p $i $j [expr {$j - 1}]
903 if {[lsearch -exact $dupparents $p] >= 0} {
904 # draw a double-width line to indicate the doubled parent
905 lappend coords $xj $y2
906 lappend sidelines($p) [list $coords 2]
907 if {![info exists mainline($p)]} {
908 set mainline($p) [list $xj $y2]
911 # normal case, no parent duplicated
912 if {![info exists mainline($p)]} {
914 lappend coords $xj $y2
916 set mainline($p) $coords
918 lappend coords $xj $y2
919 lappend sidelines($p) [list $coords 1]
923 } elseif {[lindex $todo $i] != $id} {
924 set j [lsearch -exact $todo $id]
925 set xj [expr {$canvx0 + $j * $linespc}]
926 lappend mainline($id) $xi $y1 $xj $y2
931 proc decidenext {{noread 0}} {
932 global parents children nchildren ncleft todo
933 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
934 global datemode cdate
936 global currentparents oldlevel oldnlines oldtodo
937 global lineno lthickness
939 # remove the null entry if present
940 set nullentry [lsearch -exact $todo {}]
941 if {$nullentry >= 0} {
942 set todo [lreplace $todo $nullentry $nullentry]
945 # choose which one to do next time around
946 set todol [llength $todo]
949 for {set k $todol} {[incr k -1] >= 0} {} {
950 set p [lindex $todo $k]
951 if {$ncleft($p) == 0} {
953 if {![info exists commitinfo($p)]} {
959 if {$latest == {} || $cdate($p) > $latest} {
961 set latest $cdate($p)
971 puts "ERROR: none of the pending commits can be done yet:"
973 puts " $p ($ncleft($p))"
979 # If we are reducing, put in a null entry
980 if {$todol < $oldnlines} {
981 if {$nullentry >= 0} {
984 && [lindex $oldtodo $i] == [lindex $todo $i]} {
994 set todo [linsert $todo $i {}]
1003 proc drawcommit {id} {
1004 global phase todo nchildren datemode nextupdate
1007 if {$phase != "incrdraw"} {
1010 set startcommits $id
1013 updatetodo 0 $datemode
1015 if {$nchildren($id) == 0} {
1017 lappend startcommits $id
1019 set level [decidenext 1]
1020 if {$level == {} || $id != [lindex $todo $level]} {
1025 drawcommitline $level
1026 if {[updatetodo $level $datemode]} {
1027 set level [decidenext 1]
1028 if {$level == {}} break
1030 set id [lindex $todo $level]
1031 if {![info exists commitlisted($id)]} {
1034 if {[clock clicks -milliseconds] >= $nextupdate} {
1042 proc finishcommits {} {
1045 global canv mainfont ctext maincursor textcursor
1047 if {$phase != "incrdraw"} {
1049 $canv create text 3 3 -anchor nw -text "No commits selected" \
1050 -font $mainfont -tags textitems
1054 set level [decidenext]
1055 drawrest $level [llength $startcommits]
1057 . config -cursor $maincursor
1058 $ctext config -cursor $textcursor
1062 global nextupdate startmsecs startcommits todo
1064 if {$startcommits == {}} return
1065 set startmsecs [clock clicks -milliseconds]
1066 set nextupdate [expr $startmsecs + 100]
1068 set todo [lindex $startcommits 0]
1072 proc drawrest {level startix} {
1073 global phase stopped redisplaying selectedline
1074 global datemode currentparents todo
1076 global nextupdate startmsecs startcommits idline
1080 set startid [lindex $startcommits $startix]
1082 if {$startid != {}} {
1083 set startline $idline($startid)
1087 drawcommitline $level
1088 set hard [updatetodo $level $datemode]
1089 if {$numcommits == $startline} {
1090 lappend todo $startid
1093 set startid [lindex $startcommits $startix]
1095 if {$startid != {}} {
1096 set startline $idline($startid)
1100 set level [decidenext]
1101 if {$level < 0} break
1104 if {[clock clicks -milliseconds] >= $nextupdate} {
1111 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1112 #puts "overall $drawmsecs ms for $numcommits commits"
1113 if {$redisplaying} {
1114 if {$stopped == 0 && [info exists selectedline]} {
1115 selectline $selectedline
1117 if {$stopped == 1} {
1119 after idle drawgraph
1126 proc findmatches {f} {
1127 global findtype foundstring foundstrlen
1128 if {$findtype == "Regexp"} {
1129 set matches [regexp -indices -all -inline $foundstring $f]
1131 if {$findtype == "IgnCase"} {
1132 set str [string tolower $f]
1138 while {[set j [string first $foundstring $str $i]] >= 0} {
1139 lappend matches [list $j [expr $j+$foundstrlen-1]]
1140 set i [expr $j + $foundstrlen]
1147 global findtype findloc findstring markedmatches commitinfo
1148 global numcommits lineid linehtag linentag linedtag
1149 global mainfont namefont canv canv2 canv3 selectedline
1150 global matchinglines foundstring foundstrlen
1155 set matchinglines {}
1156 if {$findloc == "Pickaxe"} {
1160 if {$findtype == "IgnCase"} {
1161 set foundstring [string tolower $findstring]
1163 set foundstring $findstring
1165 set foundstrlen [string length $findstring]
1166 if {$foundstrlen == 0} return
1167 if {$findloc == "Files"} {
1171 if {![info exists selectedline]} {
1174 set oldsel $selectedline
1177 set fldtypes {Headline Author Date Committer CDate Comment}
1178 for {set l 0} {$l < $numcommits} {incr l} {
1180 set info $commitinfo($id)
1182 foreach f $info ty $fldtypes {
1183 if {$findloc != "All fields" && $findloc != $ty} {
1186 set matches [findmatches $f]
1187 if {$matches == {}} continue
1189 if {$ty == "Headline"} {
1190 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1191 } elseif {$ty == "Author"} {
1192 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1193 } elseif {$ty == "Date"} {
1194 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1198 lappend matchinglines $l
1199 if {!$didsel && $l > $oldsel} {
1205 if {$matchinglines == {}} {
1207 } elseif {!$didsel} {
1208 findselectline [lindex $matchinglines 0]
1212 proc findselectline {l} {
1213 global findloc commentend ctext
1215 if {$findloc == "All fields" || $findloc == "Comments"} {
1216 # highlight the matches in the comments
1217 set f [$ctext get 1.0 $commentend]
1218 set matches [findmatches $f]
1219 foreach match $matches {
1220 set start [lindex $match 0]
1221 set end [expr [lindex $match 1] + 1]
1222 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1227 proc findnext {restart} {
1228 global matchinglines selectedline
1229 if {![info exists matchinglines]} {
1235 if {![info exists selectedline]} return
1236 foreach l $matchinglines {
1237 if {$l > $selectedline} {
1246 global matchinglines selectedline
1247 if {![info exists matchinglines]} {
1251 if {![info exists selectedline]} return
1253 foreach l $matchinglines {
1254 if {$l >= $selectedline} break
1258 findselectline $prev
1264 proc findlocchange {name ix op} {
1265 global findloc findtype findtypemenu
1266 if {$findloc == "Pickaxe"} {
1272 $findtypemenu entryconf 1 -state $state
1273 $findtypemenu entryconf 2 -state $state
1276 proc stopfindproc {{done 0}} {
1277 global findprocpid findprocfile findids
1278 global ctext findoldcursor phase maincursor textcursor
1279 global findinprogress
1281 catch {unset findids}
1282 if {[info exists findprocpid]} {
1284 catch {exec kill $findprocpid}
1286 catch {close $findprocfile}
1289 if {[info exists findinprogress]} {
1290 unset findinprogress
1291 if {$phase != "incrdraw"} {
1292 . config -cursor $maincursor
1293 $ctext config -cursor $textcursor
1298 proc findpatches {} {
1299 global findstring selectedline numcommits
1300 global findprocpid findprocfile
1301 global finddidsel ctext lineid findinprogress
1302 global findinsertpos
1304 if {$numcommits == 0} return
1306 # make a list of all the ids to search, starting at the one
1307 # after the selected line (if any)
1308 if {[info exists selectedline]} {
1314 for {set i 0} {$i < $numcommits} {incr i} {
1315 if {[incr l] >= $numcommits} {
1318 append inputids $lineid($l) "\n"
1322 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1325 error_popup "Error starting search process: $err"
1329 set findinsertpos end
1331 set findprocpid [pid $f]
1332 fconfigure $f -blocking 0
1333 fileevent $f readable readfindproc
1335 . config -cursor watch
1336 $ctext config -cursor watch
1337 set findinprogress 1
1340 proc readfindproc {} {
1341 global findprocfile finddidsel
1342 global idline matchinglines findinsertpos
1344 set n [gets $findprocfile line]
1346 if {[eof $findprocfile]} {
1354 if {![regexp {^[0-9a-f]{40}} $line id]} {
1355 error_popup "Can't parse git-diff-tree output: $line"
1359 if {![info exists idline($id)]} {
1360 puts stderr "spurious id: $id"
1367 proc insertmatch {l id} {
1368 global matchinglines findinsertpos finddidsel
1370 if {$findinsertpos == "end"} {
1371 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1372 set matchinglines [linsert $matchinglines 0 $l]
1375 lappend matchinglines $l
1378 set matchinglines [linsert $matchinglines $findinsertpos $l]
1389 global selectedline numcommits lineid ctext
1390 global ffileline finddidsel parents nparents
1391 global findinprogress findstartline findinsertpos
1392 global treediffs fdiffids fdiffsneeded fdiffpos
1393 global findmergefiles
1395 if {$numcommits == 0} return
1397 if {[info exists selectedline]} {
1398 set l [expr {$selectedline + 1}]
1403 set findstartline $l
1408 if {$findmergefiles || $nparents($id) == 1} {
1409 foreach p $parents($id) {
1410 if {![info exists treediffs([list $id $p])]} {
1411 append diffsneeded "$id $p\n"
1412 lappend fdiffsneeded [list $id $p]
1416 if {[incr l] >= $numcommits} {
1419 if {$l == $findstartline} break
1422 # start off a git-diff-tree process if needed
1423 if {$diffsneeded ne {}} {
1425 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1427 error_popup "Error starting search process: $err"
1430 catch {unset fdiffids}
1432 fconfigure $df -blocking 0
1433 fileevent $df readable [list readfilediffs $df]
1437 set findinsertpos end
1439 set p [lindex $parents($id) 0]
1440 . config -cursor watch
1441 $ctext config -cursor watch
1442 set findinprogress 1
1443 findcont [list $id $p]
1447 proc readfilediffs {df} {
1448 global findids fdiffids fdiffs
1450 set n [gets $df line]
1454 if {[catch {close $df} err]} {
1457 error_popup "Error in git-diff-tree: $err"
1458 } elseif {[info exists findids]} {
1462 error_popup "Couldn't find diffs for {$ids}"
1467 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1468 # start of a new string of diffs
1470 set fdiffids [list $id $p]
1472 } elseif {[string match ":*" $line]} {
1473 lappend fdiffs [lindex $line 5]
1477 proc donefilediff {} {
1478 global fdiffids fdiffs treediffs findids
1479 global fdiffsneeded fdiffpos
1481 if {[info exists fdiffids]} {
1482 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1483 && $fdiffpos < [llength $fdiffsneeded]} {
1484 # git-diff-tree doesn't output anything for a commit
1485 # which doesn't change anything
1486 set nullids [lindex $fdiffsneeded $fdiffpos]
1487 set treediffs($nullids) {}
1488 if {[info exists findids] && $nullids eq $findids} {
1496 if {![info exists treediffs($fdiffids)]} {
1497 set treediffs($fdiffids) $fdiffs
1499 if {[info exists findids] && $fdiffids eq $findids} {
1506 proc findcont {ids} {
1507 global findids treediffs parents nparents
1508 global ffileline findstartline finddidsel
1509 global lineid numcommits matchinglines findinprogress
1510 global findmergefiles
1512 set id [lindex $ids 0]
1513 set p [lindex $ids 1]
1514 set pi [lsearch -exact $parents($id) $p]
1517 if {$findmergefiles || $nparents($id) == 1} {
1518 if {![info exists treediffs($ids)]} {
1524 foreach f $treediffs($ids) {
1525 set x [findmatches $f]
1533 set pi $nparents($id)
1536 set pi $nparents($id)
1538 if {[incr pi] >= $nparents($id)} {
1540 if {[incr l] >= $numcommits} {
1543 if {$l == $findstartline} break
1546 set p [lindex $parents($id) $pi]
1547 set ids [list $id $p]
1555 # mark a commit as matching by putting a yellow background
1556 # behind the headline
1557 proc markheadline {l id} {
1558 global canv mainfont linehtag commitinfo
1560 set bbox [$canv bbox $linehtag($l)]
1561 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1565 # mark the bits of a headline, author or date that match a find string
1566 proc markmatches {canv l str tag matches font} {
1567 set bbox [$canv bbox $tag]
1568 set x0 [lindex $bbox 0]
1569 set y0 [lindex $bbox 1]
1570 set y1 [lindex $bbox 3]
1571 foreach match $matches {
1572 set start [lindex $match 0]
1573 set end [lindex $match 1]
1574 if {$start > $end} continue
1575 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1576 set xlen [font measure $font [string range $str 0 [expr $end]]]
1577 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1578 -outline {} -tags matches -fill yellow]
1583 proc unmarkmatches {} {
1584 global matchinglines findids
1585 allcanvs delete matches
1586 catch {unset matchinglines}
1587 catch {unset findids}
1590 proc selcanvline {w x y} {
1591 global canv canvy0 ctext linespc selectedline
1592 global lineid linehtag linentag linedtag rowtextx
1593 set ymax [lindex [$canv cget -scrollregion] 3]
1594 if {$ymax == {}} return
1595 set yfrac [lindex [$canv yview] 0]
1596 set y [expr {$y + $yfrac * $ymax}]
1597 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1602 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1608 proc selectline {l} {
1609 global canv canv2 canv3 ctext commitinfo selectedline
1610 global lineid linehtag linentag linedtag
1611 global canvy0 linespc parents nparents
1612 global cflist currentid sha1entry
1613 global commentend idtags
1615 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1617 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1618 -tags secsel -fill [$canv cget -selectbackground]]
1620 $canv2 delete secsel
1621 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1622 -tags secsel -fill [$canv2 cget -selectbackground]]
1624 $canv3 delete secsel
1625 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1626 -tags secsel -fill [$canv3 cget -selectbackground]]
1628 set y [expr {$canvy0 + $l * $linespc}]
1629 set ymax [lindex [$canv cget -scrollregion] 3]
1630 set ytop [expr {$y - $linespc - 1}]
1631 set ybot [expr {$y + $linespc + 1}]
1632 set wnow [$canv yview]
1633 set wtop [expr [lindex $wnow 0] * $ymax]
1634 set wbot [expr [lindex $wnow 1] * $ymax]
1635 set wh [expr {$wbot - $wtop}]
1637 if {$ytop < $wtop} {
1638 if {$ybot < $wtop} {
1639 set newtop [expr {$y - $wh / 2.0}]
1642 if {$newtop > $wtop - $linespc} {
1643 set newtop [expr {$wtop - $linespc}]
1646 } elseif {$ybot > $wbot} {
1647 if {$ytop > $wbot} {
1648 set newtop [expr {$y - $wh / 2.0}]
1650 set newtop [expr {$ybot - $wh}]
1651 if {$newtop < $wtop + $linespc} {
1652 set newtop [expr {$wtop + $linespc}]
1656 if {$newtop != $wtop} {
1660 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1666 $sha1entry delete 0 end
1667 $sha1entry insert 0 $id
1668 $sha1entry selection from 0
1669 $sha1entry selection to end
1671 $ctext conf -state normal
1672 $ctext delete 0.0 end
1673 $ctext mark set fmark.0 0.0
1674 $ctext mark gravity fmark.0 left
1675 set info $commitinfo($id)
1676 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1677 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1678 if {[info exists idtags($id)]} {
1679 $ctext insert end "Tags:"
1680 foreach tag $idtags($id) {
1681 $ctext insert end " $tag"
1683 $ctext insert end "\n"
1685 $ctext insert end "\n"
1686 $ctext insert end [lindex $info 5]
1687 $ctext insert end "\n"
1688 $ctext tag delete Comments
1689 $ctext tag remove found 1.0 end
1690 $ctext conf -state disabled
1691 set commentend [$ctext index "end - 1c"]
1693 $cflist delete 0 end
1694 $cflist insert end "Comments"
1695 if {$nparents($id) == 1} {
1696 startdiff [concat $id $parents($id)]
1697 } elseif {$nparents($id) > 1} {
1702 proc selnextline {dir} {
1704 if {![info exists selectedline]} return
1705 set l [expr $selectedline + $dir]
1710 proc mergediff {id} {
1711 global parents diffmergeid diffmergegca mergefilelist diffpindex
1715 set diffmergegca [findgca $parents($id)]
1716 if {[info exists mergefilelist($id)]} {
1723 proc findgca {ids} {
1730 set gca [exec git-merge-base $gca $id]
1739 proc contmergediff {ids} {
1740 global diffmergeid diffpindex parents nparents diffmergegca
1741 global treediffs mergefilelist diffids
1743 # diff the child against each of the parents, and diff
1744 # each of the parents against the GCA.
1746 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1747 set ids [list [lindex $ids 1] $diffmergegca]
1749 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1750 set p [lindex $parents($diffmergeid) $diffpindex]
1751 set ids [list $diffmergeid $p]
1753 if {![info exists treediffs($ids)]} {
1760 # If a file in some parent is different from the child and also
1761 # different from the GCA, then it's interesting.
1762 # If we don't have a GCA, then a file is interesting if it is
1763 # different from the child in all the parents.
1764 if {$diffmergegca ne {}} {
1766 foreach p $parents($diffmergeid) {
1767 set gcadiffs $treediffs([list $p $diffmergegca])
1768 foreach f $treediffs([list $diffmergeid $p]) {
1769 if {[lsearch -exact $files $f] < 0
1770 && [lsearch -exact $gcadiffs $f] >= 0} {
1775 set files [lsort $files]
1777 set p [lindex $parents($diffmergeid) 0]
1778 set files $treediffs([list $diffmergeid $p])
1779 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1780 set p [lindex $parents($diffmergeid) $i]
1781 set df $treediffs([list $diffmergeid $p])
1784 if {[lsearch -exact $df $f] >= 0} {
1792 set mergefilelist($diffmergeid) $files
1796 proc showmergediff {} {
1797 global cflist diffmergeid mergefilelist
1799 set files $mergefilelist($diffmergeid)
1801 $cflist insert end $f
1805 proc startdiff {ids} {
1806 global treediffs diffids treepending diffmergeid
1809 catch {unset diffmergeid}
1810 if {![info exists treediffs($ids)]} {
1811 if {![info exists treepending]} {
1819 proc addtocflist {ids} {
1820 global treediffs cflist
1821 foreach f $treediffs($ids) {
1822 $cflist insert end $f
1827 proc gettreediffs {ids} {
1828 global treediff parents treepending
1829 set treepending $ids
1831 set id [lindex $ids 0]
1832 set p [lindex $ids 1]
1833 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1834 fconfigure $gdtf -blocking 0
1835 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
1838 proc gettreediffline {gdtf ids} {
1839 global treediff treediffs treepending diffids diffmergeid
1841 set n [gets $gdtf line]
1843 if {![eof $gdtf]} return
1845 set treediffs($ids) $treediff
1847 if {$ids != $diffids} {
1848 gettreediffs $diffids
1850 if {[info exists diffmergeid]} {
1858 set file [lindex $line 5]
1859 lappend treediff $file
1862 proc getblobdiffs {ids} {
1863 global diffopts blobdifffd diffids env curdifftag curtagstart
1864 global difffilestart nextupdate diffinhdr treediffs
1866 set id [lindex $ids 0]
1867 set p [lindex $ids 1]
1868 set env(GIT_DIFF_OPTS) $diffopts
1869 set cmd [list | git-diff-tree -r -p -C $p $id]
1870 if {[catch {set bdf [open $cmd r]} err]} {
1871 puts "error getting diffs: $err"
1875 fconfigure $bdf -blocking 0
1876 set blobdifffd($ids) $bdf
1877 set curdifftag Comments
1879 catch {unset difffilestart}
1880 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
1881 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1884 proc getblobdiffline {bdf ids} {
1885 global diffids blobdifffd ctext curdifftag curtagstart
1886 global diffnexthead diffnextnote difffilestart
1887 global nextupdate diffinhdr treediffs
1890 set n [gets $bdf line]
1894 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1895 $ctext tag add $curdifftag $curtagstart end
1900 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1903 $ctext conf -state normal
1904 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
1905 # start of a new file
1906 $ctext insert end "\n"
1907 $ctext tag add $curdifftag $curtagstart end
1908 set curtagstart [$ctext index "end - 1c"]
1910 set here [$ctext index "end - 1c"]
1911 set i [lsearch -exact $treediffs($diffids) $fname]
1913 set difffilestart($i) $here
1915 $ctext mark set fmark.$i $here
1916 $ctext mark gravity fmark.$i left
1918 if {$newname != $fname} {
1919 set i [lsearch -exact $treediffs($diffids) $newname]
1921 set difffilestart($i) $here
1923 $ctext mark set fmark.$i $here
1924 $ctext mark gravity fmark.$i left
1927 set curdifftag "f:$fname"
1928 $ctext tag delete $curdifftag
1929 set l [expr {(78 - [string length $header]) / 2}]
1930 set pad [string range "----------------------------------------" 1 $l]
1931 $ctext insert end "$pad $header $pad\n" filesep
1933 } elseif {[regexp {^(---|\+\+\+)} $line]} {
1935 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1936 $line match f1l f1c f2l f2c rest]} {
1938 $ctext insert end "\t" hunksep
1939 $ctext insert end " $f1l " d0 " $f2l " d1
1940 $ctext insert end " $rest \n" hunksep
1942 $ctext insert end "$line\n" hunksep
1946 set x [string range $line 0 0]
1947 if {$x == "-" || $x == "+"} {
1948 set tag [expr {$x == "+"}]
1950 set line [string range $line 1 end]
1952 $ctext insert end "$line\n" d$tag
1953 } elseif {$x == " "} {
1955 set line [string range $line 1 end]
1957 $ctext insert end "$line\n"
1958 } elseif {$diffinhdr || $x == "\\"} {
1959 # e.g. "\ No newline at end of file"
1960 $ctext insert end "$line\n" filesep
1962 # Something else we don't recognize
1963 if {$curdifftag != "Comments"} {
1964 $ctext insert end "\n"
1965 $ctext tag add $curdifftag $curtagstart end
1966 set curtagstart [$ctext index "end - 1c"]
1967 set curdifftag Comments
1969 $ctext insert end "$line\n" filesep
1972 $ctext conf -state disabled
1973 if {[clock clicks -milliseconds] >= $nextupdate} {
1975 fileevent $bdf readable {}
1977 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1982 global difffilestart ctext
1983 set here [$ctext index @0,0]
1984 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1985 if {[$ctext compare $difffilestart($i) > $here]} {
1986 if {![info exists pos]
1987 || [$ctext compare $difffilestart($i) < $pos]} {
1988 set pos $difffilestart($i)
1992 if {[info exists pos]} {
1997 proc listboxsel {} {
1998 global ctext cflist currentid
1999 if {![info exists currentid]} return
2000 set sel [lsort [$cflist curselection]]
2001 if {$sel eq {}} return
2002 set first [lindex $sel 0]
2003 catch {$ctext yview fmark.$first}
2007 global linespc charspc canvx0 canvy0 mainfont
2008 set linespc [font metrics $mainfont -linespace]
2009 set charspc [font measure $mainfont "m"]
2010 set canvy0 [expr 3 + 0.5 * $linespc]
2011 set canvx0 [expr 3 + 0.5 * $linespc]
2015 global selectedline stopped redisplaying phase
2016 if {$stopped > 1} return
2017 if {$phase == "getcommits"} return
2019 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2026 proc incrfont {inc} {
2027 global mainfont namefont textfont selectedline ctext canv phase
2028 global stopped entries
2030 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2031 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2032 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2034 $ctext conf -font $textfont
2035 $ctext tag conf filesep -font [concat $textfont bold]
2036 foreach e $entries {
2037 $e conf -font $mainfont
2039 if {$phase == "getcommits"} {
2040 $canv itemconf textitems -font $mainfont
2046 global sha1entry sha1string
2047 if {[string length $sha1string] == 40} {
2048 $sha1entry delete 0 end
2052 proc sha1change {n1 n2 op} {
2053 global sha1string currentid sha1but
2054 if {$sha1string == {}
2055 || ([info exists currentid] && $sha1string == $currentid)} {
2060 if {[$sha1but cget -state] == $state} return
2061 if {$state == "normal"} {
2062 $sha1but conf -state normal -relief raised -text "Goto: "
2064 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2068 proc gotocommit {} {
2069 global sha1string currentid idline tagids
2070 global lineid numcommits
2072 if {$sha1string == {}
2073 || ([info exists currentid] && $sha1string == $currentid)} return
2074 if {[info exists tagids($sha1string)]} {
2075 set id $tagids($sha1string)
2077 set id [string tolower $sha1string]
2078 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2080 for {set l 0} {$l < $numcommits} {incr l} {
2081 if {[string match $id* $lineid($l)]} {
2082 lappend matches $lineid($l)
2085 if {$matches ne {}} {
2086 if {[llength $matches] > 1} {
2087 error_popup "Short SHA1 id $id is ambiguous"
2090 set id [lindex $matches 0]
2094 if {[info exists idline($id)]} {
2095 selectline $idline($id)
2098 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2103 error_popup "$type $sha1string is not known"
2106 proc lineenter {x y id} {
2107 global hoverx hovery hoverid hovertimer
2108 global commitinfo canv
2110 if {![info exists commitinfo($id)]} return
2114 if {[info exists hovertimer]} {
2115 after cancel $hovertimer
2117 set hovertimer [after 500 linehover]
2121 proc linemotion {x y id} {
2122 global hoverx hovery hoverid hovertimer
2124 if {[info exists hoverid] && $id == $hoverid} {
2127 if {[info exists hovertimer]} {
2128 after cancel $hovertimer
2130 set hovertimer [after 500 linehover]
2134 proc lineleave {id} {
2135 global hoverid hovertimer canv
2137 if {[info exists hoverid] && $id == $hoverid} {
2139 if {[info exists hovertimer]} {
2140 after cancel $hovertimer
2148 global hoverx hovery hoverid hovertimer
2149 global canv linespc lthickness
2150 global commitinfo mainfont
2152 set text [lindex $commitinfo($hoverid) 0]
2153 set ymax [lindex [$canv cget -scrollregion] 3]
2154 if {$ymax == {}} return
2155 set yfrac [lindex [$canv yview] 0]
2156 set x [expr {$hoverx + 2 * $linespc}]
2157 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2158 set x0 [expr {$x - 2 * $lthickness}]
2159 set y0 [expr {$y - 2 * $lthickness}]
2160 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2161 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2162 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2163 -fill \#ffff80 -outline black -width 1 -tags hover]
2165 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2169 proc lineclick {x y id} {
2170 global ctext commitinfo children cflist canv
2174 # fill the details pane with info about this line
2175 $ctext conf -state normal
2176 $ctext delete 0.0 end
2177 $ctext insert end "Parent:\n "
2178 catch {destroy $ctext.$id}
2179 button $ctext.$id -text "Go:" -command "selbyid $id" \
2181 $ctext window create end -window $ctext.$id -align center
2182 set info $commitinfo($id)
2183 $ctext insert end "\t[lindex $info 0]\n"
2184 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2185 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2186 $ctext insert end "\tID:\t$id\n"
2187 if {[info exists children($id)]} {
2188 $ctext insert end "\nChildren:"
2189 foreach child $children($id) {
2190 $ctext insert end "\n "
2191 catch {destroy $ctext.$child}
2192 button $ctext.$child -text "Go:" -command "selbyid $child" \
2194 $ctext window create end -window $ctext.$child -align center
2195 set info $commitinfo($child)
2196 $ctext insert end "\t[lindex $info 0]"
2199 $ctext conf -state disabled
2201 $cflist delete 0 end
2206 if {[info exists idline($id)]} {
2207 selectline $idline($id)
2213 if {![info exists startmstime]} {
2214 set startmstime [clock clicks -milliseconds]
2216 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2219 proc rowmenu {x y id} {
2220 global rowctxmenu idline selectedline rowmenuid
2222 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2227 $rowctxmenu entryconfigure 0 -state $state
2228 $rowctxmenu entryconfigure 1 -state $state
2229 $rowctxmenu entryconfigure 2 -state $state
2231 tk_popup $rowctxmenu $x $y
2234 proc diffvssel {dirn} {
2235 global rowmenuid selectedline lineid
2239 if {![info exists selectedline]} return
2241 set oldid $lineid($selectedline)
2242 set newid $rowmenuid
2244 set oldid $rowmenuid
2245 set newid $lineid($selectedline)
2247 $ctext conf -state normal
2248 $ctext delete 0.0 end
2249 $ctext mark set fmark.0 0.0
2250 $ctext mark gravity fmark.0 left
2251 $cflist delete 0 end
2252 $cflist insert end "Top"
2253 $ctext insert end "From $oldid\n "
2254 $ctext insert end [lindex $commitinfo($oldid) 0]
2255 $ctext insert end "\n\nTo $newid\n "
2256 $ctext insert end [lindex $commitinfo($newid) 0]
2257 $ctext insert end "\n"
2258 $ctext conf -state disabled
2259 $ctext tag delete Comments
2260 $ctext tag remove found 1.0 end
2261 startdiff $newid [list $oldid]
2265 global rowmenuid currentid commitinfo patchtop patchnum
2267 if {![info exists currentid]} return
2268 set oldid $currentid
2269 set oldhead [lindex $commitinfo($oldid) 0]
2270 set newid $rowmenuid
2271 set newhead [lindex $commitinfo($newid) 0]
2274 catch {destroy $top}
2276 label $top.title -text "Generate patch"
2277 grid $top.title - -pady 10
2278 label $top.from -text "From:"
2279 entry $top.fromsha1 -width 40 -relief flat
2280 $top.fromsha1 insert 0 $oldid
2281 $top.fromsha1 conf -state readonly
2282 grid $top.from $top.fromsha1 -sticky w
2283 entry $top.fromhead -width 60 -relief flat
2284 $top.fromhead insert 0 $oldhead
2285 $top.fromhead conf -state readonly
2286 grid x $top.fromhead -sticky w
2287 label $top.to -text "To:"
2288 entry $top.tosha1 -width 40 -relief flat
2289 $top.tosha1 insert 0 $newid
2290 $top.tosha1 conf -state readonly
2291 grid $top.to $top.tosha1 -sticky w
2292 entry $top.tohead -width 60 -relief flat
2293 $top.tohead insert 0 $newhead
2294 $top.tohead conf -state readonly
2295 grid x $top.tohead -sticky w
2296 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2297 grid $top.rev x -pady 10
2298 label $top.flab -text "Output file:"
2299 entry $top.fname -width 60
2300 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2302 grid $top.flab $top.fname -sticky w
2304 button $top.buts.gen -text "Generate" -command mkpatchgo
2305 button $top.buts.can -text "Cancel" -command mkpatchcan
2306 grid $top.buts.gen $top.buts.can
2307 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2308 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2309 grid $top.buts - -pady 10 -sticky ew
2313 proc mkpatchrev {} {
2316 set oldid [$patchtop.fromsha1 get]
2317 set oldhead [$patchtop.fromhead get]
2318 set newid [$patchtop.tosha1 get]
2319 set newhead [$patchtop.tohead get]
2320 foreach e [list fromsha1 fromhead tosha1 tohead] \
2321 v [list $newid $newhead $oldid $oldhead] {
2322 $patchtop.$e conf -state normal
2323 $patchtop.$e delete 0 end
2324 $patchtop.$e insert 0 $v
2325 $patchtop.$e conf -state readonly
2332 set oldid [$patchtop.fromsha1 get]
2333 set newid [$patchtop.tosha1 get]
2334 set fname [$patchtop.fname get]
2335 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2336 error_popup "Error creating patch: $err"
2338 catch {destroy $patchtop}
2342 proc mkpatchcan {} {
2345 catch {destroy $patchtop}
2350 global rowmenuid mktagtop commitinfo
2354 catch {destroy $top}
2356 label $top.title -text "Create tag"
2357 grid $top.title - -pady 10
2358 label $top.id -text "ID:"
2359 entry $top.sha1 -width 40 -relief flat
2360 $top.sha1 insert 0 $rowmenuid
2361 $top.sha1 conf -state readonly
2362 grid $top.id $top.sha1 -sticky w
2363 entry $top.head -width 60 -relief flat
2364 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2365 $top.head conf -state readonly
2366 grid x $top.head -sticky w
2367 label $top.tlab -text "Tag name:"
2368 entry $top.tag -width 60
2369 grid $top.tlab $top.tag -sticky w
2371 button $top.buts.gen -text "Create" -command mktaggo
2372 button $top.buts.can -text "Cancel" -command mktagcan
2373 grid $top.buts.gen $top.buts.can
2374 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2375 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2376 grid $top.buts - -pady 10 -sticky ew
2381 global mktagtop env tagids idtags
2382 global idpos idline linehtag canv selectedline
2384 set id [$mktagtop.sha1 get]
2385 set tag [$mktagtop.tag get]
2387 error_popup "No tag name specified"
2390 if {[info exists tagids($tag)]} {
2391 error_popup "Tag \"$tag\" already exists"
2396 if {[info exists env(GIT_DIR)]} {
2397 set dir $env(GIT_DIR)
2399 set fname [file join $dir "refs/tags" $tag]
2400 set f [open $fname w]
2404 error_popup "Error creating tag: $err"
2408 set tagids($tag) $id
2409 lappend idtags($id) $tag
2410 $canv delete tag.$id
2411 set xt [eval drawtags $id $idpos($id)]
2412 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2413 if {[info exists selectedline] && $selectedline == $idline($id)} {
2414 selectline $selectedline
2421 catch {destroy $mktagtop}
2430 proc writecommit {} {
2431 global rowmenuid wrcomtop commitinfo wrcomcmd
2433 set top .writecommit
2435 catch {destroy $top}
2437 label $top.title -text "Write commit to file"
2438 grid $top.title - -pady 10
2439 label $top.id -text "ID:"
2440 entry $top.sha1 -width 40 -relief flat
2441 $top.sha1 insert 0 $rowmenuid
2442 $top.sha1 conf -state readonly
2443 grid $top.id $top.sha1 -sticky w
2444 entry $top.head -width 60 -relief flat
2445 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2446 $top.head conf -state readonly
2447 grid x $top.head -sticky w
2448 label $top.clab -text "Command:"
2449 entry $top.cmd -width 60 -textvariable wrcomcmd
2450 grid $top.clab $top.cmd -sticky w -pady 10
2451 label $top.flab -text "Output file:"
2452 entry $top.fname -width 60
2453 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2454 grid $top.flab $top.fname -sticky w
2456 button $top.buts.gen -text "Write" -command wrcomgo
2457 button $top.buts.can -text "Cancel" -command wrcomcan
2458 grid $top.buts.gen $top.buts.can
2459 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2460 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2461 grid $top.buts - -pady 10 -sticky ew
2468 set id [$wrcomtop.sha1 get]
2469 set cmd "echo $id | [$wrcomtop.cmd get]"
2470 set fname [$wrcomtop.fname get]
2471 if {[catch {exec sh -c $cmd >$fname &} err]} {
2472 error_popup "Error writing commit: $err"
2474 catch {destroy $wrcomtop}
2481 catch {destroy $wrcomtop}
2494 set diffopts "-U 5 -p"
2495 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2497 set mainfont {Helvetica 9}
2498 set textfont {Courier 9}
2499 set findmergefiles 0
2502 set colors {green red blue magenta darkgrey brown orange}
2504 catch {source ~/.gitk}
2506 set namefont $mainfont
2508 lappend namefont bold
2513 switch -regexp -- $arg {
2515 "^-b" { set boldnames 1 }
2516 "^-d" { set datemode 1 }
2518 lappend revtreeargs $arg
2530 getcommits $revtreeargs