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 ncmupdate
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]
36 set parse_args [concat --default HEAD $rargs]
37 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
39 # if git-rev-parse failed for some reason...
43 set parsed_args $rargs
46 set commfd [open "|git-rev-list --header --topo-order --parents $parsed_args" r]
48 puts stderr "Error executing git-rev-list: $err"
52 fconfigure $commfd -blocking 0 -translation lf
53 fileevent $commfd readable [list getcommitlines $commfd]
55 $canv create text 3 3 -anchor nw -text "Reading commits..." \
56 -font $mainfont -tags textitems
57 . config -cursor watch
61 proc getcommitlines {commfd} {
62 global commits parents cdate children
63 global commitlisted phase commitinfo nextupdate
64 global stopped redisplaying leftover
66 set stuff [read $commfd]
68 if {![eof $commfd]} return
69 # set it blocking so we wait for the process to terminate
70 fconfigure $commfd -blocking 1
71 if {![catch {close $commfd} err]} {
72 after idle finishcommits
75 if {[string range $err 0 4] == "usage"} {
77 {Gitk: error reading commits: bad arguments to git-rev-list.
78 (Note: arguments to gitk are passed to git-rev-list
79 to allow selection of commits to be displayed.)}
81 set err "Error reading commits: $err"
88 set i [string first "\0" $stuff $start]
90 append leftover [string range $stuff $start end]
93 set cmit [string range $stuff $start [expr {$i - 1}]]
95 set cmit "$leftover$cmit"
98 set start [expr {$i + 1}]
99 set j [string first "\n" $cmit]
102 set ids [string range $cmit 0 [expr {$j - 1}]]
105 if {![regexp {^[0-9a-f]{40}$} $id]} {
113 if {[string length $shortcmit] > 80} {
114 set shortcmit "[string range $shortcmit 0 80]..."
116 error_popup "Can't parse git-rev-list output: {$shortcmit}"
119 set id [lindex $ids 0]
120 set olds [lrange $ids 1 end]
121 set cmit [string range $cmit [expr {$j + 1}] end]
123 set commitlisted($id) 1
124 parsecommit $id $cmit 1 [lrange $ids 1 end]
126 if {[clock clicks -milliseconds] >= $nextupdate} {
129 while {$redisplaying} {
133 set phase "getcommits"
134 foreach id $commits {
137 if {[clock clicks -milliseconds] >= $nextupdate} {
146 proc doupdate {reading} {
147 global commfd nextupdate numcommits ncmupdate
150 fileevent $commfd readable {}
153 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
154 if {$numcommits < 100} {
155 set ncmupdate [expr {$numcommits + 1}]
156 } elseif {$numcommits < 10000} {
157 set ncmupdate [expr {$numcommits + 10}]
159 set ncmupdate [expr {$numcommits + 100}]
162 fileevent $commfd readable [list getcommitlines $commfd]
166 proc readcommit {id} {
167 if [catch {set contents [exec git-cat-file commit $id]}] return
168 parsecommit $id $contents 0 {}
171 proc parsecommit {id contents listed olds} {
172 global commitinfo children nchildren parents nparents cdate ncleft
181 if {![info exists nchildren($id)]} {
186 set parents($id) $olds
187 set nparents($id) [llength $olds]
189 if {![info exists nchildren($p)]} {
190 set children($p) [list $id]
193 } elseif {[lsearch -exact $children($p) $id] < 0} {
194 lappend children($p) $id
199 foreach line [split $contents "\n"] {
204 set tag [lindex $line 0]
205 if {$tag == "author"} {
206 set x [expr {[llength $line] - 2}]
207 set audate [lindex $line $x]
208 set auname [lrange $line 1 [expr {$x - 1}]]
209 } elseif {$tag == "committer"} {
210 set x [expr {[llength $line] - 2}]
211 set comdate [lindex $line $x]
212 set comname [lrange $line 1 [expr {$x - 1}]]
216 if {$comment == {}} {
217 set headline [string trim $line]
222 # git-rev-list indents the comment by 4 spaces;
223 # if we got this via git-cat-file, add the indentation
230 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
232 if {$comdate != {}} {
233 set cdate($id) $comdate
234 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
236 set commitinfo($id) [list $headline $auname $audate \
237 $comname $comdate $comment]
241 global tagids idtags headids idheads
242 set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
247 if {[regexp {^[0-9a-f]{40}} $line id]} {
248 set direct [file tail $f]
249 set tagids($direct) $id
250 lappend idtags($id) $direct
251 set contents [split [exec git-cat-file tag $id] "\n"]
255 foreach l $contents {
257 switch -- [lindex $l 0] {
258 "object" {set obj [lindex $l 1]}
259 "type" {set type [lindex $l 1]}
260 "tag" {set tag [string range $l 4 end]}
263 if {$obj != {} && $type == "commit" && $tag != {}} {
264 set tagids($tag) $obj
265 lappend idtags($obj) $tag
271 set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
275 set line [read $fd 40]
276 if {[regexp {^[0-9a-f]{40}} $line id]} {
277 set head [file tail $f]
278 set headids($head) $line
279 lappend idheads($line) $head
286 proc error_popup msg {
290 message $w.m -text $msg -justify center -aspect 400
291 pack $w.m -side top -fill x -padx 20 -pady 20
292 button $w.ok -text OK -command "destroy $w"
293 pack $w.ok -side bottom -fill x
294 bind $w <Visibility> "grab $w; focus $w"
299 global canv canv2 canv3 linespc charspc ctext cflist textfont
300 global findtype findtypemenu findloc findstring fstring geometry
301 global entries sha1entry sha1string sha1but
302 global maincursor textcursor curtextcursor
303 global rowctxmenu gaudydiff mergemax
306 .bar add cascade -label "File" -menu .bar.file
308 .bar.file add command -label "Quit" -command doquit
310 .bar add cascade -label "Help" -menu .bar.help
311 .bar.help add command -label "About gitk" -command about
312 . configure -menu .bar
314 if {![info exists geometry(canv1)]} {
315 set geometry(canv1) [expr 45 * $charspc]
316 set geometry(canv2) [expr 30 * $charspc]
317 set geometry(canv3) [expr 15 * $charspc]
318 set geometry(canvh) [expr 25 * $linespc + 4]
319 set geometry(ctextw) 80
320 set geometry(ctexth) 30
321 set geometry(cflistw) 30
323 panedwindow .ctop -orient vertical
324 if {[info exists geometry(width)]} {
325 .ctop conf -width $geometry(width) -height $geometry(height)
326 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
327 set geometry(ctexth) [expr {($texth - 8) /
328 [font metrics $textfont -linespace]}]
332 pack .ctop.top.bar -side bottom -fill x
333 set cscroll .ctop.top.csb
334 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
335 pack $cscroll -side right -fill y
336 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
337 pack .ctop.top.clist -side top -fill both -expand 1
339 set canv .ctop.top.clist.canv
340 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
342 -yscrollincr $linespc -yscrollcommand "$cscroll set"
343 .ctop.top.clist add $canv
344 set canv2 .ctop.top.clist.canv2
345 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
346 -bg white -bd 0 -yscrollincr $linespc
347 .ctop.top.clist add $canv2
348 set canv3 .ctop.top.clist.canv3
349 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
350 -bg white -bd 0 -yscrollincr $linespc
351 .ctop.top.clist add $canv3
352 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
354 set sha1entry .ctop.top.bar.sha1
355 set entries $sha1entry
356 set sha1but .ctop.top.bar.sha1label
357 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
358 -command gotocommit -width 8
359 $sha1but conf -disabledforeground [$sha1but cget -foreground]
360 pack .ctop.top.bar.sha1label -side left
361 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
362 trace add variable sha1string write sha1change
363 pack $sha1entry -side left -pady 2
365 image create bitmap bm-left -data {
366 #define left_width 16
367 #define left_height 16
368 static unsigned char left_bits[] = {
369 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
370 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
371 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
373 image create bitmap bm-right -data {
374 #define right_width 16
375 #define right_height 16
376 static unsigned char right_bits[] = {
377 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
378 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
379 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
381 button .ctop.top.bar.leftbut -image bm-left -command goback \
382 -state disabled -width 26
383 pack .ctop.top.bar.leftbut -side left -fill y
384 button .ctop.top.bar.rightbut -image bm-right -command goforw \
385 -state disabled -width 26
386 pack .ctop.top.bar.rightbut -side left -fill y
388 button .ctop.top.bar.findbut -text "Find" -command dofind
389 pack .ctop.top.bar.findbut -side left
391 set fstring .ctop.top.bar.findstring
392 lappend entries $fstring
393 entry $fstring -width 30 -font $textfont -textvariable findstring
394 pack $fstring -side left -expand 1 -fill x
396 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
397 findtype Exact IgnCase Regexp]
398 set findloc "All fields"
399 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
400 Comments Author Committer Files Pickaxe
401 pack .ctop.top.bar.findloc -side right
402 pack .ctop.top.bar.findtype -side right
403 # for making sure type==Exact whenever loc==Pickaxe
404 trace add variable findloc write findlocchange
406 panedwindow .ctop.cdet -orient horizontal
408 frame .ctop.cdet.left
409 set ctext .ctop.cdet.left.ctext
410 text $ctext -bg white -state disabled -font $textfont \
411 -width $geometry(ctextw) -height $geometry(ctexth) \
412 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
413 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
414 pack .ctop.cdet.left.sb -side right -fill y
415 pack $ctext -side left -fill both -expand 1
416 .ctop.cdet add .ctop.cdet.left
418 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
420 $ctext tag conf hunksep -back blue -fore white
421 $ctext tag conf d0 -back "#ff8080"
422 $ctext tag conf d1 -back green
424 $ctext tag conf hunksep -fore blue
425 $ctext tag conf d0 -fore red
426 $ctext tag conf d1 -fore "#00a000"
427 $ctext tag conf m0 -fore red
428 $ctext tag conf m1 -fore blue
429 $ctext tag conf m2 -fore green
430 $ctext tag conf m3 -fore purple
431 $ctext tag conf m4 -fore brown
432 $ctext tag conf mmax -fore darkgrey
434 $ctext tag conf mresult -font [concat $textfont bold]
435 $ctext tag conf msep -font [concat $textfont bold]
436 $ctext tag conf found -back yellow
439 frame .ctop.cdet.right
440 set cflist .ctop.cdet.right.cfiles
441 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
442 -yscrollcommand ".ctop.cdet.right.sb set"
443 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
444 pack .ctop.cdet.right.sb -side right -fill y
445 pack $cflist -side left -fill both -expand 1
446 .ctop.cdet add .ctop.cdet.right
447 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
449 pack .ctop -side top -fill both -expand 1
451 bindall <1> {selcanvline %W %x %y}
452 #bindall <B1-Motion> {selcanvline %W %x %y}
453 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
454 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
455 bindall <2> "allcanvs scan mark 0 %y"
456 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
457 bind . <Key-Up> "selnextline -1"
458 bind . <Key-Down> "selnextline 1"
459 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
460 bind . <Key-Next> "allcanvs yview scroll 1 pages"
461 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
462 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
463 bindkey <Key-space> "$ctext yview scroll 1 pages"
464 bindkey p "selnextline -1"
465 bindkey n "selnextline 1"
466 bindkey b "$ctext yview scroll -1 pages"
467 bindkey d "$ctext yview scroll 18 units"
468 bindkey u "$ctext yview scroll -18 units"
469 bindkey / {findnext 1}
470 bindkey <Key-Return> {findnext 0}
473 bind . <Control-q> doquit
474 bind . <Control-f> dofind
475 bind . <Control-g> {findnext 0}
476 bind . <Control-r> findprev
477 bind . <Control-equal> {incrfont 1}
478 bind . <Control-KP_Add> {incrfont 1}
479 bind . <Control-minus> {incrfont -1}
480 bind . <Control-KP_Subtract> {incrfont -1}
481 bind $cflist <<ListboxSelect>> listboxsel
482 bind . <Destroy> {savestuff %W}
483 bind . <Button-1> "click %W"
484 bind $fstring <Key-Return> dofind
485 bind $sha1entry <Key-Return> gotocommit
486 bind $sha1entry <<PasteSelection>> clearsha1
488 set maincursor [. cget -cursor]
489 set textcursor [$ctext cget -cursor]
490 set curtextcursor $textcursor
492 set rowctxmenu .rowctxmenu
493 menu $rowctxmenu -tearoff 0
494 $rowctxmenu add command -label "Diff this -> selected" \
495 -command {diffvssel 0}
496 $rowctxmenu add command -label "Diff selected -> this" \
497 -command {diffvssel 1}
498 $rowctxmenu add command -label "Make patch" -command mkpatch
499 $rowctxmenu add command -label "Create tag" -command mktag
500 $rowctxmenu add command -label "Write commit to file" -command writecommit
503 # when we make a key binding for the toplevel, make sure
504 # it doesn't get triggered when that key is pressed in the
505 # find string entry widget.
506 proc bindkey {ev script} {
509 set escript [bind Entry $ev]
510 if {$escript == {}} {
511 set escript [bind Entry <Key>]
514 bind $e $ev "$escript; break"
518 # set the focus back to the toplevel for any click outside
529 global canv canv2 canv3 ctext cflist mainfont textfont
530 global stuffsaved findmergefiles gaudydiff maxgraphpct
533 if {$stuffsaved} return
534 if {![winfo viewable .]} return
536 set f [open "~/.gitk-new" w]
537 puts $f [list set mainfont $mainfont]
538 puts $f [list set textfont $textfont]
539 puts $f [list set findmergefiles $findmergefiles]
540 puts $f [list set gaudydiff $gaudydiff]
541 puts $f [list set maxgraphpct $maxgraphpct]
542 puts $f [list set maxwidth $maxwidth]
543 puts $f "set geometry(width) [winfo width .ctop]"
544 puts $f "set geometry(height) [winfo height .ctop]"
545 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
546 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
547 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
548 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
549 set wid [expr {([winfo width $ctext] - 8) \
550 / [font measure $textfont "0"]}]
551 puts $f "set geometry(ctextw) $wid"
552 set wid [expr {([winfo width $cflist] - 11) \
553 / [font measure [$cflist cget -font] "0"]}]
554 puts $f "set geometry(cflistw) $wid"
556 file rename -force "~/.gitk-new" "~/.gitk"
561 proc resizeclistpanes {win w} {
563 if [info exists oldwidth($win)] {
564 set s0 [$win sash coord 0]
565 set s1 [$win sash coord 1]
567 set sash0 [expr {int($w/2 - 2)}]
568 set sash1 [expr {int($w*5/6 - 2)}]
570 set factor [expr {1.0 * $w / $oldwidth($win)}]
571 set sash0 [expr {int($factor * [lindex $s0 0])}]
572 set sash1 [expr {int($factor * [lindex $s1 0])}]
576 if {$sash1 < $sash0 + 20} {
577 set sash1 [expr $sash0 + 20]
579 if {$sash1 > $w - 10} {
580 set sash1 [expr $w - 10]
581 if {$sash0 > $sash1 - 20} {
582 set sash0 [expr $sash1 - 20]
586 $win sash place 0 $sash0 [lindex $s0 1]
587 $win sash place 1 $sash1 [lindex $s1 1]
589 set oldwidth($win) $w
592 proc resizecdetpanes {win w} {
594 if [info exists oldwidth($win)] {
595 set s0 [$win sash coord 0]
597 set sash0 [expr {int($w*3/4 - 2)}]
599 set factor [expr {1.0 * $w / $oldwidth($win)}]
600 set sash0 [expr {int($factor * [lindex $s0 0])}]
604 if {$sash0 > $w - 15} {
605 set sash0 [expr $w - 15]
608 $win sash place 0 $sash0 [lindex $s0 1]
610 set oldwidth($win) $w
614 global canv canv2 canv3
620 proc bindall {event action} {
621 global canv canv2 canv3
622 bind $canv $event $action
623 bind $canv2 $event $action
624 bind $canv3 $event $action
629 if {[winfo exists $w]} {
634 wm title $w "About gitk"
638 Copyright © 2005 Paul Mackerras
640 Use and redistribute under the terms of the GNU General Public License} \
641 -justify center -aspect 400
642 pack $w.m -side top -fill x -padx 20 -pady 20
643 button $w.ok -text Close -command "destroy $w"
644 pack $w.ok -side bottom
647 proc assigncolor {id} {
648 global commitinfo colormap commcolors colors nextcolor
649 global parents nparents children nchildren
650 global cornercrossings crossings
652 if [info exists colormap($id)] return
653 set ncolors [llength $colors]
654 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
655 set child [lindex $children($id) 0]
656 if {[info exists colormap($child)]
657 && $nparents($child) == 1} {
658 set colormap($id) $colormap($child)
663 if {[info exists cornercrossings($id)]} {
664 foreach x $cornercrossings($id) {
665 if {[info exists colormap($x)]
666 && [lsearch -exact $badcolors $colormap($x)] < 0} {
667 lappend badcolors $colormap($x)
670 if {[llength $badcolors] >= $ncolors} {
674 set origbad $badcolors
675 if {[llength $badcolors] < $ncolors - 1} {
676 if {[info exists crossings($id)]} {
677 foreach x $crossings($id) {
678 if {[info exists colormap($x)]
679 && [lsearch -exact $badcolors $colormap($x)] < 0} {
680 lappend badcolors $colormap($x)
683 if {[llength $badcolors] >= $ncolors} {
684 set badcolors $origbad
687 set origbad $badcolors
689 if {[llength $badcolors] < $ncolors - 1} {
690 foreach child $children($id) {
691 if {[info exists colormap($child)]
692 && [lsearch -exact $badcolors $colormap($child)] < 0} {
693 lappend badcolors $colormap($child)
695 if {[info exists parents($child)]} {
696 foreach p $parents($child) {
697 if {[info exists colormap($p)]
698 && [lsearch -exact $badcolors $colormap($p)] < 0} {
699 lappend badcolors $colormap($p)
704 if {[llength $badcolors] >= $ncolors} {
705 set badcolors $origbad
708 for {set i 0} {$i <= $ncolors} {incr i} {
709 set c [lindex $colors $nextcolor]
710 if {[incr nextcolor] >= $ncolors} {
713 if {[lsearch -exact $badcolors $c]} break
719 global canvy canvy0 lineno numcommits nextcolor linespc
720 global mainline mainlinearrow sidelines
721 global nchildren ncleft
722 global displist nhyperspace
729 catch {unset mainline}
730 catch {unset mainlinearrow}
731 catch {unset sidelines}
732 foreach id [array names nchildren] {
733 set ncleft($id) $nchildren($id)
739 proc bindline {t id} {
742 $canv bind $t <Enter> "lineenter %x %y $id"
743 $canv bind $t <Motion> "linemotion %x %y $id"
744 $canv bind $t <Leave> "lineleave $id"
745 $canv bind $t <Button-1> "lineclick %x %y $id 1"
748 # level here is an index in displist
749 proc drawcommitline {level} {
750 global parents children nparents displist
751 global canv canv2 canv3 mainfont namefont canvy linespc
752 global lineid linehtag linentag linedtag commitinfo
753 global colormap numcommits currentparents dupparents
754 global idtags idline idheads
755 global lineno lthickness mainline mainlinearrow sidelines
756 global commitlisted rowtextx idpos lastuse displist
757 global oldnlines olddlevel olddisplist
761 set id [lindex $displist $level]
762 set lastuse($id) $lineno
763 set lineid($lineno) $id
764 set idline($id) $lineno
765 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
766 if {![info exists commitinfo($id)]} {
768 if {![info exists commitinfo($id)]} {
769 set commitinfo($id) {"No commit information available"}
774 set currentparents {}
776 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
777 foreach p $parents($id) {
778 if {[lsearch -exact $currentparents $p] < 0} {
779 lappend currentparents $p
781 # remember that this parent was listed twice
782 lappend dupparents $p
786 set x [xcoord $level $level $lineno]
788 set canvy [expr $canvy + $linespc]
789 allcanvs conf -scrollregion \
790 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
791 if {[info exists mainline($id)]} {
792 lappend mainline($id) $x $y1
793 if {$mainlinearrow($id) ne "none"} {
794 set mainline($id) [trimdiagstart $mainline($id)]
796 set t [$canv create line $mainline($id) \
797 -width $lthickness -fill $colormap($id) \
798 -arrow $mainlinearrow($id)]
802 if {[info exists sidelines($id)]} {
803 foreach ls $sidelines($id) {
804 set coords [lindex $ls 0]
805 set thick [lindex $ls 1]
806 set arrow [lindex $ls 2]
807 set t [$canv create line $coords -fill $colormap($id) \
808 -width [expr {$thick * $lthickness}] -arrow $arrow]
813 set orad [expr {$linespc / 3}]
814 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
815 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
816 -fill $ofill -outline black -width 1]
818 $canv bind $t <1> {selcanvline {} %x %y}
819 set xt [xcoord [llength $displist] $level $lineno]
820 if {[llength $currentparents] > 2} {
821 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
823 set rowtextx($lineno) $xt
824 set idpos($id) [list $x $xt $y1]
825 if {[info exists idtags($id)] || [info exists idheads($id)]} {
826 set xt [drawtags $id $x $xt $y1]
828 set headline [lindex $commitinfo($id) 0]
829 set name [lindex $commitinfo($id) 1]
830 set date [lindex $commitinfo($id) 2]
831 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
832 -text $headline -font $mainfont ]
833 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
834 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
835 -text $name -font $namefont]
836 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
837 -text $date -font $mainfont]
840 set olddisplist $displist
841 set oldnlines [llength $displist]
844 proc drawtags {id x xt y1} {
845 global idtags idheads
846 global linespc lthickness
851 if {[info exists idtags($id)]} {
852 set marks $idtags($id)
853 set ntags [llength $marks]
855 if {[info exists idheads($id)]} {
856 set marks [concat $marks $idheads($id)]
862 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
863 set yt [expr $y1 - 0.5 * $linespc]
864 set yb [expr $yt + $linespc - 1]
868 set wid [font measure $mainfont $tag]
871 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
873 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
874 -width $lthickness -fill black -tags tag.$id]
876 foreach tag $marks x $xvals wid $wvals {
877 set xl [expr $x + $delta]
878 set xr [expr $x + $delta + $wid + $lthickness]
879 if {[incr ntags -1] >= 0} {
881 $canv create polygon $x [expr $yt + $delta] $xl $yt\
882 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
883 -width 1 -outline black -fill yellow -tags tag.$id
886 set xl [expr $xl - $delta/2]
887 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
888 -width 1 -outline black -fill green -tags tag.$id
890 $canv create text $xl $y1 -anchor w -text $tag \
891 -font $mainfont -tags tag.$id
896 proc notecrossings {id lo hi corner} {
897 global olddisplist crossings cornercrossings
899 for {set i $lo} {[incr i] < $hi} {} {
900 set p [lindex $olddisplist $i]
901 if {$p == {}} continue
903 if {![info exists cornercrossings($id)]
904 || [lsearch -exact $cornercrossings($id) $p] < 0} {
905 lappend cornercrossings($id) $p
907 if {![info exists cornercrossings($p)]
908 || [lsearch -exact $cornercrossings($p) $id] < 0} {
909 lappend cornercrossings($p) $id
912 if {![info exists crossings($id)]
913 || [lsearch -exact $crossings($id) $p] < 0} {
914 lappend crossings($id) $p
916 if {![info exists crossings($p)]
917 || [lsearch -exact $crossings($p) $id] < 0} {
918 lappend crossings($p) $id
924 proc xcoord {i level ln} {
925 global canvx0 xspc1 xspc2
927 set x [expr {$canvx0 + $i * $xspc1($ln)}]
928 if {$i > 0 && $i == $level} {
929 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
930 } elseif {$i > $level} {
931 set x [expr {$x + $xspc2 - $xspc1($ln)}]
936 # it seems Tk can't draw arrows on the end of diagonal line segments...
937 proc trimdiagend {line} {
938 while {[llength $line] > 4} {
939 set x1 [lindex $line end-3]
940 set y1 [lindex $line end-2]
941 set x2 [lindex $line end-1]
942 set y2 [lindex $line end]
943 if {($x1 == $x2) != ($y1 == $y2)} break
944 set line [lreplace $line end-1 end]
949 proc trimdiagstart {line} {
950 while {[llength $line] > 4} {
951 set x1 [lindex $line 0]
952 set y1 [lindex $line 1]
953 set x2 [lindex $line 2]
954 set y2 [lindex $line 3]
955 if {($x1 == $x2) != ($y1 == $y2)} break
956 set line [lreplace $line 0 1]
961 proc drawslants {id needonscreen nohs} {
962 global canv mainline mainlinearrow sidelines
963 global canvx0 canvy xspc1 xspc2 lthickness
964 global currentparents dupparents
965 global lthickness linespc canvy colormap lineno geometry
966 global maxgraphpct maxwidth
967 global displist onscreen lastuse
968 global parents commitlisted
969 global oldnlines olddlevel olddisplist
970 global nhyperspace numcommits nnewparents
978 set y1 [expr {$canvy - $linespc}]
981 # work out what we need to get back on screen
983 if {$onscreen($id) < 0} {
984 # next to do isn't displayed, better get it on screen...
985 lappend reins [list $id 0]
987 # make sure all the previous commits's parents are on the screen
988 foreach p $currentparents {
989 if {$onscreen($p) < 0} {
990 lappend reins [list $p 0]
993 # bring back anything requested by caller
994 if {$needonscreen ne {}} {
995 lappend reins $needonscreen
999 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1000 set dlevel $olddlevel
1001 set x [xcoord $dlevel $dlevel $lineno]
1002 set mainline($id) [list $x $y1]
1003 set mainlinearrow($id) none
1004 set lastuse($id) $lineno
1005 set displist [lreplace $displist $dlevel $dlevel $id]
1007 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1012 set displist [lreplace $displist $olddlevel $olddlevel]
1014 foreach p $currentparents {
1015 set lastuse($p) $lineno
1016 if {$onscreen($p) == 0} {
1017 set displist [linsert $displist $j $p]
1022 if {$onscreen($id) == 0} {
1023 lappend displist $id
1027 # remove the null entry if present
1028 set nullentry [lsearch -exact $displist {}]
1029 if {$nullentry >= 0} {
1030 set displist [lreplace $displist $nullentry $nullentry]
1033 # bring back the ones we need now (if we did it earlier
1034 # it would change displist and invalidate olddlevel)
1036 # test again in case of duplicates in reins
1037 set p [lindex $pi 0]
1038 if {$onscreen($p) < 0} {
1040 set lastuse($p) $lineno
1041 set displist [linsert $displist [lindex $pi 1] $p]
1046 set lastuse($id) $lineno
1048 # see if we need to make any lines jump off into hyperspace
1049 set displ [llength $displist]
1050 if {$displ > $maxwidth} {
1052 foreach x $displist {
1053 lappend ages [list $lastuse($x) $x]
1055 set ages [lsort -integer -index 0 $ages]
1057 while {$displ > $maxwidth} {
1058 set use [lindex $ages $k 0]
1059 set victim [lindex $ages $k 1]
1060 if {$use >= $lineno - 5} break
1062 if {[lsearch -exact $nohs $victim] >= 0} continue
1063 set i [lsearch -exact $displist $victim]
1064 set displist [lreplace $displist $i $i]
1065 set onscreen($victim) -1
1068 if {$i < $nullentry} {
1071 set x [lindex $mainline($victim) end-1]
1072 lappend mainline($victim) $x $y1
1073 set line [trimdiagend $mainline($victim)]
1075 if {$mainlinearrow($victim) ne "none"} {
1076 set line [trimdiagstart $line]
1079 lappend sidelines($victim) [list $line 1 $arrow]
1080 unset mainline($victim)
1084 set dlevel [lsearch -exact $displist $id]
1086 # If we are reducing, put in a null entry
1087 if {$displ < $oldnlines} {
1088 # does the next line look like a merge?
1089 # i.e. does it have > 1 new parent?
1090 if {$nnewparents($id) > 1} {
1091 set i [expr {$dlevel + 1}]
1092 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1094 if {$nullentry >= 0 && $nullentry < $i} {
1097 } elseif {$nullentry >= 0} {
1100 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1105 if {$dlevel >= $i} {
1110 set displist [linsert $displist $i {}]
1112 if {$dlevel >= $i} {
1118 # decide on the line spacing for the next line
1119 set lj [expr {$lineno + 1}]
1120 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1121 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1122 set xspc1($lj) $xspc2
1124 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1125 if {$xspc1($lj) < $lthickness} {
1126 set xspc1($lj) $lthickness
1130 foreach idi $reins {
1131 set id [lindex $idi 0]
1132 set j [lsearch -exact $displist $id]
1133 set xj [xcoord $j $dlevel $lj]
1134 set mainline($id) [list $xj $y2]
1135 set mainlinearrow($id) first
1139 foreach id $olddisplist {
1141 if {$id == {}} continue
1142 if {$onscreen($id) <= 0} continue
1143 set xi [xcoord $i $olddlevel $lineno]
1144 if {$i == $olddlevel} {
1145 foreach p $currentparents {
1146 set j [lsearch -exact $displist $p]
1147 set coords [list $xi $y1]
1148 set xj [xcoord $j $dlevel $lj]
1149 if {$xj < $xi - $linespc} {
1150 lappend coords [expr {$xj + $linespc}] $y1
1151 notecrossings $p $j $i [expr {$j + 1}]
1152 } elseif {$xj > $xi + $linespc} {
1153 lappend coords [expr {$xj - $linespc}] $y1
1154 notecrossings $p $i $j [expr {$j - 1}]
1156 if {[lsearch -exact $dupparents $p] >= 0} {
1157 # draw a double-width line to indicate the doubled parent
1158 lappend coords $xj $y2
1159 lappend sidelines($p) [list $coords 2 none]
1160 if {![info exists mainline($p)]} {
1161 set mainline($p) [list $xj $y2]
1162 set mainlinearrow($p) none
1165 # normal case, no parent duplicated
1167 set dx [expr {abs($xi - $xj)}]
1168 if {0 && $dx < $linespc} {
1169 set yb [expr {$y1 + $dx}]
1171 if {![info exists mainline($p)]} {
1173 lappend coords $xj $yb
1175 set mainline($p) $coords
1176 set mainlinearrow($p) none
1178 lappend coords $xj $yb
1180 lappend coords $xj $y2
1182 lappend sidelines($p) [list $coords 1 none]
1188 if {[lindex $displist $i] != $id} {
1189 set j [lsearch -exact $displist $id]
1191 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1192 || ($olddlevel < $i && $i < $dlevel)
1193 || ($dlevel < $i && $i < $olddlevel)} {
1194 set xj [xcoord $j $dlevel $lj]
1195 lappend mainline($id) $xi $y1 $xj $y2
1202 # search for x in a list of lists
1203 proc llsearch {llist x} {
1206 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1214 proc drawmore {reading} {
1215 global displayorder numcommits ncmupdate nextupdate
1216 global stopped nhyperspace parents commitlisted
1217 global maxwidth onscreen displist currentparents olddlevel
1219 set n [llength $displayorder]
1220 while {$numcommits < $n} {
1221 set id [lindex $displayorder $numcommits]
1222 set ctxend [expr {$numcommits + 10}]
1223 if {!$reading && $ctxend > $n} {
1227 if {$numcommits > 0} {
1228 set dlist [lreplace $displist $olddlevel $olddlevel]
1230 foreach p $currentparents {
1231 if {$onscreen($p) == 0} {
1232 set dlist [linsert $dlist $i $p]
1239 set isfat [expr {[llength $dlist] > $maxwidth}]
1240 if {$nhyperspace > 0 || $isfat} {
1241 if {$ctxend > $n} break
1242 # work out what to bring back and
1243 # what we want to don't want to send into hyperspace
1245 for {set k $numcommits} {$k < $ctxend} {incr k} {
1246 set x [lindex $displayorder $k]
1247 set i [llsearch $dlist $x]
1249 set i [llength $dlist]
1252 if {[lsearch -exact $nohs $x] < 0} {
1255 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1256 set reins [list $x $i]
1259 if {[info exists commitlisted($x)]} {
1261 foreach p $parents($x) {
1262 if {[llsearch $dlist $p] < 0} {
1264 if {[lsearch -exact $nohs $p] < 0} {
1267 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1268 set reins [list $p [expr {$i + $right}]]
1274 set l [lindex $dlist $i]
1275 if {[llength $l] == 1} {
1278 set j [lsearch -exact $l $x]
1279 set l [concat [lreplace $l $j $j] $newp]
1281 set dlist [lreplace $dlist $i $i $l]
1282 if {$room && $isfat && [llength $newp] <= 1} {
1288 set dlevel [drawslants $id $reins $nohs]
1289 drawcommitline $dlevel
1290 if {[clock clicks -milliseconds] >= $nextupdate
1291 && $numcommits >= $ncmupdate} {
1298 # level here is an index in todo
1299 proc updatetodo {level noshortcut} {
1300 global ncleft todo nnewparents
1301 global commitlisted parents onscreen
1303 set id [lindex $todo $level]
1305 if {[info exists commitlisted($id)]} {
1306 foreach p $parents($id) {
1307 if {[lsearch -exact $olds $p] < 0} {
1312 if {!$noshortcut && [llength $olds] == 1} {
1313 set p [lindex $olds 0]
1314 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1316 set todo [lreplace $todo $level $level $p]
1318 set nnewparents($id) 1
1323 set todo [lreplace $todo $level $level]
1328 set k [lsearch -exact $todo $p]
1330 set todo [linsert $todo $i $p]
1336 set nnewparents($id) $n
1341 proc decidenext {{noread 0}} {
1343 global datemode cdate
1346 # choose which one to do next time around
1347 set todol [llength $todo]
1350 for {set k $todol} {[incr k -1] >= 0} {} {
1351 set p [lindex $todo $k]
1352 if {$ncleft($p) == 0} {
1354 if {![info exists commitinfo($p)]} {
1360 if {$latest == {} || $cdate($p) > $latest} {
1362 set latest $cdate($p)
1372 puts "ERROR: none of the pending commits can be done yet:"
1374 puts " $p ($ncleft($p))"
1383 proc drawcommit {id} {
1384 global phase todo nchildren datemode nextupdate
1385 global numcommits ncmupdate displayorder todo onscreen
1387 if {$phase != "incrdraw"} {
1393 if {$nchildren($id) == 0} {
1397 set level [decidenext 1]
1398 if {$level == {} || $id != [lindex $todo $level]} {
1402 lappend displayorder [lindex $todo $level]
1403 if {[updatetodo $level $datemode]} {
1404 set level [decidenext 1]
1405 if {$level == {}} break
1407 set id [lindex $todo $level]
1408 if {![info exists commitlisted($id)]} {
1415 proc finishcommits {} {
1417 global canv mainfont ctext maincursor textcursor
1419 if {$phase != "incrdraw"} {
1421 $canv create text 3 3 -anchor nw -text "No commits selected" \
1422 -font $mainfont -tags textitems
1427 . config -cursor $maincursor
1428 settextcursor $textcursor
1431 # Don't change the text pane cursor if it is currently the hand cursor,
1432 # showing that we are over a sha1 ID link.
1433 proc settextcursor {c} {
1434 global ctext curtextcursor
1436 if {[$ctext cget -cursor] == $curtextcursor} {
1437 $ctext config -cursor $c
1439 set curtextcursor $c
1443 global nextupdate startmsecs ncmupdate
1444 global displayorder onscreen
1446 if {$displayorder == {}} return
1447 set startmsecs [clock clicks -milliseconds]
1448 set nextupdate [expr $startmsecs + 100]
1451 foreach id $displayorder {
1458 global phase stopped redisplaying selectedline
1459 global datemode todo displayorder
1460 global numcommits ncmupdate
1461 global nextupdate startmsecs idline
1463 set level [decidenext]
1467 lappend displayorder [lindex $todo $level]
1468 set hard [updatetodo $level $datemode]
1470 set level [decidenext]
1471 if {$level < 0} break
1477 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1478 #puts "overall $drawmsecs ms for $numcommits commits"
1479 if {$redisplaying} {
1480 if {$stopped == 0 && [info exists selectedline]} {
1481 selectline $selectedline 0
1483 if {$stopped == 1} {
1485 after idle drawgraph
1492 proc findmatches {f} {
1493 global findtype foundstring foundstrlen
1494 if {$findtype == "Regexp"} {
1495 set matches [regexp -indices -all -inline $foundstring $f]
1497 if {$findtype == "IgnCase"} {
1498 set str [string tolower $f]
1504 while {[set j [string first $foundstring $str $i]] >= 0} {
1505 lappend matches [list $j [expr $j+$foundstrlen-1]]
1506 set i [expr $j + $foundstrlen]
1513 global findtype findloc findstring markedmatches commitinfo
1514 global numcommits lineid linehtag linentag linedtag
1515 global mainfont namefont canv canv2 canv3 selectedline
1516 global matchinglines foundstring foundstrlen
1521 set matchinglines {}
1522 if {$findloc == "Pickaxe"} {
1526 if {$findtype == "IgnCase"} {
1527 set foundstring [string tolower $findstring]
1529 set foundstring $findstring
1531 set foundstrlen [string length $findstring]
1532 if {$foundstrlen == 0} return
1533 if {$findloc == "Files"} {
1537 if {![info exists selectedline]} {
1540 set oldsel $selectedline
1543 set fldtypes {Headline Author Date Committer CDate Comment}
1544 for {set l 0} {$l < $numcommits} {incr l} {
1546 set info $commitinfo($id)
1548 foreach f $info ty $fldtypes {
1549 if {$findloc != "All fields" && $findloc != $ty} {
1552 set matches [findmatches $f]
1553 if {$matches == {}} continue
1555 if {$ty == "Headline"} {
1556 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1557 } elseif {$ty == "Author"} {
1558 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1559 } elseif {$ty == "Date"} {
1560 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1564 lappend matchinglines $l
1565 if {!$didsel && $l > $oldsel} {
1571 if {$matchinglines == {}} {
1573 } elseif {!$didsel} {
1574 findselectline [lindex $matchinglines 0]
1578 proc findselectline {l} {
1579 global findloc commentend ctext
1581 if {$findloc == "All fields" || $findloc == "Comments"} {
1582 # highlight the matches in the comments
1583 set f [$ctext get 1.0 $commentend]
1584 set matches [findmatches $f]
1585 foreach match $matches {
1586 set start [lindex $match 0]
1587 set end [expr [lindex $match 1] + 1]
1588 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1593 proc findnext {restart} {
1594 global matchinglines selectedline
1595 if {![info exists matchinglines]} {
1601 if {![info exists selectedline]} return
1602 foreach l $matchinglines {
1603 if {$l > $selectedline} {
1612 global matchinglines selectedline
1613 if {![info exists matchinglines]} {
1617 if {![info exists selectedline]} return
1619 foreach l $matchinglines {
1620 if {$l >= $selectedline} break
1624 findselectline $prev
1630 proc findlocchange {name ix op} {
1631 global findloc findtype findtypemenu
1632 if {$findloc == "Pickaxe"} {
1638 $findtypemenu entryconf 1 -state $state
1639 $findtypemenu entryconf 2 -state $state
1642 proc stopfindproc {{done 0}} {
1643 global findprocpid findprocfile findids
1644 global ctext findoldcursor phase maincursor textcursor
1645 global findinprogress
1647 catch {unset findids}
1648 if {[info exists findprocpid]} {
1650 catch {exec kill $findprocpid}
1652 catch {close $findprocfile}
1655 if {[info exists findinprogress]} {
1656 unset findinprogress
1657 if {$phase != "incrdraw"} {
1658 . config -cursor $maincursor
1659 settextcursor $textcursor
1664 proc findpatches {} {
1665 global findstring selectedline numcommits
1666 global findprocpid findprocfile
1667 global finddidsel ctext lineid findinprogress
1668 global findinsertpos
1670 if {$numcommits == 0} return
1672 # make a list of all the ids to search, starting at the one
1673 # after the selected line (if any)
1674 if {[info exists selectedline]} {
1680 for {set i 0} {$i < $numcommits} {incr i} {
1681 if {[incr l] >= $numcommits} {
1684 append inputids $lineid($l) "\n"
1688 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1691 error_popup "Error starting search process: $err"
1695 set findinsertpos end
1697 set findprocpid [pid $f]
1698 fconfigure $f -blocking 0
1699 fileevent $f readable readfindproc
1701 . config -cursor watch
1703 set findinprogress 1
1706 proc readfindproc {} {
1707 global findprocfile finddidsel
1708 global idline matchinglines findinsertpos
1710 set n [gets $findprocfile line]
1712 if {[eof $findprocfile]} {
1720 if {![regexp {^[0-9a-f]{40}} $line id]} {
1721 error_popup "Can't parse git-diff-tree output: $line"
1725 if {![info exists idline($id)]} {
1726 puts stderr "spurious id: $id"
1733 proc insertmatch {l id} {
1734 global matchinglines findinsertpos finddidsel
1736 if {$findinsertpos == "end"} {
1737 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1738 set matchinglines [linsert $matchinglines 0 $l]
1741 lappend matchinglines $l
1744 set matchinglines [linsert $matchinglines $findinsertpos $l]
1755 global selectedline numcommits lineid ctext
1756 global ffileline finddidsel parents nparents
1757 global findinprogress findstartline findinsertpos
1758 global treediffs fdiffids fdiffsneeded fdiffpos
1759 global findmergefiles
1761 if {$numcommits == 0} return
1763 if {[info exists selectedline]} {
1764 set l [expr {$selectedline + 1}]
1769 set findstartline $l
1774 if {$findmergefiles || $nparents($id) == 1} {
1775 foreach p $parents($id) {
1776 if {![info exists treediffs([list $id $p])]} {
1777 append diffsneeded "$id $p\n"
1778 lappend fdiffsneeded [list $id $p]
1782 if {[incr l] >= $numcommits} {
1785 if {$l == $findstartline} break
1788 # start off a git-diff-tree process if needed
1789 if {$diffsneeded ne {}} {
1791 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1793 error_popup "Error starting search process: $err"
1796 catch {unset fdiffids}
1798 fconfigure $df -blocking 0
1799 fileevent $df readable [list readfilediffs $df]
1803 set findinsertpos end
1805 set p [lindex $parents($id) 0]
1806 . config -cursor watch
1808 set findinprogress 1
1809 findcont [list $id $p]
1813 proc readfilediffs {df} {
1814 global findids fdiffids fdiffs
1816 set n [gets $df line]
1820 if {[catch {close $df} err]} {
1823 error_popup "Error in git-diff-tree: $err"
1824 } elseif {[info exists findids]} {
1828 error_popup "Couldn't find diffs for {$ids}"
1833 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1834 # start of a new string of diffs
1836 set fdiffids [list $id $p]
1838 } elseif {[string match ":*" $line]} {
1839 lappend fdiffs [lindex $line 5]
1843 proc donefilediff {} {
1844 global fdiffids fdiffs treediffs findids
1845 global fdiffsneeded fdiffpos
1847 if {[info exists fdiffids]} {
1848 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1849 && $fdiffpos < [llength $fdiffsneeded]} {
1850 # git-diff-tree doesn't output anything for a commit
1851 # which doesn't change anything
1852 set nullids [lindex $fdiffsneeded $fdiffpos]
1853 set treediffs($nullids) {}
1854 if {[info exists findids] && $nullids eq $findids} {
1862 if {![info exists treediffs($fdiffids)]} {
1863 set treediffs($fdiffids) $fdiffs
1865 if {[info exists findids] && $fdiffids eq $findids} {
1872 proc findcont {ids} {
1873 global findids treediffs parents nparents
1874 global ffileline findstartline finddidsel
1875 global lineid numcommits matchinglines findinprogress
1876 global findmergefiles
1878 set id [lindex $ids 0]
1879 set p [lindex $ids 1]
1880 set pi [lsearch -exact $parents($id) $p]
1883 if {$findmergefiles || $nparents($id) == 1} {
1884 if {![info exists treediffs($ids)]} {
1890 foreach f $treediffs($ids) {
1891 set x [findmatches $f]
1899 set pi $nparents($id)
1902 set pi $nparents($id)
1904 if {[incr pi] >= $nparents($id)} {
1906 if {[incr l] >= $numcommits} {
1909 if {$l == $findstartline} break
1912 set p [lindex $parents($id) $pi]
1913 set ids [list $id $p]
1921 # mark a commit as matching by putting a yellow background
1922 # behind the headline
1923 proc markheadline {l id} {
1924 global canv mainfont linehtag commitinfo
1926 set bbox [$canv bbox $linehtag($l)]
1927 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1931 # mark the bits of a headline, author or date that match a find string
1932 proc markmatches {canv l str tag matches font} {
1933 set bbox [$canv bbox $tag]
1934 set x0 [lindex $bbox 0]
1935 set y0 [lindex $bbox 1]
1936 set y1 [lindex $bbox 3]
1937 foreach match $matches {
1938 set start [lindex $match 0]
1939 set end [lindex $match 1]
1940 if {$start > $end} continue
1941 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1942 set xlen [font measure $font [string range $str 0 [expr $end]]]
1943 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1944 -outline {} -tags matches -fill yellow]
1949 proc unmarkmatches {} {
1950 global matchinglines findids
1951 allcanvs delete matches
1952 catch {unset matchinglines}
1953 catch {unset findids}
1956 proc selcanvline {w x y} {
1957 global canv canvy0 ctext linespc
1958 global lineid linehtag linentag linedtag rowtextx
1959 set ymax [lindex [$canv cget -scrollregion] 3]
1960 if {$ymax == {}} return
1961 set yfrac [lindex [$canv yview] 0]
1962 set y [expr {$y + $yfrac * $ymax}]
1963 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1968 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1974 proc commit_descriptor {p} {
1977 if {[info exists commitinfo($p)]} {
1978 set l [lindex $commitinfo($p) 0]
1983 proc selectline {l isnew} {
1984 global canv canv2 canv3 ctext commitinfo selectedline
1985 global lineid linehtag linentag linedtag
1986 global canvy0 linespc parents nparents children
1987 global cflist currentid sha1entry
1988 global commentend idtags idline
1991 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1993 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1994 -tags secsel -fill [$canv cget -selectbackground]]
1996 $canv2 delete secsel
1997 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1998 -tags secsel -fill [$canv2 cget -selectbackground]]
2000 $canv3 delete secsel
2001 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2002 -tags secsel -fill [$canv3 cget -selectbackground]]
2004 set y [expr {$canvy0 + $l * $linespc}]
2005 set ymax [lindex [$canv cget -scrollregion] 3]
2006 set ytop [expr {$y - $linespc - 1}]
2007 set ybot [expr {$y + $linespc + 1}]
2008 set wnow [$canv yview]
2009 set wtop [expr [lindex $wnow 0] * $ymax]
2010 set wbot [expr [lindex $wnow 1] * $ymax]
2011 set wh [expr {$wbot - $wtop}]
2013 if {$ytop < $wtop} {
2014 if {$ybot < $wtop} {
2015 set newtop [expr {$y - $wh / 2.0}]
2018 if {$newtop > $wtop - $linespc} {
2019 set newtop [expr {$wtop - $linespc}]
2022 } elseif {$ybot > $wbot} {
2023 if {$ytop > $wbot} {
2024 set newtop [expr {$y - $wh / 2.0}]
2026 set newtop [expr {$ybot - $wh}]
2027 if {$newtop < $wtop + $linespc} {
2028 set newtop [expr {$wtop + $linespc}]
2032 if {$newtop != $wtop} {
2036 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2040 addtohistory [list selectline $l 0]
2047 $sha1entry delete 0 end
2048 $sha1entry insert 0 $id
2049 $sha1entry selection from 0
2050 $sha1entry selection to end
2052 $ctext conf -state normal
2053 $ctext delete 0.0 end
2054 $ctext mark set fmark.0 0.0
2055 $ctext mark gravity fmark.0 left
2056 set info $commitinfo($id)
2057 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2058 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2059 if {[info exists idtags($id)]} {
2060 $ctext insert end "Tags:"
2061 foreach tag $idtags($id) {
2062 $ctext insert end " $tag"
2064 $ctext insert end "\n"
2067 set commentstart [$ctext index "end - 1c"]
2069 if {[info exists parents($id)]} {
2070 foreach p $parents($id) {
2071 append comment "Parent: [commit_descriptor $p]\n"
2074 if {[info exists children($id)]} {
2075 foreach c $children($id) {
2076 append comment "Child: [commit_descriptor $c]\n"
2080 append comment [lindex $info 5]
2081 $ctext insert end $comment
2082 $ctext insert end "\n"
2084 # make anything that looks like a SHA1 ID be a clickable link
2085 set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
2090 set linkid [string range $comment $s $e]
2091 if {![info exists idline($linkid)]} continue
2093 $ctext tag add link "$commentstart + $s c" "$commentstart + $e c"
2094 $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
2095 $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
2098 $ctext tag conf link -foreground blue -underline 1
2099 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2100 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2102 $ctext tag delete Comments
2103 $ctext tag remove found 1.0 end
2104 $ctext conf -state disabled
2105 set commentend [$ctext index "end - 1c"]
2107 $cflist delete 0 end
2108 $cflist insert end "Comments"
2109 if {$nparents($id) == 1} {
2110 startdiff [concat $id $parents($id)]
2111 } elseif {$nparents($id) > 1} {
2116 proc selnextline {dir} {
2118 if {![info exists selectedline]} return
2119 set l [expr $selectedline + $dir]
2124 proc unselectline {} {
2127 catch {unset selectedline}
2128 allcanvs delete secsel
2131 proc addtohistory {cmd} {
2132 global history historyindex
2134 if {$historyindex > 0
2135 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2139 if {$historyindex < [llength $history]} {
2140 set history [lreplace $history $historyindex end $cmd]
2142 lappend history $cmd
2145 if {$historyindex > 1} {
2146 .ctop.top.bar.leftbut conf -state normal
2148 .ctop.top.bar.leftbut conf -state disabled
2150 .ctop.top.bar.rightbut conf -state disabled
2154 global history historyindex
2156 if {$historyindex > 1} {
2157 incr historyindex -1
2158 set cmd [lindex $history [expr {$historyindex - 1}]]
2160 .ctop.top.bar.rightbut conf -state normal
2162 if {$historyindex <= 1} {
2163 .ctop.top.bar.leftbut conf -state disabled
2168 global history historyindex
2170 if {$historyindex < [llength $history]} {
2171 set cmd [lindex $history $historyindex]
2174 .ctop.top.bar.leftbut conf -state normal
2176 if {$historyindex >= [llength $history]} {
2177 .ctop.top.bar.rightbut conf -state disabled
2181 proc mergediff {id} {
2182 global parents diffmergeid diffmergegca mergefilelist diffpindex
2186 set diffmergegca [findgca $parents($id)]
2187 if {[info exists mergefilelist($id)]} {
2188 if {$mergefilelist($id) ne {}} {
2196 proc findgca {ids} {
2203 set gca [exec git-merge-base $gca $id]
2212 proc contmergediff {ids} {
2213 global diffmergeid diffpindex parents nparents diffmergegca
2214 global treediffs mergefilelist diffids treepending
2216 # diff the child against each of the parents, and diff
2217 # each of the parents against the GCA.
2219 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2220 set ids [list [lindex $ids 1] $diffmergegca]
2222 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2223 set p [lindex $parents($diffmergeid) $diffpindex]
2224 set ids [list $diffmergeid $p]
2226 if {![info exists treediffs($ids)]} {
2228 if {![info exists treepending]} {
2235 # If a file in some parent is different from the child and also
2236 # different from the GCA, then it's interesting.
2237 # If we don't have a GCA, then a file is interesting if it is
2238 # different from the child in all the parents.
2239 if {$diffmergegca ne {}} {
2241 foreach p $parents($diffmergeid) {
2242 set gcadiffs $treediffs([list $p $diffmergegca])
2243 foreach f $treediffs([list $diffmergeid $p]) {
2244 if {[lsearch -exact $files $f] < 0
2245 && [lsearch -exact $gcadiffs $f] >= 0} {
2250 set files [lsort $files]
2252 set p [lindex $parents($diffmergeid) 0]
2253 set files $treediffs([list $diffmergeid $p])
2254 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2255 set p [lindex $parents($diffmergeid) $i]
2256 set df $treediffs([list $diffmergeid $p])
2259 if {[lsearch -exact $df $f] >= 0} {
2267 set mergefilelist($diffmergeid) $files
2273 proc showmergediff {} {
2274 global cflist diffmergeid mergefilelist parents
2275 global diffopts diffinhunk currentfile currenthunk filelines
2276 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2278 set files $mergefilelist($diffmergeid)
2280 $cflist insert end $f
2282 set env(GIT_DIFF_OPTS) $diffopts
2284 catch {unset currentfile}
2285 catch {unset currenthunk}
2286 catch {unset filelines}
2287 catch {unset groupfilenum}
2288 catch {unset grouphunks}
2289 set groupfilelast -1
2290 foreach p $parents($diffmergeid) {
2291 set cmd [list | git-diff-tree -p $p $diffmergeid]
2292 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2293 if {[catch {set f [open $cmd r]} err]} {
2294 error_popup "Error getting diffs: $err"
2301 set ids [list $diffmergeid $p]
2302 set mergefds($ids) $f
2303 set diffinhunk($ids) 0
2304 set diffblocked($ids) 0
2305 fconfigure $f -blocking 0
2306 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2310 proc getmergediffline {f ids id} {
2311 global diffmergeid diffinhunk diffoldlines diffnewlines
2312 global currentfile currenthunk
2313 global diffoldstart diffnewstart diffoldlno diffnewlno
2314 global diffblocked mergefilelist
2315 global noldlines nnewlines difflcounts filelines
2317 set n [gets $f line]
2319 if {![eof $f]} return
2322 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2329 if {$diffinhunk($ids) != 0} {
2330 set fi $currentfile($ids)
2331 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2332 # continuing an existing hunk
2333 set line [string range $line 1 end]
2334 set p [lindex $ids 1]
2335 if {$match eq "-" || $match eq " "} {
2336 set filelines($p,$fi,$diffoldlno($ids)) $line
2337 incr diffoldlno($ids)
2339 if {$match eq "+" || $match eq " "} {
2340 set filelines($id,$fi,$diffnewlno($ids)) $line
2341 incr diffnewlno($ids)
2343 if {$match eq " "} {
2344 if {$diffinhunk($ids) == 2} {
2345 lappend difflcounts($ids) \
2346 [list $noldlines($ids) $nnewlines($ids)]
2347 set noldlines($ids) 0
2348 set diffinhunk($ids) 1
2350 incr noldlines($ids)
2351 } elseif {$match eq "-" || $match eq "+"} {
2352 if {$diffinhunk($ids) == 1} {
2353 lappend difflcounts($ids) [list $noldlines($ids)]
2354 set noldlines($ids) 0
2355 set nnewlines($ids) 0
2356 set diffinhunk($ids) 2
2358 if {$match eq "-"} {
2359 incr noldlines($ids)
2361 incr nnewlines($ids)
2364 # and if it's \ No newline at end of line, then what?
2368 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2369 lappend difflcounts($ids) [list $noldlines($ids)]
2370 } elseif {$diffinhunk($ids) == 2
2371 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2372 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2374 set currenthunk($ids) [list $currentfile($ids) \
2375 $diffoldstart($ids) $diffnewstart($ids) \
2376 $diffoldlno($ids) $diffnewlno($ids) \
2378 set diffinhunk($ids) 0
2379 # -1 = need to block, 0 = unblocked, 1 = is blocked
2380 set diffblocked($ids) -1
2382 if {$diffblocked($ids) == -1} {
2383 fileevent $f readable {}
2384 set diffblocked($ids) 1
2390 if {!$diffblocked($ids)} {
2392 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2393 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2396 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2397 # start of a new file
2398 set currentfile($ids) \
2399 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2400 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2401 $line match f1l f1c f2l f2c rest]} {
2402 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2403 # start of a new hunk
2404 if {$f1l == 0 && $f1c == 0} {
2407 if {$f2l == 0 && $f2c == 0} {
2410 set diffinhunk($ids) 1
2411 set diffoldstart($ids) $f1l
2412 set diffnewstart($ids) $f2l
2413 set diffoldlno($ids) $f1l
2414 set diffnewlno($ids) $f2l
2415 set difflcounts($ids) {}
2416 set noldlines($ids) 0
2417 set nnewlines($ids) 0
2422 proc processhunks {} {
2423 global diffmergeid parents nparents currenthunk
2424 global mergefilelist diffblocked mergefds
2425 global grouphunks grouplinestart grouplineend groupfilenum
2427 set nfiles [llength $mergefilelist($diffmergeid)]
2431 # look for the earliest hunk
2432 foreach p $parents($diffmergeid) {
2433 set ids [list $diffmergeid $p]
2434 if {![info exists currenthunk($ids)]} return
2435 set i [lindex $currenthunk($ids) 0]
2436 set l [lindex $currenthunk($ids) 2]
2437 if {$i < $fi || ($i == $fi && $l < $lno)} {
2444 if {$fi < $nfiles} {
2445 set ids [list $diffmergeid $pi]
2446 set hunk $currenthunk($ids)
2447 unset currenthunk($ids)
2448 if {$diffblocked($ids) > 0} {
2449 fileevent $mergefds($ids) readable \
2450 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2452 set diffblocked($ids) 0
2454 if {[info exists groupfilenum] && $groupfilenum == $fi
2455 && $lno <= $grouplineend} {
2456 # add this hunk to the pending group
2457 lappend grouphunks($pi) $hunk
2458 set endln [lindex $hunk 4]
2459 if {$endln > $grouplineend} {
2460 set grouplineend $endln
2466 # succeeding stuff doesn't belong in this group, so
2467 # process the group now
2468 if {[info exists groupfilenum]} {
2474 if {$fi >= $nfiles} break
2477 set groupfilenum $fi
2478 set grouphunks($pi) [list $hunk]
2479 set grouplinestart $lno
2480 set grouplineend [lindex $hunk 4]
2484 proc processgroup {} {
2485 global groupfilelast groupfilenum difffilestart
2486 global mergefilelist diffmergeid ctext filelines
2487 global parents diffmergeid diffoffset
2488 global grouphunks grouplinestart grouplineend nparents
2491 $ctext conf -state normal
2494 if {$groupfilelast != $f} {
2495 $ctext insert end "\n"
2496 set here [$ctext index "end - 1c"]
2497 set difffilestart($f) $here
2498 set mark fmark.[expr {$f + 1}]
2499 $ctext mark set $mark $here
2500 $ctext mark gravity $mark left
2501 set header [lindex $mergefilelist($id) $f]
2502 set l [expr {(78 - [string length $header]) / 2}]
2503 set pad [string range "----------------------------------------" 1 $l]
2504 $ctext insert end "$pad $header $pad\n" filesep
2505 set groupfilelast $f
2506 foreach p $parents($id) {
2507 set diffoffset($p) 0
2511 $ctext insert end "@@" msep
2512 set nlines [expr {$grouplineend - $grouplinestart}]
2515 foreach p $parents($id) {
2516 set startline [expr {$grouplinestart + $diffoffset($p)}]
2518 set nl $grouplinestart
2519 if {[info exists grouphunks($p)]} {
2520 foreach h $grouphunks($p) {
2523 for {} {$nl < $l} {incr nl} {
2524 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2528 foreach chunk [lindex $h 5] {
2529 if {[llength $chunk] == 2} {
2530 set olc [lindex $chunk 0]
2531 set nlc [lindex $chunk 1]
2532 set nnl [expr {$nl + $nlc}]
2533 lappend events [list $nl $nnl $pnum $olc $nlc]
2537 incr ol [lindex $chunk 0]
2538 incr nl [lindex $chunk 0]
2543 if {$nl < $grouplineend} {
2544 for {} {$nl < $grouplineend} {incr nl} {
2545 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2549 set nlines [expr {$ol - $startline}]
2550 $ctext insert end " -$startline,$nlines" msep
2554 set nlines [expr {$grouplineend - $grouplinestart}]
2555 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2557 set events [lsort -integer -index 0 $events]
2558 set nevents [llength $events]
2559 set nmerge $nparents($diffmergeid)
2560 set l $grouplinestart
2561 for {set i 0} {$i < $nevents} {set i $j} {
2562 set nl [lindex $events $i 0]
2564 $ctext insert end " $filelines($id,$f,$l)\n"
2567 set e [lindex $events $i]
2568 set enl [lindex $e 1]
2572 set pnum [lindex $e 2]
2573 set olc [lindex $e 3]
2574 set nlc [lindex $e 4]
2575 if {![info exists delta($pnum)]} {
2576 set delta($pnum) [expr {$olc - $nlc}]
2577 lappend active $pnum
2579 incr delta($pnum) [expr {$olc - $nlc}]
2581 if {[incr j] >= $nevents} break
2582 set e [lindex $events $j]
2583 if {[lindex $e 0] >= $enl} break
2584 if {[lindex $e 1] > $enl} {
2585 set enl [lindex $e 1]
2588 set nlc [expr {$enl - $l}]
2591 if {[llength $active] == $nmerge - 1} {
2592 # no diff for one of the parents, i.e. it's identical
2593 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2594 if {![info exists delta($pnum)]} {
2595 if {$pnum < $mergemax} {
2603 } elseif {[llength $active] == $nmerge} {
2604 # all parents are different, see if one is very similar
2606 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2607 set sim [similarity $pnum $l $nlc $f \
2608 [lrange $events $i [expr {$j-1}]]]
2609 if {$sim > $bestsim} {
2615 lappend ncol m$bestpn
2619 foreach p $parents($id) {
2621 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2622 set olc [expr {$nlc + $delta($pnum)}]
2623 set ol [expr {$l + $diffoffset($p)}]
2624 incr diffoffset($p) $delta($pnum)
2626 for {} {$olc > 0} {incr olc -1} {
2627 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2631 set endl [expr {$l + $nlc}]
2633 # show this pretty much as a normal diff
2634 set p [lindex $parents($id) $bestpn]
2635 set ol [expr {$l + $diffoffset($p)}]
2636 incr diffoffset($p) $delta($bestpn)
2637 unset delta($bestpn)
2638 for {set k $i} {$k < $j} {incr k} {
2639 set e [lindex $events $k]
2640 if {[lindex $e 2] != $bestpn} continue
2641 set nl [lindex $e 0]
2642 set ol [expr {$ol + $nl - $l}]
2643 for {} {$l < $nl} {incr l} {
2644 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2647 for {} {$c > 0} {incr c -1} {
2648 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2651 set nl [lindex $e 1]
2652 for {} {$l < $nl} {incr l} {
2653 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2657 for {} {$l < $endl} {incr l} {
2658 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2661 while {$l < $grouplineend} {
2662 $ctext insert end " $filelines($id,$f,$l)\n"
2665 $ctext conf -state disabled
2668 proc similarity {pnum l nlc f events} {
2669 global diffmergeid parents diffoffset filelines
2672 set p [lindex $parents($id) $pnum]
2673 set ol [expr {$l + $diffoffset($p)}]
2674 set endl [expr {$l + $nlc}]
2678 if {[lindex $e 2] != $pnum} continue
2679 set nl [lindex $e 0]
2680 set ol [expr {$ol + $nl - $l}]
2681 for {} {$l < $nl} {incr l} {
2682 incr same [string length $filelines($id,$f,$l)]
2685 set oc [lindex $e 3]
2686 for {} {$oc > 0} {incr oc -1} {
2687 incr diff [string length $filelines($p,$f,$ol)]
2691 set nl [lindex $e 1]
2692 for {} {$l < $nl} {incr l} {
2693 incr diff [string length $filelines($id,$f,$l)]
2697 for {} {$l < $endl} {incr l} {
2698 incr same [string length $filelines($id,$f,$l)]
2704 return [expr {200 * $same / (2 * $same + $diff)}]
2707 proc startdiff {ids} {
2708 global treediffs diffids treepending diffmergeid
2711 catch {unset diffmergeid}
2712 if {![info exists treediffs($ids)]} {
2713 if {![info exists treepending]} {
2721 proc addtocflist {ids} {
2722 global treediffs cflist
2723 foreach f $treediffs($ids) {
2724 $cflist insert end $f
2729 proc gettreediffs {ids} {
2730 global treediff parents treepending
2731 set treepending $ids
2733 set id [lindex $ids 0]
2734 set p [lindex $ids 1]
2735 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2736 fconfigure $gdtf -blocking 0
2737 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2740 proc gettreediffline {gdtf ids} {
2741 global treediff treediffs treepending diffids diffmergeid
2743 set n [gets $gdtf line]
2745 if {![eof $gdtf]} return
2747 set treediffs($ids) $treediff
2749 if {$ids != $diffids} {
2750 gettreediffs $diffids
2752 if {[info exists diffmergeid]} {
2760 set file [lindex $line 5]
2761 lappend treediff $file
2764 proc getblobdiffs {ids} {
2765 global diffopts blobdifffd diffids env curdifftag curtagstart
2766 global difffilestart nextupdate diffinhdr treediffs
2768 set id [lindex $ids 0]
2769 set p [lindex $ids 1]
2770 set env(GIT_DIFF_OPTS) $diffopts
2771 set cmd [list | git-diff-tree -r -p -C $p $id]
2772 if {[catch {set bdf [open $cmd r]} err]} {
2773 puts "error getting diffs: $err"
2777 fconfigure $bdf -blocking 0
2778 set blobdifffd($ids) $bdf
2779 set curdifftag Comments
2781 catch {unset difffilestart}
2782 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2783 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2786 proc getblobdiffline {bdf ids} {
2787 global diffids blobdifffd ctext curdifftag curtagstart
2788 global diffnexthead diffnextnote difffilestart
2789 global nextupdate diffinhdr treediffs
2792 set n [gets $bdf line]
2796 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2797 $ctext tag add $curdifftag $curtagstart end
2802 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2805 $ctext conf -state normal
2806 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2807 # start of a new file
2808 $ctext insert end "\n"
2809 $ctext tag add $curdifftag $curtagstart end
2810 set curtagstart [$ctext index "end - 1c"]
2812 set here [$ctext index "end - 1c"]
2813 set i [lsearch -exact $treediffs($diffids) $fname]
2815 set difffilestart($i) $here
2817 $ctext mark set fmark.$i $here
2818 $ctext mark gravity fmark.$i left
2820 if {$newname != $fname} {
2821 set i [lsearch -exact $treediffs($diffids) $newname]
2823 set difffilestart($i) $here
2825 $ctext mark set fmark.$i $here
2826 $ctext mark gravity fmark.$i left
2829 set curdifftag "f:$fname"
2830 $ctext tag delete $curdifftag
2831 set l [expr {(78 - [string length $header]) / 2}]
2832 set pad [string range "----------------------------------------" 1 $l]
2833 $ctext insert end "$pad $header $pad\n" filesep
2835 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2837 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2838 $line match f1l f1c f2l f2c rest]} {
2840 $ctext insert end "\t" hunksep
2841 $ctext insert end " $f1l " d0 " $f2l " d1
2842 $ctext insert end " $rest \n" hunksep
2844 $ctext insert end "$line\n" hunksep
2848 set x [string range $line 0 0]
2849 if {$x == "-" || $x == "+"} {
2850 set tag [expr {$x == "+"}]
2852 set line [string range $line 1 end]
2854 $ctext insert end "$line\n" d$tag
2855 } elseif {$x == " "} {
2857 set line [string range $line 1 end]
2859 $ctext insert end "$line\n"
2860 } elseif {$diffinhdr || $x == "\\"} {
2861 # e.g. "\ No newline at end of file"
2862 $ctext insert end "$line\n" filesep
2864 # Something else we don't recognize
2865 if {$curdifftag != "Comments"} {
2866 $ctext insert end "\n"
2867 $ctext tag add $curdifftag $curtagstart end
2868 set curtagstart [$ctext index "end - 1c"]
2869 set curdifftag Comments
2871 $ctext insert end "$line\n" filesep
2874 $ctext conf -state disabled
2875 if {[clock clicks -milliseconds] >= $nextupdate} {
2877 fileevent $bdf readable {}
2879 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2884 global difffilestart ctext
2885 set here [$ctext index @0,0]
2886 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2887 if {[$ctext compare $difffilestart($i) > $here]} {
2888 if {![info exists pos]
2889 || [$ctext compare $difffilestart($i) < $pos]} {
2890 set pos $difffilestart($i)
2894 if {[info exists pos]} {
2899 proc listboxsel {} {
2900 global ctext cflist currentid
2901 if {![info exists currentid]} return
2902 set sel [lsort [$cflist curselection]]
2903 if {$sel eq {}} return
2904 set first [lindex $sel 0]
2905 catch {$ctext yview fmark.$first}
2909 global linespc charspc canvx0 canvy0 mainfont
2910 global xspc1 xspc2 lthickness
2912 set linespc [font metrics $mainfont -linespace]
2913 set charspc [font measure $mainfont "m"]
2914 set canvy0 [expr 3 + 0.5 * $linespc]
2915 set canvx0 [expr 3 + 0.5 * $linespc]
2916 set lthickness [expr {int($linespc / 9) + 1}]
2917 set xspc1(0) $linespc
2922 global stopped redisplaying phase
2923 if {$stopped > 1} return
2924 if {$phase == "getcommits"} return
2926 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2933 proc incrfont {inc} {
2934 global mainfont namefont textfont ctext canv phase
2935 global stopped entries
2937 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2938 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2939 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2941 $ctext conf -font $textfont
2942 $ctext tag conf filesep -font [concat $textfont bold]
2943 foreach e $entries {
2944 $e conf -font $mainfont
2946 if {$phase == "getcommits"} {
2947 $canv itemconf textitems -font $mainfont
2953 global sha1entry sha1string
2954 if {[string length $sha1string] == 40} {
2955 $sha1entry delete 0 end
2959 proc sha1change {n1 n2 op} {
2960 global sha1string currentid sha1but
2961 if {$sha1string == {}
2962 || ([info exists currentid] && $sha1string == $currentid)} {
2967 if {[$sha1but cget -state] == $state} return
2968 if {$state == "normal"} {
2969 $sha1but conf -state normal -relief raised -text "Goto: "
2971 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2975 proc gotocommit {} {
2976 global sha1string currentid idline tagids
2977 global lineid numcommits
2979 if {$sha1string == {}
2980 || ([info exists currentid] && $sha1string == $currentid)} return
2981 if {[info exists tagids($sha1string)]} {
2982 set id $tagids($sha1string)
2984 set id [string tolower $sha1string]
2985 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2987 for {set l 0} {$l < $numcommits} {incr l} {
2988 if {[string match $id* $lineid($l)]} {
2989 lappend matches $lineid($l)
2992 if {$matches ne {}} {
2993 if {[llength $matches] > 1} {
2994 error_popup "Short SHA1 id $id is ambiguous"
2997 set id [lindex $matches 0]
3001 if {[info exists idline($id)]} {
3002 selectline $idline($id) 1
3005 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3010 error_popup "$type $sha1string is not known"
3013 proc lineenter {x y id} {
3014 global hoverx hovery hoverid hovertimer
3015 global commitinfo canv
3017 if {![info exists commitinfo($id)]} return
3021 if {[info exists hovertimer]} {
3022 after cancel $hovertimer
3024 set hovertimer [after 500 linehover]
3028 proc linemotion {x y id} {
3029 global hoverx hovery hoverid hovertimer
3031 if {[info exists hoverid] && $id == $hoverid} {
3034 if {[info exists hovertimer]} {
3035 after cancel $hovertimer
3037 set hovertimer [after 500 linehover]
3041 proc lineleave {id} {
3042 global hoverid hovertimer canv
3044 if {[info exists hoverid] && $id == $hoverid} {
3046 if {[info exists hovertimer]} {
3047 after cancel $hovertimer
3055 global hoverx hovery hoverid hovertimer
3056 global canv linespc lthickness
3057 global commitinfo mainfont
3059 set text [lindex $commitinfo($hoverid) 0]
3060 set ymax [lindex [$canv cget -scrollregion] 3]
3061 if {$ymax == {}} return
3062 set yfrac [lindex [$canv yview] 0]
3063 set x [expr {$hoverx + 2 * $linespc}]
3064 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3065 set x0 [expr {$x - 2 * $lthickness}]
3066 set y0 [expr {$y - 2 * $lthickness}]
3067 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3068 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3069 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3070 -fill \#ffff80 -outline black -width 1 -tags hover]
3072 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3076 proc lineclick {x y id isnew} {
3077 global ctext commitinfo children cflist canv
3082 addtohistory [list lineclick $x $x $id 0]
3085 # fill the details pane with info about this line
3086 $ctext conf -state normal
3087 $ctext delete 0.0 end
3088 $ctext tag conf link -foreground blue -underline 1
3089 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3090 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3091 $ctext insert end "Parent:\t"
3092 $ctext insert end $id [list link link0]
3093 $ctext tag bind link0 <1> [list selbyid $id]
3094 set info $commitinfo($id)
3095 $ctext insert end "\n\t[lindex $info 0]\n"
3096 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3097 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3098 if {[info exists children($id)]} {
3099 $ctext insert end "\nChildren:"
3101 foreach child $children($id) {
3103 set info $commitinfo($child)
3104 $ctext insert end "\n\t"
3105 $ctext insert end $child [list link link$i]
3106 $ctext tag bind link$i <1> [list selbyid $child]
3107 $ctext insert end "\n\t[lindex $info 0]"
3108 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3109 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3112 $ctext conf -state disabled
3114 $cflist delete 0 end
3119 if {[info exists idline($id)]} {
3120 selectline $idline($id) 1
3126 if {![info exists startmstime]} {
3127 set startmstime [clock clicks -milliseconds]
3129 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3132 proc rowmenu {x y id} {
3133 global rowctxmenu idline selectedline rowmenuid
3135 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3140 $rowctxmenu entryconfigure 0 -state $state
3141 $rowctxmenu entryconfigure 1 -state $state
3142 $rowctxmenu entryconfigure 2 -state $state
3144 tk_popup $rowctxmenu $x $y
3147 proc diffvssel {dirn} {
3148 global rowmenuid selectedline lineid
3150 if {![info exists selectedline]} return
3152 set oldid $lineid($selectedline)
3153 set newid $rowmenuid
3155 set oldid $rowmenuid
3156 set newid $lineid($selectedline)
3158 addtohistory [list doseldiff $oldid $newid]
3159 doseldiff $oldid $newid
3162 proc doseldiff {oldid newid} {
3166 $ctext conf -state normal
3167 $ctext delete 0.0 end
3168 $ctext mark set fmark.0 0.0
3169 $ctext mark gravity fmark.0 left
3170 $cflist delete 0 end
3171 $cflist insert end "Top"
3172 $ctext insert end "From "
3173 $ctext tag conf link -foreground blue -underline 1
3174 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3175 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3176 $ctext tag bind link0 <1> [list selbyid $oldid]
3177 $ctext insert end $oldid [list link link0]
3178 $ctext insert end "\n "
3179 $ctext insert end [lindex $commitinfo($oldid) 0]
3180 $ctext insert end "\n\nTo "
3181 $ctext tag bind link1 <1> [list selbyid $newid]
3182 $ctext insert end $newid [list link link1]
3183 $ctext insert end "\n "
3184 $ctext insert end [lindex $commitinfo($newid) 0]
3185 $ctext insert end "\n"
3186 $ctext conf -state disabled
3187 $ctext tag delete Comments
3188 $ctext tag remove found 1.0 end
3189 startdiff [list $newid $oldid]
3193 global rowmenuid currentid commitinfo patchtop patchnum
3195 if {![info exists currentid]} return
3196 set oldid $currentid
3197 set oldhead [lindex $commitinfo($oldid) 0]
3198 set newid $rowmenuid
3199 set newhead [lindex $commitinfo($newid) 0]
3202 catch {destroy $top}
3204 label $top.title -text "Generate patch"
3205 grid $top.title - -pady 10
3206 label $top.from -text "From:"
3207 entry $top.fromsha1 -width 40 -relief flat
3208 $top.fromsha1 insert 0 $oldid
3209 $top.fromsha1 conf -state readonly
3210 grid $top.from $top.fromsha1 -sticky w
3211 entry $top.fromhead -width 60 -relief flat
3212 $top.fromhead insert 0 $oldhead
3213 $top.fromhead conf -state readonly
3214 grid x $top.fromhead -sticky w
3215 label $top.to -text "To:"
3216 entry $top.tosha1 -width 40 -relief flat
3217 $top.tosha1 insert 0 $newid
3218 $top.tosha1 conf -state readonly
3219 grid $top.to $top.tosha1 -sticky w
3220 entry $top.tohead -width 60 -relief flat
3221 $top.tohead insert 0 $newhead
3222 $top.tohead conf -state readonly
3223 grid x $top.tohead -sticky w
3224 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3225 grid $top.rev x -pady 10
3226 label $top.flab -text "Output file:"
3227 entry $top.fname -width 60
3228 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3230 grid $top.flab $top.fname -sticky w
3232 button $top.buts.gen -text "Generate" -command mkpatchgo
3233 button $top.buts.can -text "Cancel" -command mkpatchcan
3234 grid $top.buts.gen $top.buts.can
3235 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3236 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3237 grid $top.buts - -pady 10 -sticky ew
3241 proc mkpatchrev {} {
3244 set oldid [$patchtop.fromsha1 get]
3245 set oldhead [$patchtop.fromhead get]
3246 set newid [$patchtop.tosha1 get]
3247 set newhead [$patchtop.tohead get]
3248 foreach e [list fromsha1 fromhead tosha1 tohead] \
3249 v [list $newid $newhead $oldid $oldhead] {
3250 $patchtop.$e conf -state normal
3251 $patchtop.$e delete 0 end
3252 $patchtop.$e insert 0 $v
3253 $patchtop.$e conf -state readonly
3260 set oldid [$patchtop.fromsha1 get]
3261 set newid [$patchtop.tosha1 get]
3262 set fname [$patchtop.fname get]
3263 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3264 error_popup "Error creating patch: $err"
3266 catch {destroy $patchtop}
3270 proc mkpatchcan {} {
3273 catch {destroy $patchtop}
3278 global rowmenuid mktagtop commitinfo
3282 catch {destroy $top}
3284 label $top.title -text "Create tag"
3285 grid $top.title - -pady 10
3286 label $top.id -text "ID:"
3287 entry $top.sha1 -width 40 -relief flat
3288 $top.sha1 insert 0 $rowmenuid
3289 $top.sha1 conf -state readonly
3290 grid $top.id $top.sha1 -sticky w
3291 entry $top.head -width 60 -relief flat
3292 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3293 $top.head conf -state readonly
3294 grid x $top.head -sticky w
3295 label $top.tlab -text "Tag name:"
3296 entry $top.tag -width 60
3297 grid $top.tlab $top.tag -sticky w
3299 button $top.buts.gen -text "Create" -command mktaggo
3300 button $top.buts.can -text "Cancel" -command mktagcan
3301 grid $top.buts.gen $top.buts.can
3302 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3303 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3304 grid $top.buts - -pady 10 -sticky ew
3309 global mktagtop env tagids idtags
3310 global idpos idline linehtag canv selectedline
3312 set id [$mktagtop.sha1 get]
3313 set tag [$mktagtop.tag get]
3315 error_popup "No tag name specified"
3318 if {[info exists tagids($tag)]} {
3319 error_popup "Tag \"$tag\" already exists"
3324 set fname [file join $dir "refs/tags" $tag]
3325 set f [open $fname w]
3329 error_popup "Error creating tag: $err"
3333 set tagids($tag) $id
3334 lappend idtags($id) $tag
3335 $canv delete tag.$id
3336 set xt [eval drawtags $id $idpos($id)]
3337 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3338 if {[info exists selectedline] && $selectedline == $idline($id)} {
3339 selectline $selectedline 0
3346 catch {destroy $mktagtop}
3355 proc writecommit {} {
3356 global rowmenuid wrcomtop commitinfo wrcomcmd
3358 set top .writecommit
3360 catch {destroy $top}
3362 label $top.title -text "Write commit to file"
3363 grid $top.title - -pady 10
3364 label $top.id -text "ID:"
3365 entry $top.sha1 -width 40 -relief flat
3366 $top.sha1 insert 0 $rowmenuid
3367 $top.sha1 conf -state readonly
3368 grid $top.id $top.sha1 -sticky w
3369 entry $top.head -width 60 -relief flat
3370 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3371 $top.head conf -state readonly
3372 grid x $top.head -sticky w
3373 label $top.clab -text "Command:"
3374 entry $top.cmd -width 60 -textvariable wrcomcmd
3375 grid $top.clab $top.cmd -sticky w -pady 10
3376 label $top.flab -text "Output file:"
3377 entry $top.fname -width 60
3378 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3379 grid $top.flab $top.fname -sticky w
3381 button $top.buts.gen -text "Write" -command wrcomgo
3382 button $top.buts.can -text "Cancel" -command wrcomcan
3383 grid $top.buts.gen $top.buts.can
3384 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3385 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3386 grid $top.buts - -pady 10 -sticky ew
3393 set id [$wrcomtop.sha1 get]
3394 set cmd "echo $id | [$wrcomtop.cmd get]"
3395 set fname [$wrcomtop.fname get]
3396 if {[catch {exec sh -c $cmd >$fname &} err]} {
3397 error_popup "Error writing commit: $err"
3399 catch {destroy $wrcomtop}
3406 catch {destroy $wrcomtop}
3419 set diffopts "-U 5 -p"
3420 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3422 set mainfont {Helvetica 9}
3423 set textfont {Courier 9}
3424 set findmergefiles 0
3429 set colors {green red blue magenta darkgrey brown orange}
3431 catch {source ~/.gitk}
3433 set namefont $mainfont
3435 lappend namefont bold
3440 switch -regexp -- $arg {
3442 "^-b" { set boldnames 1 }
3443 "^-d" { set datemode 1 }
3445 lappend revtreeargs $arg
3460 getcommits $revtreeargs