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.
12 if {[info exists env(GIT_DIR)]} {
19 proc getcommits {rargs} {
20 global commits commfd phase canv mainfont env
21 global startmsecs nextupdate
22 global ctext maincursor textcursor leftover
24 # check that we can find a .git directory somewhere...
26 if {![file isdirectory $gitdir]} {
27 error_popup "Cannot find the git directory \"$gitdir\"."
32 set startmsecs [clock clicks -milliseconds]
33 set nextupdate [expr $startmsecs + 100]
35 set parse_args [concat --default HEAD $rargs]
36 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
38 # if git-rev-parse failed for some reason...
42 set parsed_args $rargs
45 set commfd [open "|git-rev-list --header --topo-order $parsed_args" r]
47 puts stderr "Error executing git-rev-list: $err"
51 fconfigure $commfd -blocking 0 -translation lf
52 fileevent $commfd readable "getcommitlines $commfd"
54 $canv create text 3 3 -anchor nw -text "Reading commits..." \
55 -font $mainfont -tags textitems
56 . config -cursor watch
60 proc getcommitlines {commfd} {
61 global commits parents cdate children nchildren
62 global commitlisted phase commitinfo nextupdate
63 global stopped redisplaying leftover
65 set stuff [read $commfd]
67 if {![eof $commfd]} return
68 # set it blocking so we wait for the process to terminate
69 fconfigure $commfd -blocking 1
70 if {![catch {close $commfd} err]} {
71 after idle finishcommits
74 if {[string range $err 0 4] == "usage"} {
76 {Gitk: error reading commits: bad arguments to git-rev-list.
77 (Note: arguments to gitk are passed to git-rev-list
78 to allow selection of commits to be displayed.)}
80 set err "Error reading commits: $err"
87 set i [string first "\0" $stuff $start]
89 append leftover [string range $stuff $start end]
92 set cmit [string range $stuff $start [expr {$i - 1}]]
94 set cmit "$leftover$cmit"
97 set start [expr {$i + 1}]
98 if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
100 if {[string length $shortcmit] > 80} {
101 set shortcmit "[string range $shortcmit 0 80]..."
103 error_popup "Can't parse git-rev-list output: {$shortcmit}"
106 set cmit [string range $cmit 41 end]
108 set commitlisted($id) 1
109 parsecommit $id $cmit 1
111 if {[clock clicks -milliseconds] >= $nextupdate} {
114 while {$redisplaying} {
118 set phase "getcommits"
119 foreach id $commits {
122 if {[clock clicks -milliseconds] >= $nextupdate} {
132 global commfd nextupdate
135 fileevent $commfd readable {}
137 fileevent $commfd readable "getcommitlines $commfd"
140 proc readcommit {id} {
141 if [catch {set contents [exec git-cat-file commit $id]}] return
142 parsecommit $id $contents 0
145 proc parsecommit {id contents listed} {
146 global commitinfo children nchildren parents nparents cdate ncleft
155 if {![info exists nchildren($id)]} {
162 foreach line [split $contents "\n"] {
167 set tag [lindex $line 0]
168 if {$tag == "parent"} {
169 set p [lindex $line 1]
170 if {![info exists nchildren($p)]} {
175 lappend parents($id) $p
177 # sometimes we get a commit that lists a parent twice...
178 if {$listed && [lsearch -exact $children($p) $id] < 0} {
179 lappend children($p) $id
183 } elseif {$tag == "author"} {
184 set x [expr {[llength $line] - 2}]
185 set audate [lindex $line $x]
186 set auname [lrange $line 1 [expr {$x - 1}]]
187 } elseif {$tag == "committer"} {
188 set x [expr {[llength $line] - 2}]
189 set comdate [lindex $line $x]
190 set comname [lrange $line 1 [expr {$x - 1}]]
194 if {$comment == {}} {
195 set headline [string trim $line]
200 # git-rev-list indents the comment by 4 spaces;
201 # if we got this via git-cat-file, add the indentation
208 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
210 if {$comdate != {}} {
211 set cdate($id) $comdate
212 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
214 set commitinfo($id) [list $headline $auname $audate \
215 $comname $comdate $comment]
219 global tagids idtags headids idheads
220 set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
225 if {[regexp {^[0-9a-f]{40}} $line id]} {
226 set direct [file tail $f]
227 set tagids($direct) $id
228 lappend idtags($id) $direct
229 set contents [split [exec git-cat-file tag $id] "\n"]
233 foreach l $contents {
235 switch -- [lindex $l 0] {
236 "object" {set obj [lindex $l 1]}
237 "type" {set type [lindex $l 1]}
238 "tag" {set tag [string range $l 4 end]}
241 if {$obj != {} && $type == "commit" && $tag != {}} {
242 set tagids($tag) $obj
243 lappend idtags($obj) $tag
249 set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
253 set line [read $fd 40]
254 if {[regexp {^[0-9a-f]{40}} $line id]} {
255 set head [file tail $f]
256 set headids($head) $line
257 lappend idheads($line) $head
264 proc error_popup msg {
268 message $w.m -text $msg -justify center -aspect 400
269 pack $w.m -side top -fill x -padx 20 -pady 20
270 button $w.ok -text OK -command "destroy $w"
271 pack $w.ok -side bottom -fill x
272 bind $w <Visibility> "grab $w; focus $w"
277 global canv canv2 canv3 linespc charspc ctext cflist textfont
278 global findtype findtypemenu findloc findstring fstring geometry
279 global entries sha1entry sha1string sha1but
280 global maincursor textcursor curtextcursor
281 global rowctxmenu gaudydiff mergemax
284 .bar add cascade -label "File" -menu .bar.file
286 .bar.file add command -label "Quit" -command doquit
288 .bar add cascade -label "Help" -menu .bar.help
289 .bar.help add command -label "About gitk" -command about
290 . configure -menu .bar
292 if {![info exists geometry(canv1)]} {
293 set geometry(canv1) [expr 45 * $charspc]
294 set geometry(canv2) [expr 30 * $charspc]
295 set geometry(canv3) [expr 15 * $charspc]
296 set geometry(canvh) [expr 25 * $linespc + 4]
297 set geometry(ctextw) 80
298 set geometry(ctexth) 30
299 set geometry(cflistw) 30
301 panedwindow .ctop -orient vertical
302 if {[info exists geometry(width)]} {
303 .ctop conf -width $geometry(width) -height $geometry(height)
304 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
305 set geometry(ctexth) [expr {($texth - 8) /
306 [font metrics $textfont -linespace]}]
310 pack .ctop.top.bar -side bottom -fill x
311 set cscroll .ctop.top.csb
312 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
313 pack $cscroll -side right -fill y
314 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
315 pack .ctop.top.clist -side top -fill both -expand 1
317 set canv .ctop.top.clist.canv
318 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
320 -yscrollincr $linespc -yscrollcommand "$cscroll set"
321 .ctop.top.clist add $canv
322 set canv2 .ctop.top.clist.canv2
323 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
324 -bg white -bd 0 -yscrollincr $linespc
325 .ctop.top.clist add $canv2
326 set canv3 .ctop.top.clist.canv3
327 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
328 -bg white -bd 0 -yscrollincr $linespc
329 .ctop.top.clist add $canv3
330 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
332 set sha1entry .ctop.top.bar.sha1
333 set entries $sha1entry
334 set sha1but .ctop.top.bar.sha1label
335 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
336 -command gotocommit -width 8
337 $sha1but conf -disabledforeground [$sha1but cget -foreground]
338 pack .ctop.top.bar.sha1label -side left
339 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
340 trace add variable sha1string write sha1change
341 pack $sha1entry -side left -pady 2
343 image create bitmap bm-left -data {
344 #define left_width 16
345 #define left_height 16
346 static unsigned char left_bits[] = {
347 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
348 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
349 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
351 image create bitmap bm-right -data {
352 #define right_width 16
353 #define right_height 16
354 static unsigned char right_bits[] = {
355 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
356 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
357 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
359 button .ctop.top.bar.leftbut -image bm-left -command goback \
360 -state disabled -width 26
361 pack .ctop.top.bar.leftbut -side left -fill y
362 button .ctop.top.bar.rightbut -image bm-right -command goforw \
363 -state disabled -width 26
364 pack .ctop.top.bar.rightbut -side left -fill y
366 button .ctop.top.bar.findbut -text "Find" -command dofind
367 pack .ctop.top.bar.findbut -side left
369 set fstring .ctop.top.bar.findstring
370 lappend entries $fstring
371 entry $fstring -width 30 -font $textfont -textvariable findstring
372 pack $fstring -side left -expand 1 -fill x
374 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
375 findtype Exact IgnCase Regexp]
376 set findloc "All fields"
377 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
378 Comments Author Committer Files Pickaxe
379 pack .ctop.top.bar.findloc -side right
380 pack .ctop.top.bar.findtype -side right
381 # for making sure type==Exact whenever loc==Pickaxe
382 trace add variable findloc write findlocchange
384 panedwindow .ctop.cdet -orient horizontal
386 frame .ctop.cdet.left
387 set ctext .ctop.cdet.left.ctext
388 text $ctext -bg white -state disabled -font $textfont \
389 -width $geometry(ctextw) -height $geometry(ctexth) \
390 -yscrollcommand ".ctop.cdet.left.sb set"
391 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
392 pack .ctop.cdet.left.sb -side right -fill y
393 pack $ctext -side left -fill both -expand 1
394 .ctop.cdet add .ctop.cdet.left
396 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
398 $ctext tag conf hunksep -back blue -fore white
399 $ctext tag conf d0 -back "#ff8080"
400 $ctext tag conf d1 -back green
402 $ctext tag conf hunksep -fore blue
403 $ctext tag conf d0 -fore red
404 $ctext tag conf d1 -fore "#00a000"
405 $ctext tag conf m0 -fore red
406 $ctext tag conf m1 -fore blue
407 $ctext tag conf m2 -fore green
408 $ctext tag conf m3 -fore purple
409 $ctext tag conf m4 -fore brown
410 $ctext tag conf mmax -fore darkgrey
412 $ctext tag conf mresult -font [concat $textfont bold]
413 $ctext tag conf msep -font [concat $textfont bold]
414 $ctext tag conf found -back yellow
417 frame .ctop.cdet.right
418 set cflist .ctop.cdet.right.cfiles
419 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
420 -yscrollcommand ".ctop.cdet.right.sb set"
421 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
422 pack .ctop.cdet.right.sb -side right -fill y
423 pack $cflist -side left -fill both -expand 1
424 .ctop.cdet add .ctop.cdet.right
425 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
427 pack .ctop -side top -fill both -expand 1
429 bindall <1> {selcanvline %W %x %y}
430 #bindall <B1-Motion> {selcanvline %W %x %y}
431 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
432 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
433 bindall <2> "allcanvs scan mark 0 %y"
434 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
435 bind . <Key-Up> "selnextline -1"
436 bind . <Key-Down> "selnextline 1"
437 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
438 bind . <Key-Next> "allcanvs yview scroll 1 pages"
439 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
440 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
441 bindkey <Key-space> "$ctext yview scroll 1 pages"
442 bindkey p "selnextline -1"
443 bindkey n "selnextline 1"
444 bindkey b "$ctext yview scroll -1 pages"
445 bindkey d "$ctext yview scroll 18 units"
446 bindkey u "$ctext yview scroll -18 units"
447 bindkey / {findnext 1}
448 bindkey <Key-Return> {findnext 0}
451 bind . <Control-q> doquit
452 bind . <Control-f> dofind
453 bind . <Control-g> {findnext 0}
454 bind . <Control-r> findprev
455 bind . <Control-equal> {incrfont 1}
456 bind . <Control-KP_Add> {incrfont 1}
457 bind . <Control-minus> {incrfont -1}
458 bind . <Control-KP_Subtract> {incrfont -1}
459 bind $cflist <<ListboxSelect>> listboxsel
460 bind . <Destroy> {savestuff %W}
461 bind . <Button-1> "click %W"
462 bind $fstring <Key-Return> dofind
463 bind $sha1entry <Key-Return> gotocommit
464 bind $sha1entry <<PasteSelection>> clearsha1
466 set maincursor [. cget -cursor]
467 set textcursor [$ctext cget -cursor]
468 set curtextcursor $textcursor
470 set rowctxmenu .rowctxmenu
471 menu $rowctxmenu -tearoff 0
472 $rowctxmenu add command -label "Diff this -> selected" \
473 -command {diffvssel 0}
474 $rowctxmenu add command -label "Diff selected -> this" \
475 -command {diffvssel 1}
476 $rowctxmenu add command -label "Make patch" -command mkpatch
477 $rowctxmenu add command -label "Create tag" -command mktag
478 $rowctxmenu add command -label "Write commit to file" -command writecommit
481 # when we make a key binding for the toplevel, make sure
482 # it doesn't get triggered when that key is pressed in the
483 # find string entry widget.
484 proc bindkey {ev script} {
487 set escript [bind Entry $ev]
488 if {$escript == {}} {
489 set escript [bind Entry <Key>]
492 bind $e $ev "$escript; break"
496 # set the focus back to the toplevel for any click outside
507 global canv canv2 canv3 ctext cflist mainfont textfont
508 global stuffsaved findmergefiles gaudydiff maxgraphpct
510 if {$stuffsaved} return
511 if {![winfo viewable .]} return
513 set f [open "~/.gitk-new" w]
514 puts $f [list set mainfont $mainfont]
515 puts $f [list set textfont $textfont]
516 puts $f [list set findmergefiles $findmergefiles]
517 puts $f [list set gaudydiff $gaudydiff]
518 puts $f [list set maxgraphpct $maxgraphpct]
519 puts $f "set geometry(width) [winfo width .ctop]"
520 puts $f "set geometry(height) [winfo height .ctop]"
521 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
522 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
523 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
524 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
525 set wid [expr {([winfo width $ctext] - 8) \
526 / [font measure $textfont "0"]}]
527 puts $f "set geometry(ctextw) $wid"
528 set wid [expr {([winfo width $cflist] - 11) \
529 / [font measure [$cflist cget -font] "0"]}]
530 puts $f "set geometry(cflistw) $wid"
532 file rename -force "~/.gitk-new" "~/.gitk"
537 proc resizeclistpanes {win w} {
539 if [info exists oldwidth($win)] {
540 set s0 [$win sash coord 0]
541 set s1 [$win sash coord 1]
543 set sash0 [expr {int($w/2 - 2)}]
544 set sash1 [expr {int($w*5/6 - 2)}]
546 set factor [expr {1.0 * $w / $oldwidth($win)}]
547 set sash0 [expr {int($factor * [lindex $s0 0])}]
548 set sash1 [expr {int($factor * [lindex $s1 0])}]
552 if {$sash1 < $sash0 + 20} {
553 set sash1 [expr $sash0 + 20]
555 if {$sash1 > $w - 10} {
556 set sash1 [expr $w - 10]
557 if {$sash0 > $sash1 - 20} {
558 set sash0 [expr $sash1 - 20]
562 $win sash place 0 $sash0 [lindex $s0 1]
563 $win sash place 1 $sash1 [lindex $s1 1]
565 set oldwidth($win) $w
568 proc resizecdetpanes {win w} {
570 if [info exists oldwidth($win)] {
571 set s0 [$win sash coord 0]
573 set sash0 [expr {int($w*3/4 - 2)}]
575 set factor [expr {1.0 * $w / $oldwidth($win)}]
576 set sash0 [expr {int($factor * [lindex $s0 0])}]
580 if {$sash0 > $w - 15} {
581 set sash0 [expr $w - 15]
584 $win sash place 0 $sash0 [lindex $s0 1]
586 set oldwidth($win) $w
590 global canv canv2 canv3
596 proc bindall {event action} {
597 global canv canv2 canv3
598 bind $canv $event $action
599 bind $canv2 $event $action
600 bind $canv3 $event $action
605 if {[winfo exists $w]} {
610 wm title $w "About gitk"
614 Copyright © 2005 Paul Mackerras
616 Use and redistribute under the terms of the GNU General Public License} \
617 -justify center -aspect 400
618 pack $w.m -side top -fill x -padx 20 -pady 20
619 button $w.ok -text Close -command "destroy $w"
620 pack $w.ok -side bottom
623 proc assigncolor {id} {
624 global commitinfo colormap commcolors colors nextcolor
625 global parents nparents children nchildren
626 global cornercrossings crossings
628 if [info exists colormap($id)] return
629 set ncolors [llength $colors]
630 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
631 set child [lindex $children($id) 0]
632 if {[info exists colormap($child)]
633 && $nparents($child) == 1} {
634 set colormap($id) $colormap($child)
639 if {[info exists cornercrossings($id)]} {
640 foreach x $cornercrossings($id) {
641 if {[info exists colormap($x)]
642 && [lsearch -exact $badcolors $colormap($x)] < 0} {
643 lappend badcolors $colormap($x)
646 if {[llength $badcolors] >= $ncolors} {
650 set origbad $badcolors
651 if {[llength $badcolors] < $ncolors - 1} {
652 if {[info exists crossings($id)]} {
653 foreach x $crossings($id) {
654 if {[info exists colormap($x)]
655 && [lsearch -exact $badcolors $colormap($x)] < 0} {
656 lappend badcolors $colormap($x)
659 if {[llength $badcolors] >= $ncolors} {
660 set badcolors $origbad
663 set origbad $badcolors
665 if {[llength $badcolors] < $ncolors - 1} {
666 foreach child $children($id) {
667 if {[info exists colormap($child)]
668 && [lsearch -exact $badcolors $colormap($child)] < 0} {
669 lappend badcolors $colormap($child)
671 if {[info exists parents($child)]} {
672 foreach p $parents($child) {
673 if {[info exists colormap($p)]
674 && [lsearch -exact $badcolors $colormap($p)] < 0} {
675 lappend badcolors $colormap($p)
680 if {[llength $badcolors] >= $ncolors} {
681 set badcolors $origbad
684 for {set i 0} {$i <= $ncolors} {incr i} {
685 set c [lindex $colors $nextcolor]
686 if {[incr nextcolor] >= $ncolors} {
689 if {[lsearch -exact $badcolors $c]} break
695 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
696 global mainline sidelines
697 global nchildren ncleft
704 set lthickness [expr {int($linespc / 9) + 1}]
705 catch {unset mainline}
706 catch {unset sidelines}
707 foreach id [array names nchildren] {
708 set ncleft($id) $nchildren($id)
712 proc bindline {t id} {
715 $canv bind $t <Enter> "lineenter %x %y $id"
716 $canv bind $t <Motion> "linemotion %x %y $id"
717 $canv bind $t <Leave> "lineleave $id"
718 $canv bind $t <Button-1> "lineclick %x %y $id 1"
721 proc drawcommitline {level} {
722 global parents children nparents nchildren todo
723 global canv canv2 canv3 mainfont namefont canvy linespc
724 global lineid linehtag linentag linedtag commitinfo
725 global colormap numcommits currentparents dupparents
726 global oldlevel oldnlines oldtodo
727 global idtags idline idheads
728 global lineno lthickness mainline sidelines
729 global commitlisted rowtextx idpos
733 set id [lindex $todo $level]
734 set lineid($lineno) $id
735 set idline($id) $lineno
736 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
737 if {![info exists commitinfo($id)]} {
739 if {![info exists commitinfo($id)]} {
740 set commitinfo($id) {"No commit information available"}
745 set currentparents {}
747 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
748 foreach p $parents($id) {
749 if {[lsearch -exact $currentparents $p] < 0} {
750 lappend currentparents $p
752 # remember that this parent was listed twice
753 lappend dupparents $p
757 set x [xcoord $level $level $lineno]
759 set canvy [expr $canvy + $linespc]
760 allcanvs conf -scrollregion \
761 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
762 if {[info exists mainline($id)]} {
763 lappend mainline($id) $x $y1
764 set t [$canv create line $mainline($id) \
765 -width $lthickness -fill $colormap($id)]
769 if {[info exists sidelines($id)]} {
770 foreach ls $sidelines($id) {
771 set coords [lindex $ls 0]
772 set thick [lindex $ls 1]
773 set t [$canv create line $coords -fill $colormap($id) \
774 -width [expr {$thick * $lthickness}]]
779 set orad [expr {$linespc / 3}]
780 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
781 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
782 -fill $ofill -outline black -width 1]
784 $canv bind $t <1> {selcanvline {} %x %y}
785 set xt [xcoord [llength $todo] $level $lineno]
786 if {[llength $currentparents] > 2} {
787 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
789 set rowtextx($lineno) $xt
790 set idpos($id) [list $x $xt $y1]
791 if {[info exists idtags($id)] || [info exists idheads($id)]} {
792 set xt [drawtags $id $x $xt $y1]
794 set headline [lindex $commitinfo($id) 0]
795 set name [lindex $commitinfo($id) 1]
796 set date [lindex $commitinfo($id) 2]
797 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
798 -text $headline -font $mainfont ]
799 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
800 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
801 -text $name -font $namefont]
802 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
803 -text $date -font $mainfont]
806 proc drawtags {id x xt y1} {
807 global idtags idheads
808 global linespc lthickness
813 if {[info exists idtags($id)]} {
814 set marks $idtags($id)
815 set ntags [llength $marks]
817 if {[info exists idheads($id)]} {
818 set marks [concat $marks $idheads($id)]
824 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
825 set yt [expr $y1 - 0.5 * $linespc]
826 set yb [expr $yt + $linespc - 1]
830 set wid [font measure $mainfont $tag]
833 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
835 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
836 -width $lthickness -fill black -tags tag.$id]
838 foreach tag $marks x $xvals wid $wvals {
839 set xl [expr $x + $delta]
840 set xr [expr $x + $delta + $wid + $lthickness]
841 if {[incr ntags -1] >= 0} {
843 $canv create polygon $x [expr $yt + $delta] $xl $yt\
844 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
845 -width 1 -outline black -fill yellow -tags tag.$id
848 set xl [expr $xl - $delta/2]
849 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
850 -width 1 -outline black -fill green -tags tag.$id
852 $canv create text $xl $y1 -anchor w -text $tag \
853 -font $mainfont -tags tag.$id
858 proc updatetodo {level noshortcut} {
859 global currentparents ncleft todo
860 global mainline oldlevel oldtodo oldnlines
861 global canvy linespc mainline
862 global commitinfo lineno xspc1
866 set oldnlines [llength $todo]
867 if {!$noshortcut && [llength $currentparents] == 1} {
868 set p [lindex $currentparents 0]
869 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
871 set x [xcoord $level $level $lineno]
872 set y [expr $canvy - $linespc]
873 set mainline($p) [list $x $y]
874 set todo [lreplace $todo $level $level $p]
875 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
880 set todo [lreplace $todo $level $level]
882 foreach p $currentparents {
884 set k [lsearch -exact $todo $p]
886 set todo [linsert $todo $i $p]
893 proc notecrossings {id lo hi corner} {
894 global oldtodo crossings cornercrossings
896 for {set i $lo} {[incr i] < $hi} {} {
897 set p [lindex $oldtodo $i]
898 if {$p == {}} continue
900 if {![info exists cornercrossings($id)]
901 || [lsearch -exact $cornercrossings($id) $p] < 0} {
902 lappend cornercrossings($id) $p
904 if {![info exists cornercrossings($p)]
905 || [lsearch -exact $cornercrossings($p) $id] < 0} {
906 lappend cornercrossings($p) $id
909 if {![info exists crossings($id)]
910 || [lsearch -exact $crossings($id) $p] < 0} {
911 lappend crossings($id) $p
913 if {![info exists crossings($p)]
914 || [lsearch -exact $crossings($p) $id] < 0} {
915 lappend crossings($p) $id
921 proc xcoord {i level ln} {
922 global canvx0 xspc1 xspc2
924 set x [expr {$canvx0 + $i * $xspc1($ln)}]
925 if {$i > 0 && $i == $level} {
926 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
927 } elseif {$i > $level} {
928 set x [expr {$x + $xspc2 - $xspc1($ln)}]
933 proc drawslants {level} {
934 global canv mainline sidelines canvx0 canvy xspc1 xspc2 lthickness
935 global oldlevel oldtodo todo currentparents dupparents
936 global lthickness linespc canvy colormap lineno geometry
939 # decide on the line spacing for the next line
940 set lj [expr {$lineno + 1}]
941 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
942 set n [llength $todo]
943 if {$n <= 1 || $canvx0 + $n * $xspc2 <= $maxw} {
944 set xspc1($lj) $xspc2
946 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($n - 1)}]
947 if {$xspc1($lj) < $lthickness} {
948 set xspc1($lj) $lthickness
952 set y1 [expr $canvy - $linespc]
955 foreach id $oldtodo {
957 if {$id == {}} continue
958 set xi [xcoord $i $oldlevel $lineno]
959 if {$i == $oldlevel} {
960 foreach p $currentparents {
961 set j [lsearch -exact $todo $p]
962 set coords [list $xi $y1]
963 set xj [xcoord $j $level $lj]
964 if {$xj < $xi - $linespc} {
965 lappend coords [expr {$xj + $linespc}] $y1
966 notecrossings $p $j $i [expr {$j + 1}]
967 } elseif {$xj > $xi + $linespc} {
968 lappend coords [expr {$xj - $linespc}] $y1
969 notecrossings $p $i $j [expr {$j - 1}]
971 if {[lsearch -exact $dupparents $p] >= 0} {
972 # draw a double-width line to indicate the doubled parent
973 lappend coords $xj $y2
974 lappend sidelines($p) [list $coords 2]
975 if {![info exists mainline($p)]} {
976 set mainline($p) [list $xj $y2]
979 # normal case, no parent duplicated
981 set dx [expr {abs($xi - $xj)}]
982 if {0 && $dx < $linespc} {
983 set yb [expr {$y1 + $dx}]
985 if {![info exists mainline($p)]} {
987 lappend coords $xj $yb
989 set mainline($p) $coords
991 lappend coords $xj $yb
993 lappend coords $xj $y2
995 lappend sidelines($p) [list $coords 1]
1001 if {[lindex $todo $i] != $id} {
1002 set j [lsearch -exact $todo $id]
1004 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1005 || ($oldlevel <= $i && $i <= $level)
1006 || ($level <= $i && $i <= $oldlevel)} {
1007 set xj [xcoord $j $level $lj]
1008 set dx [expr {abs($xi - $xj)}]
1010 if {0 && $dx < $linespc} {
1011 set yb [expr {$y1 + $dx}]
1013 lappend mainline($id) $xi $y1 $xj $yb
1019 proc decidenext {{noread 0}} {
1020 global parents children nchildren ncleft todo
1021 global canv canv2 canv3 mainfont namefont canvy linespc
1022 global datemode cdate
1024 global currentparents oldlevel oldnlines oldtodo
1025 global lineno lthickness
1027 # remove the null entry if present
1028 set nullentry [lsearch -exact $todo {}]
1029 if {$nullentry >= 0} {
1030 set todo [lreplace $todo $nullentry $nullentry]
1033 # choose which one to do next time around
1034 set todol [llength $todo]
1037 for {set k $todol} {[incr k -1] >= 0} {} {
1038 set p [lindex $todo $k]
1039 if {$ncleft($p) == 0} {
1041 if {![info exists commitinfo($p)]} {
1047 if {$latest == {} || $cdate($p) > $latest} {
1049 set latest $cdate($p)
1059 puts "ERROR: none of the pending commits can be done yet:"
1061 puts " $p ($ncleft($p))"
1067 # If we are reducing, put in a null entry
1068 if {$todol < $oldnlines} {
1069 if {$nullentry >= 0} {
1072 && [lindex $oldtodo $i] == [lindex $todo $i]} {
1082 set todo [linsert $todo $i {}]
1091 proc drawcommit {id} {
1092 global phase todo nchildren datemode nextupdate
1095 if {$phase != "incrdraw"} {
1098 set startcommits $id
1101 updatetodo 0 $datemode
1103 if {$nchildren($id) == 0} {
1105 lappend startcommits $id
1107 set level [decidenext 1]
1108 if {$level == {} || $id != [lindex $todo $level]} {
1113 drawcommitline $level
1114 if {[updatetodo $level $datemode]} {
1115 set level [decidenext 1]
1116 if {$level == {}} break
1118 set id [lindex $todo $level]
1119 if {![info exists commitlisted($id)]} {
1122 if {[clock clicks -milliseconds] >= $nextupdate} {
1130 proc finishcommits {} {
1133 global canv mainfont ctext maincursor textcursor
1135 if {$phase != "incrdraw"} {
1137 $canv create text 3 3 -anchor nw -text "No commits selected" \
1138 -font $mainfont -tags textitems
1141 set level [decidenext]
1143 drawrest $level [llength $startcommits]
1145 . config -cursor $maincursor
1146 settextcursor $textcursor
1149 # Don't change the text pane cursor if it is currently the hand cursor,
1150 # showing that we are over a sha1 ID link.
1151 proc settextcursor {c} {
1152 global ctext curtextcursor
1154 if {[$ctext cget -cursor] == $curtextcursor} {
1155 $ctext config -cursor $c
1157 set curtextcursor $c
1161 global nextupdate startmsecs startcommits todo
1163 if {$startcommits == {}} return
1164 set startmsecs [clock clicks -milliseconds]
1165 set nextupdate [expr $startmsecs + 100]
1167 set todo [lindex $startcommits 0]
1171 proc drawrest {level startix} {
1172 global phase stopped redisplaying selectedline
1173 global datemode currentparents todo
1175 global nextupdate startmsecs startcommits idline
1179 set startid [lindex $startcommits $startix]
1181 if {$startid != {}} {
1182 set startline $idline($startid)
1186 drawcommitline $level
1187 set hard [updatetodo $level $datemode]
1188 if {$numcommits == $startline} {
1189 lappend todo $startid
1192 set startid [lindex $startcommits $startix]
1194 if {$startid != {}} {
1195 set startline $idline($startid)
1199 set level [decidenext]
1200 if {$level < 0} break
1203 if {[clock clicks -milliseconds] >= $nextupdate} {
1210 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1211 #puts "overall $drawmsecs ms for $numcommits commits"
1212 if {$redisplaying} {
1213 if {$stopped == 0 && [info exists selectedline]} {
1214 selectline $selectedline 0
1216 if {$stopped == 1} {
1218 after idle drawgraph
1225 proc findmatches {f} {
1226 global findtype foundstring foundstrlen
1227 if {$findtype == "Regexp"} {
1228 set matches [regexp -indices -all -inline $foundstring $f]
1230 if {$findtype == "IgnCase"} {
1231 set str [string tolower $f]
1237 while {[set j [string first $foundstring $str $i]] >= 0} {
1238 lappend matches [list $j [expr $j+$foundstrlen-1]]
1239 set i [expr $j + $foundstrlen]
1246 global findtype findloc findstring markedmatches commitinfo
1247 global numcommits lineid linehtag linentag linedtag
1248 global mainfont namefont canv canv2 canv3 selectedline
1249 global matchinglines foundstring foundstrlen
1254 set matchinglines {}
1255 if {$findloc == "Pickaxe"} {
1259 if {$findtype == "IgnCase"} {
1260 set foundstring [string tolower $findstring]
1262 set foundstring $findstring
1264 set foundstrlen [string length $findstring]
1265 if {$foundstrlen == 0} return
1266 if {$findloc == "Files"} {
1270 if {![info exists selectedline]} {
1273 set oldsel $selectedline
1276 set fldtypes {Headline Author Date Committer CDate Comment}
1277 for {set l 0} {$l < $numcommits} {incr l} {
1279 set info $commitinfo($id)
1281 foreach f $info ty $fldtypes {
1282 if {$findloc != "All fields" && $findloc != $ty} {
1285 set matches [findmatches $f]
1286 if {$matches == {}} continue
1288 if {$ty == "Headline"} {
1289 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1290 } elseif {$ty == "Author"} {
1291 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1292 } elseif {$ty == "Date"} {
1293 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1297 lappend matchinglines $l
1298 if {!$didsel && $l > $oldsel} {
1304 if {$matchinglines == {}} {
1306 } elseif {!$didsel} {
1307 findselectline [lindex $matchinglines 0]
1311 proc findselectline {l} {
1312 global findloc commentend ctext
1314 if {$findloc == "All fields" || $findloc == "Comments"} {
1315 # highlight the matches in the comments
1316 set f [$ctext get 1.0 $commentend]
1317 set matches [findmatches $f]
1318 foreach match $matches {
1319 set start [lindex $match 0]
1320 set end [expr [lindex $match 1] + 1]
1321 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1326 proc findnext {restart} {
1327 global matchinglines selectedline
1328 if {![info exists matchinglines]} {
1334 if {![info exists selectedline]} return
1335 foreach l $matchinglines {
1336 if {$l > $selectedline} {
1345 global matchinglines selectedline
1346 if {![info exists matchinglines]} {
1350 if {![info exists selectedline]} return
1352 foreach l $matchinglines {
1353 if {$l >= $selectedline} break
1357 findselectline $prev
1363 proc findlocchange {name ix op} {
1364 global findloc findtype findtypemenu
1365 if {$findloc == "Pickaxe"} {
1371 $findtypemenu entryconf 1 -state $state
1372 $findtypemenu entryconf 2 -state $state
1375 proc stopfindproc {{done 0}} {
1376 global findprocpid findprocfile findids
1377 global ctext findoldcursor phase maincursor textcursor
1378 global findinprogress
1380 catch {unset findids}
1381 if {[info exists findprocpid]} {
1383 catch {exec kill $findprocpid}
1385 catch {close $findprocfile}
1388 if {[info exists findinprogress]} {
1389 unset findinprogress
1390 if {$phase != "incrdraw"} {
1391 . config -cursor $maincursor
1392 settextcursor $textcursor
1397 proc findpatches {} {
1398 global findstring selectedline numcommits
1399 global findprocpid findprocfile
1400 global finddidsel ctext lineid findinprogress
1401 global findinsertpos
1403 if {$numcommits == 0} return
1405 # make a list of all the ids to search, starting at the one
1406 # after the selected line (if any)
1407 if {[info exists selectedline]} {
1413 for {set i 0} {$i < $numcommits} {incr i} {
1414 if {[incr l] >= $numcommits} {
1417 append inputids $lineid($l) "\n"
1421 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1424 error_popup "Error starting search process: $err"
1428 set findinsertpos end
1430 set findprocpid [pid $f]
1431 fconfigure $f -blocking 0
1432 fileevent $f readable readfindproc
1434 . config -cursor watch
1436 set findinprogress 1
1439 proc readfindproc {} {
1440 global findprocfile finddidsel
1441 global idline matchinglines findinsertpos
1443 set n [gets $findprocfile line]
1445 if {[eof $findprocfile]} {
1453 if {![regexp {^[0-9a-f]{40}} $line id]} {
1454 error_popup "Can't parse git-diff-tree output: $line"
1458 if {![info exists idline($id)]} {
1459 puts stderr "spurious id: $id"
1466 proc insertmatch {l id} {
1467 global matchinglines findinsertpos finddidsel
1469 if {$findinsertpos == "end"} {
1470 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1471 set matchinglines [linsert $matchinglines 0 $l]
1474 lappend matchinglines $l
1477 set matchinglines [linsert $matchinglines $findinsertpos $l]
1488 global selectedline numcommits lineid ctext
1489 global ffileline finddidsel parents nparents
1490 global findinprogress findstartline findinsertpos
1491 global treediffs fdiffids fdiffsneeded fdiffpos
1492 global findmergefiles
1494 if {$numcommits == 0} return
1496 if {[info exists selectedline]} {
1497 set l [expr {$selectedline + 1}]
1502 set findstartline $l
1507 if {$findmergefiles || $nparents($id) == 1} {
1508 foreach p $parents($id) {
1509 if {![info exists treediffs([list $id $p])]} {
1510 append diffsneeded "$id $p\n"
1511 lappend fdiffsneeded [list $id $p]
1515 if {[incr l] >= $numcommits} {
1518 if {$l == $findstartline} break
1521 # start off a git-diff-tree process if needed
1522 if {$diffsneeded ne {}} {
1524 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1526 error_popup "Error starting search process: $err"
1529 catch {unset fdiffids}
1531 fconfigure $df -blocking 0
1532 fileevent $df readable [list readfilediffs $df]
1536 set findinsertpos end
1538 set p [lindex $parents($id) 0]
1539 . config -cursor watch
1541 set findinprogress 1
1542 findcont [list $id $p]
1546 proc readfilediffs {df} {
1547 global findids fdiffids fdiffs
1549 set n [gets $df line]
1553 if {[catch {close $df} err]} {
1556 error_popup "Error in git-diff-tree: $err"
1557 } elseif {[info exists findids]} {
1561 error_popup "Couldn't find diffs for {$ids}"
1566 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1567 # start of a new string of diffs
1569 set fdiffids [list $id $p]
1571 } elseif {[string match ":*" $line]} {
1572 lappend fdiffs [lindex $line 5]
1576 proc donefilediff {} {
1577 global fdiffids fdiffs treediffs findids
1578 global fdiffsneeded fdiffpos
1580 if {[info exists fdiffids]} {
1581 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1582 && $fdiffpos < [llength $fdiffsneeded]} {
1583 # git-diff-tree doesn't output anything for a commit
1584 # which doesn't change anything
1585 set nullids [lindex $fdiffsneeded $fdiffpos]
1586 set treediffs($nullids) {}
1587 if {[info exists findids] && $nullids eq $findids} {
1595 if {![info exists treediffs($fdiffids)]} {
1596 set treediffs($fdiffids) $fdiffs
1598 if {[info exists findids] && $fdiffids eq $findids} {
1605 proc findcont {ids} {
1606 global findids treediffs parents nparents
1607 global ffileline findstartline finddidsel
1608 global lineid numcommits matchinglines findinprogress
1609 global findmergefiles
1611 set id [lindex $ids 0]
1612 set p [lindex $ids 1]
1613 set pi [lsearch -exact $parents($id) $p]
1616 if {$findmergefiles || $nparents($id) == 1} {
1617 if {![info exists treediffs($ids)]} {
1623 foreach f $treediffs($ids) {
1624 set x [findmatches $f]
1632 set pi $nparents($id)
1635 set pi $nparents($id)
1637 if {[incr pi] >= $nparents($id)} {
1639 if {[incr l] >= $numcommits} {
1642 if {$l == $findstartline} break
1645 set p [lindex $parents($id) $pi]
1646 set ids [list $id $p]
1654 # mark a commit as matching by putting a yellow background
1655 # behind the headline
1656 proc markheadline {l id} {
1657 global canv mainfont linehtag commitinfo
1659 set bbox [$canv bbox $linehtag($l)]
1660 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1664 # mark the bits of a headline, author or date that match a find string
1665 proc markmatches {canv l str tag matches font} {
1666 set bbox [$canv bbox $tag]
1667 set x0 [lindex $bbox 0]
1668 set y0 [lindex $bbox 1]
1669 set y1 [lindex $bbox 3]
1670 foreach match $matches {
1671 set start [lindex $match 0]
1672 set end [lindex $match 1]
1673 if {$start > $end} continue
1674 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1675 set xlen [font measure $font [string range $str 0 [expr $end]]]
1676 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1677 -outline {} -tags matches -fill yellow]
1682 proc unmarkmatches {} {
1683 global matchinglines findids
1684 allcanvs delete matches
1685 catch {unset matchinglines}
1686 catch {unset findids}
1689 proc selcanvline {w x y} {
1690 global canv canvy0 ctext linespc
1691 global lineid linehtag linentag linedtag rowtextx
1692 set ymax [lindex [$canv cget -scrollregion] 3]
1693 if {$ymax == {}} return
1694 set yfrac [lindex [$canv yview] 0]
1695 set y [expr {$y + $yfrac * $ymax}]
1696 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1701 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1707 proc selectline {l isnew} {
1708 global canv canv2 canv3 ctext commitinfo selectedline
1709 global lineid linehtag linentag linedtag
1710 global canvy0 linespc parents nparents
1711 global cflist currentid sha1entry
1712 global commentend idtags idline
1715 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1717 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1718 -tags secsel -fill [$canv cget -selectbackground]]
1720 $canv2 delete secsel
1721 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1722 -tags secsel -fill [$canv2 cget -selectbackground]]
1724 $canv3 delete secsel
1725 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1726 -tags secsel -fill [$canv3 cget -selectbackground]]
1728 set y [expr {$canvy0 + $l * $linespc}]
1729 set ymax [lindex [$canv cget -scrollregion] 3]
1730 set ytop [expr {$y - $linespc - 1}]
1731 set ybot [expr {$y + $linespc + 1}]
1732 set wnow [$canv yview]
1733 set wtop [expr [lindex $wnow 0] * $ymax]
1734 set wbot [expr [lindex $wnow 1] * $ymax]
1735 set wh [expr {$wbot - $wtop}]
1737 if {$ytop < $wtop} {
1738 if {$ybot < $wtop} {
1739 set newtop [expr {$y - $wh / 2.0}]
1742 if {$newtop > $wtop - $linespc} {
1743 set newtop [expr {$wtop - $linespc}]
1746 } elseif {$ybot > $wbot} {
1747 if {$ytop > $wbot} {
1748 set newtop [expr {$y - $wh / 2.0}]
1750 set newtop [expr {$ybot - $wh}]
1751 if {$newtop < $wtop + $linespc} {
1752 set newtop [expr {$wtop + $linespc}]
1756 if {$newtop != $wtop} {
1760 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1764 addtohistory [list selectline $l 0]
1771 $sha1entry delete 0 end
1772 $sha1entry insert 0 $id
1773 $sha1entry selection from 0
1774 $sha1entry selection to end
1776 $ctext conf -state normal
1777 $ctext delete 0.0 end
1778 $ctext mark set fmark.0 0.0
1779 $ctext mark gravity fmark.0 left
1780 set info $commitinfo($id)
1781 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1782 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1783 if {[info exists idtags($id)]} {
1784 $ctext insert end "Tags:"
1785 foreach tag $idtags($id) {
1786 $ctext insert end " $tag"
1788 $ctext insert end "\n"
1791 set commentstart [$ctext index "end - 1c"]
1793 foreach p $parents($id) {
1795 if {[info exists commitinfo($p)]} {
1796 set l [lindex $commitinfo($p) 0]
1797 if {[string length $l] > 32} {
1798 set l "[string range $l 0 28] ..."
1801 append comment "Parent: $p ($l)\n"
1804 append comment [lindex $info 5]
1805 $ctext insert end $comment
1806 $ctext insert end "\n"
1808 # make anything that looks like a SHA1 ID be a clickable link
1809 set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
1814 set linkid [string range $comment $s $e]
1815 if {![info exists idline($linkid)]} continue
1817 $ctext tag add link "$commentstart + $s c" "$commentstart + $e c"
1818 $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
1819 $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
1822 $ctext tag conf link -foreground blue -underline 1
1823 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
1824 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
1826 $ctext tag delete Comments
1827 $ctext tag remove found 1.0 end
1828 $ctext conf -state disabled
1829 set commentend [$ctext index "end - 1c"]
1831 $cflist delete 0 end
1832 $cflist insert end "Comments"
1833 if {$nparents($id) == 1} {
1834 startdiff [concat $id $parents($id)]
1835 } elseif {$nparents($id) > 1} {
1840 proc selnextline {dir} {
1842 if {![info exists selectedline]} return
1843 set l [expr $selectedline + $dir]
1848 proc unselectline {} {
1851 catch {unset selectedline}
1852 allcanvs delete secsel
1855 proc addtohistory {cmd} {
1856 global history historyindex
1858 if {$historyindex > 0
1859 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
1863 if {$historyindex < [llength $history]} {
1864 set history [lreplace $history $historyindex end $cmd]
1866 lappend history $cmd
1869 if {$historyindex > 1} {
1870 .ctop.top.bar.leftbut conf -state normal
1872 .ctop.top.bar.leftbut conf -state disabled
1874 .ctop.top.bar.rightbut conf -state disabled
1878 global history historyindex
1880 if {$historyindex > 1} {
1881 incr historyindex -1
1882 set cmd [lindex $history [expr {$historyindex - 1}]]
1884 .ctop.top.bar.rightbut conf -state normal
1886 if {$historyindex <= 1} {
1887 .ctop.top.bar.leftbut conf -state disabled
1892 global history historyindex
1894 if {$historyindex < [llength $history]} {
1895 set cmd [lindex $history $historyindex]
1898 .ctop.top.bar.leftbut conf -state normal
1900 if {$historyindex >= [llength $history]} {
1901 .ctop.top.bar.rightbut conf -state disabled
1905 proc mergediff {id} {
1906 global parents diffmergeid diffmergegca mergefilelist diffpindex
1910 set diffmergegca [findgca $parents($id)]
1911 if {[info exists mergefilelist($id)]} {
1912 if {$mergefilelist($id) ne {}} {
1920 proc findgca {ids} {
1927 set gca [exec git-merge-base $gca $id]
1936 proc contmergediff {ids} {
1937 global diffmergeid diffpindex parents nparents diffmergegca
1938 global treediffs mergefilelist diffids treepending
1940 # diff the child against each of the parents, and diff
1941 # each of the parents against the GCA.
1943 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1944 set ids [list [lindex $ids 1] $diffmergegca]
1946 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1947 set p [lindex $parents($diffmergeid) $diffpindex]
1948 set ids [list $diffmergeid $p]
1950 if {![info exists treediffs($ids)]} {
1952 if {![info exists treepending]} {
1959 # If a file in some parent is different from the child and also
1960 # different from the GCA, then it's interesting.
1961 # If we don't have a GCA, then a file is interesting if it is
1962 # different from the child in all the parents.
1963 if {$diffmergegca ne {}} {
1965 foreach p $parents($diffmergeid) {
1966 set gcadiffs $treediffs([list $p $diffmergegca])
1967 foreach f $treediffs([list $diffmergeid $p]) {
1968 if {[lsearch -exact $files $f] < 0
1969 && [lsearch -exact $gcadiffs $f] >= 0} {
1974 set files [lsort $files]
1976 set p [lindex $parents($diffmergeid) 0]
1977 set files $treediffs([list $diffmergeid $p])
1978 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1979 set p [lindex $parents($diffmergeid) $i]
1980 set df $treediffs([list $diffmergeid $p])
1983 if {[lsearch -exact $df $f] >= 0} {
1991 set mergefilelist($diffmergeid) $files
1997 proc showmergediff {} {
1998 global cflist diffmergeid mergefilelist parents
1999 global diffopts diffinhunk currentfile currenthunk filelines
2000 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2002 set files $mergefilelist($diffmergeid)
2004 $cflist insert end $f
2006 set env(GIT_DIFF_OPTS) $diffopts
2008 catch {unset currentfile}
2009 catch {unset currenthunk}
2010 catch {unset filelines}
2011 catch {unset groupfilenum}
2012 catch {unset grouphunks}
2013 set groupfilelast -1
2014 foreach p $parents($diffmergeid) {
2015 set cmd [list | git-diff-tree -p $p $diffmergeid]
2016 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2017 if {[catch {set f [open $cmd r]} err]} {
2018 error_popup "Error getting diffs: $err"
2025 set ids [list $diffmergeid $p]
2026 set mergefds($ids) $f
2027 set diffinhunk($ids) 0
2028 set diffblocked($ids) 0
2029 fconfigure $f -blocking 0
2030 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2034 proc getmergediffline {f ids id} {
2035 global diffmergeid diffinhunk diffoldlines diffnewlines
2036 global currentfile currenthunk
2037 global diffoldstart diffnewstart diffoldlno diffnewlno
2038 global diffblocked mergefilelist
2039 global noldlines nnewlines difflcounts filelines
2041 set n [gets $f line]
2043 if {![eof $f]} return
2046 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2053 if {$diffinhunk($ids) != 0} {
2054 set fi $currentfile($ids)
2055 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2056 # continuing an existing hunk
2057 set line [string range $line 1 end]
2058 set p [lindex $ids 1]
2059 if {$match eq "-" || $match eq " "} {
2060 set filelines($p,$fi,$diffoldlno($ids)) $line
2061 incr diffoldlno($ids)
2063 if {$match eq "+" || $match eq " "} {
2064 set filelines($id,$fi,$diffnewlno($ids)) $line
2065 incr diffnewlno($ids)
2067 if {$match eq " "} {
2068 if {$diffinhunk($ids) == 2} {
2069 lappend difflcounts($ids) \
2070 [list $noldlines($ids) $nnewlines($ids)]
2071 set noldlines($ids) 0
2072 set diffinhunk($ids) 1
2074 incr noldlines($ids)
2075 } elseif {$match eq "-" || $match eq "+"} {
2076 if {$diffinhunk($ids) == 1} {
2077 lappend difflcounts($ids) [list $noldlines($ids)]
2078 set noldlines($ids) 0
2079 set nnewlines($ids) 0
2080 set diffinhunk($ids) 2
2082 if {$match eq "-"} {
2083 incr noldlines($ids)
2085 incr nnewlines($ids)
2088 # and if it's \ No newline at end of line, then what?
2092 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2093 lappend difflcounts($ids) [list $noldlines($ids)]
2094 } elseif {$diffinhunk($ids) == 2
2095 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2096 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2098 set currenthunk($ids) [list $currentfile($ids) \
2099 $diffoldstart($ids) $diffnewstart($ids) \
2100 $diffoldlno($ids) $diffnewlno($ids) \
2102 set diffinhunk($ids) 0
2103 # -1 = need to block, 0 = unblocked, 1 = is blocked
2104 set diffblocked($ids) -1
2106 if {$diffblocked($ids) == -1} {
2107 fileevent $f readable {}
2108 set diffblocked($ids) 1
2114 if {!$diffblocked($ids)} {
2116 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2117 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2120 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2121 # start of a new file
2122 set currentfile($ids) \
2123 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2124 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2125 $line match f1l f1c f2l f2c rest]} {
2126 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2127 # start of a new hunk
2128 if {$f1l == 0 && $f1c == 0} {
2131 if {$f2l == 0 && $f2c == 0} {
2134 set diffinhunk($ids) 1
2135 set diffoldstart($ids) $f1l
2136 set diffnewstart($ids) $f2l
2137 set diffoldlno($ids) $f1l
2138 set diffnewlno($ids) $f2l
2139 set difflcounts($ids) {}
2140 set noldlines($ids) 0
2141 set nnewlines($ids) 0
2146 proc processhunks {} {
2147 global diffmergeid parents nparents currenthunk
2148 global mergefilelist diffblocked mergefds
2149 global grouphunks grouplinestart grouplineend groupfilenum
2151 set nfiles [llength $mergefilelist($diffmergeid)]
2155 # look for the earliest hunk
2156 foreach p $parents($diffmergeid) {
2157 set ids [list $diffmergeid $p]
2158 if {![info exists currenthunk($ids)]} return
2159 set i [lindex $currenthunk($ids) 0]
2160 set l [lindex $currenthunk($ids) 2]
2161 if {$i < $fi || ($i == $fi && $l < $lno)} {
2168 if {$fi < $nfiles} {
2169 set ids [list $diffmergeid $pi]
2170 set hunk $currenthunk($ids)
2171 unset currenthunk($ids)
2172 if {$diffblocked($ids) > 0} {
2173 fileevent $mergefds($ids) readable \
2174 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2176 set diffblocked($ids) 0
2178 if {[info exists groupfilenum] && $groupfilenum == $fi
2179 && $lno <= $grouplineend} {
2180 # add this hunk to the pending group
2181 lappend grouphunks($pi) $hunk
2182 set endln [lindex $hunk 4]
2183 if {$endln > $grouplineend} {
2184 set grouplineend $endln
2190 # succeeding stuff doesn't belong in this group, so
2191 # process the group now
2192 if {[info exists groupfilenum]} {
2198 if {$fi >= $nfiles} break
2201 set groupfilenum $fi
2202 set grouphunks($pi) [list $hunk]
2203 set grouplinestart $lno
2204 set grouplineend [lindex $hunk 4]
2208 proc processgroup {} {
2209 global groupfilelast groupfilenum difffilestart
2210 global mergefilelist diffmergeid ctext filelines
2211 global parents diffmergeid diffoffset
2212 global grouphunks grouplinestart grouplineend nparents
2215 $ctext conf -state normal
2218 if {$groupfilelast != $f} {
2219 $ctext insert end "\n"
2220 set here [$ctext index "end - 1c"]
2221 set difffilestart($f) $here
2222 set mark fmark.[expr {$f + 1}]
2223 $ctext mark set $mark $here
2224 $ctext mark gravity $mark left
2225 set header [lindex $mergefilelist($id) $f]
2226 set l [expr {(78 - [string length $header]) / 2}]
2227 set pad [string range "----------------------------------------" 1 $l]
2228 $ctext insert end "$pad $header $pad\n" filesep
2229 set groupfilelast $f
2230 foreach p $parents($id) {
2231 set diffoffset($p) 0
2235 $ctext insert end "@@" msep
2236 set nlines [expr {$grouplineend - $grouplinestart}]
2239 foreach p $parents($id) {
2240 set startline [expr {$grouplinestart + $diffoffset($p)}]
2242 set nl $grouplinestart
2243 if {[info exists grouphunks($p)]} {
2244 foreach h $grouphunks($p) {
2247 for {} {$nl < $l} {incr nl} {
2248 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2252 foreach chunk [lindex $h 5] {
2253 if {[llength $chunk] == 2} {
2254 set olc [lindex $chunk 0]
2255 set nlc [lindex $chunk 1]
2256 set nnl [expr {$nl + $nlc}]
2257 lappend events [list $nl $nnl $pnum $olc $nlc]
2261 incr ol [lindex $chunk 0]
2262 incr nl [lindex $chunk 0]
2267 if {$nl < $grouplineend} {
2268 for {} {$nl < $grouplineend} {incr nl} {
2269 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2273 set nlines [expr {$ol - $startline}]
2274 $ctext insert end " -$startline,$nlines" msep
2278 set nlines [expr {$grouplineend - $grouplinestart}]
2279 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2281 set events [lsort -integer -index 0 $events]
2282 set nevents [llength $events]
2283 set nmerge $nparents($diffmergeid)
2284 set l $grouplinestart
2285 for {set i 0} {$i < $nevents} {set i $j} {
2286 set nl [lindex $events $i 0]
2288 $ctext insert end " $filelines($id,$f,$l)\n"
2291 set e [lindex $events $i]
2292 set enl [lindex $e 1]
2296 set pnum [lindex $e 2]
2297 set olc [lindex $e 3]
2298 set nlc [lindex $e 4]
2299 if {![info exists delta($pnum)]} {
2300 set delta($pnum) [expr {$olc - $nlc}]
2301 lappend active $pnum
2303 incr delta($pnum) [expr {$olc - $nlc}]
2305 if {[incr j] >= $nevents} break
2306 set e [lindex $events $j]
2307 if {[lindex $e 0] >= $enl} break
2308 if {[lindex $e 1] > $enl} {
2309 set enl [lindex $e 1]
2312 set nlc [expr {$enl - $l}]
2315 if {[llength $active] == $nmerge - 1} {
2316 # no diff for one of the parents, i.e. it's identical
2317 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2318 if {![info exists delta($pnum)]} {
2319 if {$pnum < $mergemax} {
2327 } elseif {[llength $active] == $nmerge} {
2328 # all parents are different, see if one is very similar
2330 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2331 set sim [similarity $pnum $l $nlc $f \
2332 [lrange $events $i [expr {$j-1}]]]
2333 if {$sim > $bestsim} {
2339 lappend ncol m$bestpn
2343 foreach p $parents($id) {
2345 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2346 set olc [expr {$nlc + $delta($pnum)}]
2347 set ol [expr {$l + $diffoffset($p)}]
2348 incr diffoffset($p) $delta($pnum)
2350 for {} {$olc > 0} {incr olc -1} {
2351 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2355 set endl [expr {$l + $nlc}]
2357 # show this pretty much as a normal diff
2358 set p [lindex $parents($id) $bestpn]
2359 set ol [expr {$l + $diffoffset($p)}]
2360 incr diffoffset($p) $delta($bestpn)
2361 unset delta($bestpn)
2362 for {set k $i} {$k < $j} {incr k} {
2363 set e [lindex $events $k]
2364 if {[lindex $e 2] != $bestpn} continue
2365 set nl [lindex $e 0]
2366 set ol [expr {$ol + $nl - $l}]
2367 for {} {$l < $nl} {incr l} {
2368 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2371 for {} {$c > 0} {incr c -1} {
2372 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2375 set nl [lindex $e 1]
2376 for {} {$l < $nl} {incr l} {
2377 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2381 for {} {$l < $endl} {incr l} {
2382 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2385 while {$l < $grouplineend} {
2386 $ctext insert end " $filelines($id,$f,$l)\n"
2389 $ctext conf -state disabled
2392 proc similarity {pnum l nlc f events} {
2393 global diffmergeid parents diffoffset filelines
2396 set p [lindex $parents($id) $pnum]
2397 set ol [expr {$l + $diffoffset($p)}]
2398 set endl [expr {$l + $nlc}]
2402 if {[lindex $e 2] != $pnum} continue
2403 set nl [lindex $e 0]
2404 set ol [expr {$ol + $nl - $l}]
2405 for {} {$l < $nl} {incr l} {
2406 incr same [string length $filelines($id,$f,$l)]
2409 set oc [lindex $e 3]
2410 for {} {$oc > 0} {incr oc -1} {
2411 incr diff [string length $filelines($p,$f,$ol)]
2415 set nl [lindex $e 1]
2416 for {} {$l < $nl} {incr l} {
2417 incr diff [string length $filelines($id,$f,$l)]
2421 for {} {$l < $endl} {incr l} {
2422 incr same [string length $filelines($id,$f,$l)]
2428 return [expr {200 * $same / (2 * $same + $diff)}]
2431 proc startdiff {ids} {
2432 global treediffs diffids treepending diffmergeid
2435 catch {unset diffmergeid}
2436 if {![info exists treediffs($ids)]} {
2437 if {![info exists treepending]} {
2445 proc addtocflist {ids} {
2446 global treediffs cflist
2447 foreach f $treediffs($ids) {
2448 $cflist insert end $f
2453 proc gettreediffs {ids} {
2454 global treediff parents treepending
2455 set treepending $ids
2457 set id [lindex $ids 0]
2458 set p [lindex $ids 1]
2459 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2460 fconfigure $gdtf -blocking 0
2461 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2464 proc gettreediffline {gdtf ids} {
2465 global treediff treediffs treepending diffids diffmergeid
2467 set n [gets $gdtf line]
2469 if {![eof $gdtf]} return
2471 set treediffs($ids) $treediff
2473 if {$ids != $diffids} {
2474 gettreediffs $diffids
2476 if {[info exists diffmergeid]} {
2484 set file [lindex $line 5]
2485 lappend treediff $file
2488 proc getblobdiffs {ids} {
2489 global diffopts blobdifffd diffids env curdifftag curtagstart
2490 global difffilestart nextupdate diffinhdr treediffs
2492 set id [lindex $ids 0]
2493 set p [lindex $ids 1]
2494 set env(GIT_DIFF_OPTS) $diffopts
2495 set cmd [list | git-diff-tree -r -p -C $p $id]
2496 if {[catch {set bdf [open $cmd r]} err]} {
2497 puts "error getting diffs: $err"
2501 fconfigure $bdf -blocking 0
2502 set blobdifffd($ids) $bdf
2503 set curdifftag Comments
2505 catch {unset difffilestart}
2506 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2507 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2510 proc getblobdiffline {bdf ids} {
2511 global diffids blobdifffd ctext curdifftag curtagstart
2512 global diffnexthead diffnextnote difffilestart
2513 global nextupdate diffinhdr treediffs
2516 set n [gets $bdf line]
2520 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2521 $ctext tag add $curdifftag $curtagstart end
2526 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2529 $ctext conf -state normal
2530 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2531 # start of a new file
2532 $ctext insert end "\n"
2533 $ctext tag add $curdifftag $curtagstart end
2534 set curtagstart [$ctext index "end - 1c"]
2536 set here [$ctext index "end - 1c"]
2537 set i [lsearch -exact $treediffs($diffids) $fname]
2539 set difffilestart($i) $here
2541 $ctext mark set fmark.$i $here
2542 $ctext mark gravity fmark.$i left
2544 if {$newname != $fname} {
2545 set i [lsearch -exact $treediffs($diffids) $newname]
2547 set difffilestart($i) $here
2549 $ctext mark set fmark.$i $here
2550 $ctext mark gravity fmark.$i left
2553 set curdifftag "f:$fname"
2554 $ctext tag delete $curdifftag
2555 set l [expr {(78 - [string length $header]) / 2}]
2556 set pad [string range "----------------------------------------" 1 $l]
2557 $ctext insert end "$pad $header $pad\n" filesep
2559 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2561 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2562 $line match f1l f1c f2l f2c rest]} {
2564 $ctext insert end "\t" hunksep
2565 $ctext insert end " $f1l " d0 " $f2l " d1
2566 $ctext insert end " $rest \n" hunksep
2568 $ctext insert end "$line\n" hunksep
2572 set x [string range $line 0 0]
2573 if {$x == "-" || $x == "+"} {
2574 set tag [expr {$x == "+"}]
2576 set line [string range $line 1 end]
2578 $ctext insert end "$line\n" d$tag
2579 } elseif {$x == " "} {
2581 set line [string range $line 1 end]
2583 $ctext insert end "$line\n"
2584 } elseif {$diffinhdr || $x == "\\"} {
2585 # e.g. "\ No newline at end of file"
2586 $ctext insert end "$line\n" filesep
2588 # Something else we don't recognize
2589 if {$curdifftag != "Comments"} {
2590 $ctext insert end "\n"
2591 $ctext tag add $curdifftag $curtagstart end
2592 set curtagstart [$ctext index "end - 1c"]
2593 set curdifftag Comments
2595 $ctext insert end "$line\n" filesep
2598 $ctext conf -state disabled
2599 if {[clock clicks -milliseconds] >= $nextupdate} {
2601 fileevent $bdf readable {}
2603 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2608 global difffilestart ctext
2609 set here [$ctext index @0,0]
2610 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2611 if {[$ctext compare $difffilestart($i) > $here]} {
2612 if {![info exists pos]
2613 || [$ctext compare $difffilestart($i) < $pos]} {
2614 set pos $difffilestart($i)
2618 if {[info exists pos]} {
2623 proc listboxsel {} {
2624 global ctext cflist currentid
2625 if {![info exists currentid]} return
2626 set sel [lsort [$cflist curselection]]
2627 if {$sel eq {}} return
2628 set first [lindex $sel 0]
2629 catch {$ctext yview fmark.$first}
2633 global linespc charspc canvx0 canvy0 mainfont
2636 set linespc [font metrics $mainfont -linespace]
2637 set charspc [font measure $mainfont "m"]
2638 set canvy0 [expr 3 + 0.5 * $linespc]
2639 set canvx0 [expr 3 + 0.5 * $linespc]
2640 set xspc1(0) $linespc
2645 global stopped redisplaying phase
2646 if {$stopped > 1} return
2647 if {$phase == "getcommits"} return
2649 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2656 proc incrfont {inc} {
2657 global mainfont namefont textfont ctext canv phase
2658 global stopped entries
2660 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2661 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2662 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2664 $ctext conf -font $textfont
2665 $ctext tag conf filesep -font [concat $textfont bold]
2666 foreach e $entries {
2667 $e conf -font $mainfont
2669 if {$phase == "getcommits"} {
2670 $canv itemconf textitems -font $mainfont
2676 global sha1entry sha1string
2677 if {[string length $sha1string] == 40} {
2678 $sha1entry delete 0 end
2682 proc sha1change {n1 n2 op} {
2683 global sha1string currentid sha1but
2684 if {$sha1string == {}
2685 || ([info exists currentid] && $sha1string == $currentid)} {
2690 if {[$sha1but cget -state] == $state} return
2691 if {$state == "normal"} {
2692 $sha1but conf -state normal -relief raised -text "Goto: "
2694 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2698 proc gotocommit {} {
2699 global sha1string currentid idline tagids
2700 global lineid numcommits
2702 if {$sha1string == {}
2703 || ([info exists currentid] && $sha1string == $currentid)} return
2704 if {[info exists tagids($sha1string)]} {
2705 set id $tagids($sha1string)
2707 set id [string tolower $sha1string]
2708 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2710 for {set l 0} {$l < $numcommits} {incr l} {
2711 if {[string match $id* $lineid($l)]} {
2712 lappend matches $lineid($l)
2715 if {$matches ne {}} {
2716 if {[llength $matches] > 1} {
2717 error_popup "Short SHA1 id $id is ambiguous"
2720 set id [lindex $matches 0]
2724 if {[info exists idline($id)]} {
2725 selectline $idline($id) 1
2728 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2733 error_popup "$type $sha1string is not known"
2736 proc lineenter {x y id} {
2737 global hoverx hovery hoverid hovertimer
2738 global commitinfo canv
2740 if {![info exists commitinfo($id)]} return
2744 if {[info exists hovertimer]} {
2745 after cancel $hovertimer
2747 set hovertimer [after 500 linehover]
2751 proc linemotion {x y id} {
2752 global hoverx hovery hoverid hovertimer
2754 if {[info exists hoverid] && $id == $hoverid} {
2757 if {[info exists hovertimer]} {
2758 after cancel $hovertimer
2760 set hovertimer [after 500 linehover]
2764 proc lineleave {id} {
2765 global hoverid hovertimer canv
2767 if {[info exists hoverid] && $id == $hoverid} {
2769 if {[info exists hovertimer]} {
2770 after cancel $hovertimer
2778 global hoverx hovery hoverid hovertimer
2779 global canv linespc lthickness
2780 global commitinfo mainfont
2782 set text [lindex $commitinfo($hoverid) 0]
2783 set ymax [lindex [$canv cget -scrollregion] 3]
2784 if {$ymax == {}} return
2785 set yfrac [lindex [$canv yview] 0]
2786 set x [expr {$hoverx + 2 * $linespc}]
2787 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2788 set x0 [expr {$x - 2 * $lthickness}]
2789 set y0 [expr {$y - 2 * $lthickness}]
2790 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2791 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2792 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2793 -fill \#ffff80 -outline black -width 1 -tags hover]
2795 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2799 proc lineclick {x y id isnew} {
2800 global ctext commitinfo children cflist canv
2805 addtohistory [list lineclick $x $x $id 0]
2808 # fill the details pane with info about this line
2809 $ctext conf -state normal
2810 $ctext delete 0.0 end
2811 $ctext tag conf link -foreground blue -underline 1
2812 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2813 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2814 $ctext insert end "Parent:\t"
2815 $ctext insert end $id [list link link0]
2816 $ctext tag bind link0 <1> [list selbyid $id]
2817 set info $commitinfo($id)
2818 $ctext insert end "\n\t[lindex $info 0]\n"
2819 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2820 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2821 if {[info exists children($id)]} {
2822 $ctext insert end "\nChildren:"
2824 foreach child $children($id) {
2826 set info $commitinfo($child)
2827 $ctext insert end "\n\t"
2828 $ctext insert end $child [list link link$i]
2829 $ctext tag bind link$i <1> [list selbyid $child]
2830 $ctext insert end "\n\t[lindex $info 0]"
2831 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
2832 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
2835 $ctext conf -state disabled
2837 $cflist delete 0 end
2842 if {[info exists idline($id)]} {
2843 selectline $idline($id) 1
2849 if {![info exists startmstime]} {
2850 set startmstime [clock clicks -milliseconds]
2852 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2855 proc rowmenu {x y id} {
2856 global rowctxmenu idline selectedline rowmenuid
2858 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2863 $rowctxmenu entryconfigure 0 -state $state
2864 $rowctxmenu entryconfigure 1 -state $state
2865 $rowctxmenu entryconfigure 2 -state $state
2867 tk_popup $rowctxmenu $x $y
2870 proc diffvssel {dirn} {
2871 global rowmenuid selectedline lineid
2873 if {![info exists selectedline]} return
2875 set oldid $lineid($selectedline)
2876 set newid $rowmenuid
2878 set oldid $rowmenuid
2879 set newid $lineid($selectedline)
2881 addtohistory [list doseldiff $oldid $newid]
2882 doseldiff $oldid $newid
2885 proc doseldiff {oldid newid} {
2889 $ctext conf -state normal
2890 $ctext delete 0.0 end
2891 $ctext mark set fmark.0 0.0
2892 $ctext mark gravity fmark.0 left
2893 $cflist delete 0 end
2894 $cflist insert end "Top"
2895 $ctext insert end "From "
2896 $ctext tag conf link -foreground blue -underline 1
2897 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2898 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2899 $ctext tag bind link0 <1> [list selbyid $oldid]
2900 $ctext insert end $oldid [list link link0]
2901 $ctext insert end "\n "
2902 $ctext insert end [lindex $commitinfo($oldid) 0]
2903 $ctext insert end "\n\nTo "
2904 $ctext tag bind link1 <1> [list selbyid $newid]
2905 $ctext insert end $newid [list link link1]
2906 $ctext insert end "\n "
2907 $ctext insert end [lindex $commitinfo($newid) 0]
2908 $ctext insert end "\n"
2909 $ctext conf -state disabled
2910 $ctext tag delete Comments
2911 $ctext tag remove found 1.0 end
2912 startdiff [list $newid $oldid]
2916 global rowmenuid currentid commitinfo patchtop patchnum
2918 if {![info exists currentid]} return
2919 set oldid $currentid
2920 set oldhead [lindex $commitinfo($oldid) 0]
2921 set newid $rowmenuid
2922 set newhead [lindex $commitinfo($newid) 0]
2925 catch {destroy $top}
2927 label $top.title -text "Generate patch"
2928 grid $top.title - -pady 10
2929 label $top.from -text "From:"
2930 entry $top.fromsha1 -width 40 -relief flat
2931 $top.fromsha1 insert 0 $oldid
2932 $top.fromsha1 conf -state readonly
2933 grid $top.from $top.fromsha1 -sticky w
2934 entry $top.fromhead -width 60 -relief flat
2935 $top.fromhead insert 0 $oldhead
2936 $top.fromhead conf -state readonly
2937 grid x $top.fromhead -sticky w
2938 label $top.to -text "To:"
2939 entry $top.tosha1 -width 40 -relief flat
2940 $top.tosha1 insert 0 $newid
2941 $top.tosha1 conf -state readonly
2942 grid $top.to $top.tosha1 -sticky w
2943 entry $top.tohead -width 60 -relief flat
2944 $top.tohead insert 0 $newhead
2945 $top.tohead conf -state readonly
2946 grid x $top.tohead -sticky w
2947 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2948 grid $top.rev x -pady 10
2949 label $top.flab -text "Output file:"
2950 entry $top.fname -width 60
2951 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2953 grid $top.flab $top.fname -sticky w
2955 button $top.buts.gen -text "Generate" -command mkpatchgo
2956 button $top.buts.can -text "Cancel" -command mkpatchcan
2957 grid $top.buts.gen $top.buts.can
2958 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2959 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2960 grid $top.buts - -pady 10 -sticky ew
2964 proc mkpatchrev {} {
2967 set oldid [$patchtop.fromsha1 get]
2968 set oldhead [$patchtop.fromhead get]
2969 set newid [$patchtop.tosha1 get]
2970 set newhead [$patchtop.tohead get]
2971 foreach e [list fromsha1 fromhead tosha1 tohead] \
2972 v [list $newid $newhead $oldid $oldhead] {
2973 $patchtop.$e conf -state normal
2974 $patchtop.$e delete 0 end
2975 $patchtop.$e insert 0 $v
2976 $patchtop.$e conf -state readonly
2983 set oldid [$patchtop.fromsha1 get]
2984 set newid [$patchtop.tosha1 get]
2985 set fname [$patchtop.fname get]
2986 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2987 error_popup "Error creating patch: $err"
2989 catch {destroy $patchtop}
2993 proc mkpatchcan {} {
2996 catch {destroy $patchtop}
3001 global rowmenuid mktagtop commitinfo
3005 catch {destroy $top}
3007 label $top.title -text "Create tag"
3008 grid $top.title - -pady 10
3009 label $top.id -text "ID:"
3010 entry $top.sha1 -width 40 -relief flat
3011 $top.sha1 insert 0 $rowmenuid
3012 $top.sha1 conf -state readonly
3013 grid $top.id $top.sha1 -sticky w
3014 entry $top.head -width 60 -relief flat
3015 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3016 $top.head conf -state readonly
3017 grid x $top.head -sticky w
3018 label $top.tlab -text "Tag name:"
3019 entry $top.tag -width 60
3020 grid $top.tlab $top.tag -sticky w
3022 button $top.buts.gen -text "Create" -command mktaggo
3023 button $top.buts.can -text "Cancel" -command mktagcan
3024 grid $top.buts.gen $top.buts.can
3025 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3026 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3027 grid $top.buts - -pady 10 -sticky ew
3032 global mktagtop env tagids idtags
3033 global idpos idline linehtag canv selectedline
3035 set id [$mktagtop.sha1 get]
3036 set tag [$mktagtop.tag get]
3038 error_popup "No tag name specified"
3041 if {[info exists tagids($tag)]} {
3042 error_popup "Tag \"$tag\" already exists"
3047 set fname [file join $dir "refs/tags" $tag]
3048 set f [open $fname w]
3052 error_popup "Error creating tag: $err"
3056 set tagids($tag) $id
3057 lappend idtags($id) $tag
3058 $canv delete tag.$id
3059 set xt [eval drawtags $id $idpos($id)]
3060 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3061 if {[info exists selectedline] && $selectedline == $idline($id)} {
3062 selectline $selectedline 0
3069 catch {destroy $mktagtop}
3078 proc writecommit {} {
3079 global rowmenuid wrcomtop commitinfo wrcomcmd
3081 set top .writecommit
3083 catch {destroy $top}
3085 label $top.title -text "Write commit to file"
3086 grid $top.title - -pady 10
3087 label $top.id -text "ID:"
3088 entry $top.sha1 -width 40 -relief flat
3089 $top.sha1 insert 0 $rowmenuid
3090 $top.sha1 conf -state readonly
3091 grid $top.id $top.sha1 -sticky w
3092 entry $top.head -width 60 -relief flat
3093 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3094 $top.head conf -state readonly
3095 grid x $top.head -sticky w
3096 label $top.clab -text "Command:"
3097 entry $top.cmd -width 60 -textvariable wrcomcmd
3098 grid $top.clab $top.cmd -sticky w -pady 10
3099 label $top.flab -text "Output file:"
3100 entry $top.fname -width 60
3101 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3102 grid $top.flab $top.fname -sticky w
3104 button $top.buts.gen -text "Write" -command wrcomgo
3105 button $top.buts.can -text "Cancel" -command wrcomcan
3106 grid $top.buts.gen $top.buts.can
3107 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3108 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3109 grid $top.buts - -pady 10 -sticky ew
3116 set id [$wrcomtop.sha1 get]
3117 set cmd "echo $id | [$wrcomtop.cmd get]"
3118 set fname [$wrcomtop.fname get]
3119 if {[catch {exec sh -c $cmd >$fname &} err]} {
3120 error_popup "Error writing commit: $err"
3122 catch {destroy $wrcomtop}
3129 catch {destroy $wrcomtop}
3142 set diffopts "-U 5 -p"
3143 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3145 set mainfont {Helvetica 9}
3146 set textfont {Courier 9}
3147 set findmergefiles 0
3151 set colors {green red blue magenta darkgrey brown orange}
3153 catch {source ~/.gitk}
3155 set namefont $mainfont
3157 lappend namefont bold
3162 switch -regexp -- $arg {
3164 "^-b" { set boldnames 1 }
3165 "^-d" { set datemode 1 }
3167 lappend revtreeargs $arg
3182 getcommits $revtreeargs