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 tagcontents
243 set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
248 if {[regexp {^[0-9a-f]{40}} $line id]} {
249 set direct [file tail $f]
250 set tagids($direct) $id
251 lappend idtags($id) $direct
252 set tagblob [exec git-cat-file tag $id]
253 set contents [split $tagblob "\n"]
257 foreach l $contents {
259 switch -- [lindex $l 0] {
260 "object" {set obj [lindex $l 1]}
261 "type" {set type [lindex $l 1]}
262 "tag" {set tag [string range $l 4 end]}
265 if {$obj != {} && $type == "commit" && $tag != {}} {
266 set tagids($tag) $obj
267 lappend idtags($obj) $tag
268 set tagcontents($tag) $tagblob
274 set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
278 set line [read $fd 40]
279 if {[regexp {^[0-9a-f]{40}} $line id]} {
280 set head [file tail $f]
281 set headids($head) $line
282 lappend idheads($line) $head
287 readotherrefs refs {} {tags heads}
290 proc readotherrefs {base dname excl} {
291 global otherrefids idotherrefs
294 set files [glob -nocomplain -types f [file join $git $base *]]
298 set line [read $fd 40]
299 if {[regexp {^[0-9a-f]{40}} $line id]} {
300 set name "$dname[file tail $f]"
301 set otherrefids($name) $id
302 lappend idotherrefs($id) $name
307 set dirs [glob -nocomplain -types d [file join $git $base *]]
309 set dir [file tail $d]
310 if {[lsearch -exact $excl $dir] >= 0} continue
311 readotherrefs [file join $base $dir] "$dname$dir/" {}
315 proc error_popup msg {
319 message $w.m -text $msg -justify center -aspect 400
320 pack $w.m -side top -fill x -padx 20 -pady 20
321 button $w.ok -text OK -command "destroy $w"
322 pack $w.ok -side bottom -fill x
323 bind $w <Visibility> "grab $w; focus $w"
328 global canv canv2 canv3 linespc charspc ctext cflist textfont
329 global findtype findtypemenu findloc findstring fstring geometry
330 global entries sha1entry sha1string sha1but
331 global maincursor textcursor curtextcursor
332 global rowctxmenu gaudydiff mergemax
335 .bar add cascade -label "File" -menu .bar.file
337 .bar.file add command -label "Reread references" -command rereadrefs
338 .bar.file add command -label "Quit" -command doquit
340 .bar add cascade -label "Help" -menu .bar.help
341 .bar.help add command -label "About gitk" -command about
342 . configure -menu .bar
344 if {![info exists geometry(canv1)]} {
345 set geometry(canv1) [expr 45 * $charspc]
346 set geometry(canv2) [expr 30 * $charspc]
347 set geometry(canv3) [expr 15 * $charspc]
348 set geometry(canvh) [expr 25 * $linespc + 4]
349 set geometry(ctextw) 80
350 set geometry(ctexth) 30
351 set geometry(cflistw) 30
353 panedwindow .ctop -orient vertical
354 if {[info exists geometry(width)]} {
355 .ctop conf -width $geometry(width) -height $geometry(height)
356 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
357 set geometry(ctexth) [expr {($texth - 8) /
358 [font metrics $textfont -linespace]}]
362 pack .ctop.top.bar -side bottom -fill x
363 set cscroll .ctop.top.csb
364 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
365 pack $cscroll -side right -fill y
366 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
367 pack .ctop.top.clist -side top -fill both -expand 1
369 set canv .ctop.top.clist.canv
370 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
372 -yscrollincr $linespc -yscrollcommand "$cscroll set"
373 .ctop.top.clist add $canv
374 set canv2 .ctop.top.clist.canv2
375 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
376 -bg white -bd 0 -yscrollincr $linespc
377 .ctop.top.clist add $canv2
378 set canv3 .ctop.top.clist.canv3
379 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
380 -bg white -bd 0 -yscrollincr $linespc
381 .ctop.top.clist add $canv3
382 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
384 set sha1entry .ctop.top.bar.sha1
385 set entries $sha1entry
386 set sha1but .ctop.top.bar.sha1label
387 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
388 -command gotocommit -width 8
389 $sha1but conf -disabledforeground [$sha1but cget -foreground]
390 pack .ctop.top.bar.sha1label -side left
391 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
392 trace add variable sha1string write sha1change
393 pack $sha1entry -side left -pady 2
395 image create bitmap bm-left -data {
396 #define left_width 16
397 #define left_height 16
398 static unsigned char left_bits[] = {
399 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
400 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
401 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
403 image create bitmap bm-right -data {
404 #define right_width 16
405 #define right_height 16
406 static unsigned char right_bits[] = {
407 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
408 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
409 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
411 button .ctop.top.bar.leftbut -image bm-left -command goback \
412 -state disabled -width 26
413 pack .ctop.top.bar.leftbut -side left -fill y
414 button .ctop.top.bar.rightbut -image bm-right -command goforw \
415 -state disabled -width 26
416 pack .ctop.top.bar.rightbut -side left -fill y
418 button .ctop.top.bar.findbut -text "Find" -command dofind
419 pack .ctop.top.bar.findbut -side left
421 set fstring .ctop.top.bar.findstring
422 lappend entries $fstring
423 entry $fstring -width 30 -font $textfont -textvariable findstring
424 pack $fstring -side left -expand 1 -fill x
426 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
427 findtype Exact IgnCase Regexp]
428 set findloc "All fields"
429 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
430 Comments Author Committer Files Pickaxe
431 pack .ctop.top.bar.findloc -side right
432 pack .ctop.top.bar.findtype -side right
433 # for making sure type==Exact whenever loc==Pickaxe
434 trace add variable findloc write findlocchange
436 panedwindow .ctop.cdet -orient horizontal
438 frame .ctop.cdet.left
439 set ctext .ctop.cdet.left.ctext
440 text $ctext -bg white -state disabled -font $textfont \
441 -width $geometry(ctextw) -height $geometry(ctexth) \
442 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
443 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
444 pack .ctop.cdet.left.sb -side right -fill y
445 pack $ctext -side left -fill both -expand 1
446 .ctop.cdet add .ctop.cdet.left
448 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
450 $ctext tag conf hunksep -back blue -fore white
451 $ctext tag conf d0 -back "#ff8080"
452 $ctext tag conf d1 -back green
454 $ctext tag conf hunksep -fore blue
455 $ctext tag conf d0 -fore red
456 $ctext tag conf d1 -fore "#00a000"
457 $ctext tag conf m0 -fore red
458 $ctext tag conf m1 -fore blue
459 $ctext tag conf m2 -fore green
460 $ctext tag conf m3 -fore purple
461 $ctext tag conf m4 -fore brown
462 $ctext tag conf mmax -fore darkgrey
464 $ctext tag conf mresult -font [concat $textfont bold]
465 $ctext tag conf msep -font [concat $textfont bold]
466 $ctext tag conf found -back yellow
469 frame .ctop.cdet.right
470 set cflist .ctop.cdet.right.cfiles
471 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
472 -yscrollcommand ".ctop.cdet.right.sb set"
473 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
474 pack .ctop.cdet.right.sb -side right -fill y
475 pack $cflist -side left -fill both -expand 1
476 .ctop.cdet add .ctop.cdet.right
477 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
479 pack .ctop -side top -fill both -expand 1
481 bindall <1> {selcanvline %W %x %y}
482 #bindall <B1-Motion> {selcanvline %W %x %y}
483 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
484 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
485 bindall <2> "allcanvs scan mark 0 %y"
486 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
487 bind . <Key-Up> "selnextline -1"
488 bind . <Key-Down> "selnextline 1"
489 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
490 bind . <Key-Next> "allcanvs yview scroll 1 pages"
491 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
492 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
493 bindkey <Key-space> "$ctext yview scroll 1 pages"
494 bindkey p "selnextline -1"
495 bindkey n "selnextline 1"
496 bindkey b "$ctext yview scroll -1 pages"
497 bindkey d "$ctext yview scroll 18 units"
498 bindkey u "$ctext yview scroll -18 units"
499 bindkey / {findnext 1}
500 bindkey <Key-Return> {findnext 0}
503 bind . <Control-q> doquit
504 bind . <Control-f> dofind
505 bind . <Control-g> {findnext 0}
506 bind . <Control-r> findprev
507 bind . <Control-equal> {incrfont 1}
508 bind . <Control-KP_Add> {incrfont 1}
509 bind . <Control-minus> {incrfont -1}
510 bind . <Control-KP_Subtract> {incrfont -1}
511 bind $cflist <<ListboxSelect>> listboxsel
512 bind . <Destroy> {savestuff %W}
513 bind . <Button-1> "click %W"
514 bind $fstring <Key-Return> dofind
515 bind $sha1entry <Key-Return> gotocommit
516 bind $sha1entry <<PasteSelection>> clearsha1
518 set maincursor [. cget -cursor]
519 set textcursor [$ctext cget -cursor]
520 set curtextcursor $textcursor
522 set rowctxmenu .rowctxmenu
523 menu $rowctxmenu -tearoff 0
524 $rowctxmenu add command -label "Diff this -> selected" \
525 -command {diffvssel 0}
526 $rowctxmenu add command -label "Diff selected -> this" \
527 -command {diffvssel 1}
528 $rowctxmenu add command -label "Make patch" -command mkpatch
529 $rowctxmenu add command -label "Create tag" -command mktag
530 $rowctxmenu add command -label "Write commit to file" -command writecommit
533 # when we make a key binding for the toplevel, make sure
534 # it doesn't get triggered when that key is pressed in the
535 # find string entry widget.
536 proc bindkey {ev script} {
539 set escript [bind Entry $ev]
540 if {$escript == {}} {
541 set escript [bind Entry <Key>]
544 bind $e $ev "$escript; break"
548 # set the focus back to the toplevel for any click outside
559 global canv canv2 canv3 ctext cflist mainfont textfont
560 global stuffsaved findmergefiles gaudydiff maxgraphpct
563 if {$stuffsaved} return
564 if {![winfo viewable .]} return
566 set f [open "~/.gitk-new" w]
567 puts $f [list set mainfont $mainfont]
568 puts $f [list set textfont $textfont]
569 puts $f [list set findmergefiles $findmergefiles]
570 puts $f [list set gaudydiff $gaudydiff]
571 puts $f [list set maxgraphpct $maxgraphpct]
572 puts $f [list set maxwidth $maxwidth]
573 puts $f "set geometry(width) [winfo width .ctop]"
574 puts $f "set geometry(height) [winfo height .ctop]"
575 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
576 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
577 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
578 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
579 set wid [expr {([winfo width $ctext] - 8) \
580 / [font measure $textfont "0"]}]
581 puts $f "set geometry(ctextw) $wid"
582 set wid [expr {([winfo width $cflist] - 11) \
583 / [font measure [$cflist cget -font] "0"]}]
584 puts $f "set geometry(cflistw) $wid"
586 file rename -force "~/.gitk-new" "~/.gitk"
591 proc resizeclistpanes {win w} {
593 if [info exists oldwidth($win)] {
594 set s0 [$win sash coord 0]
595 set s1 [$win sash coord 1]
597 set sash0 [expr {int($w/2 - 2)}]
598 set sash1 [expr {int($w*5/6 - 2)}]
600 set factor [expr {1.0 * $w / $oldwidth($win)}]
601 set sash0 [expr {int($factor * [lindex $s0 0])}]
602 set sash1 [expr {int($factor * [lindex $s1 0])}]
606 if {$sash1 < $sash0 + 20} {
607 set sash1 [expr $sash0 + 20]
609 if {$sash1 > $w - 10} {
610 set sash1 [expr $w - 10]
611 if {$sash0 > $sash1 - 20} {
612 set sash0 [expr $sash1 - 20]
616 $win sash place 0 $sash0 [lindex $s0 1]
617 $win sash place 1 $sash1 [lindex $s1 1]
619 set oldwidth($win) $w
622 proc resizecdetpanes {win w} {
624 if [info exists oldwidth($win)] {
625 set s0 [$win sash coord 0]
627 set sash0 [expr {int($w*3/4 - 2)}]
629 set factor [expr {1.0 * $w / $oldwidth($win)}]
630 set sash0 [expr {int($factor * [lindex $s0 0])}]
634 if {$sash0 > $w - 15} {
635 set sash0 [expr $w - 15]
638 $win sash place 0 $sash0 [lindex $s0 1]
640 set oldwidth($win) $w
644 global canv canv2 canv3
650 proc bindall {event action} {
651 global canv canv2 canv3
652 bind $canv $event $action
653 bind $canv2 $event $action
654 bind $canv3 $event $action
659 if {[winfo exists $w]} {
664 wm title $w "About gitk"
668 Copyright © 2005 Paul Mackerras
670 Use and redistribute under the terms of the GNU General Public License} \
671 -justify center -aspect 400
672 pack $w.m -side top -fill x -padx 20 -pady 20
673 button $w.ok -text Close -command "destroy $w"
674 pack $w.ok -side bottom
677 proc assigncolor {id} {
678 global commitinfo colormap commcolors colors nextcolor
679 global parents nparents children nchildren
680 global cornercrossings crossings
682 if [info exists colormap($id)] return
683 set ncolors [llength $colors]
684 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
685 set child [lindex $children($id) 0]
686 if {[info exists colormap($child)]
687 && $nparents($child) == 1} {
688 set colormap($id) $colormap($child)
693 if {[info exists cornercrossings($id)]} {
694 foreach x $cornercrossings($id) {
695 if {[info exists colormap($x)]
696 && [lsearch -exact $badcolors $colormap($x)] < 0} {
697 lappend badcolors $colormap($x)
700 if {[llength $badcolors] >= $ncolors} {
704 set origbad $badcolors
705 if {[llength $badcolors] < $ncolors - 1} {
706 if {[info exists crossings($id)]} {
707 foreach x $crossings($id) {
708 if {[info exists colormap($x)]
709 && [lsearch -exact $badcolors $colormap($x)] < 0} {
710 lappend badcolors $colormap($x)
713 if {[llength $badcolors] >= $ncolors} {
714 set badcolors $origbad
717 set origbad $badcolors
719 if {[llength $badcolors] < $ncolors - 1} {
720 foreach child $children($id) {
721 if {[info exists colormap($child)]
722 && [lsearch -exact $badcolors $colormap($child)] < 0} {
723 lappend badcolors $colormap($child)
725 if {[info exists parents($child)]} {
726 foreach p $parents($child) {
727 if {[info exists colormap($p)]
728 && [lsearch -exact $badcolors $colormap($p)] < 0} {
729 lappend badcolors $colormap($p)
734 if {[llength $badcolors] >= $ncolors} {
735 set badcolors $origbad
738 for {set i 0} {$i <= $ncolors} {incr i} {
739 set c [lindex $colors $nextcolor]
740 if {[incr nextcolor] >= $ncolors} {
743 if {[lsearch -exact $badcolors $c]} break
749 global canvy canvy0 lineno numcommits nextcolor linespc
750 global mainline mainlinearrow sidelines
751 global nchildren ncleft
752 global displist nhyperspace
759 catch {unset mainline}
760 catch {unset mainlinearrow}
761 catch {unset sidelines}
762 foreach id [array names nchildren] {
763 set ncleft($id) $nchildren($id)
769 proc bindline {t id} {
772 $canv bind $t <Enter> "lineenter %x %y $id"
773 $canv bind $t <Motion> "linemotion %x %y $id"
774 $canv bind $t <Leave> "lineleave $id"
775 $canv bind $t <Button-1> "lineclick %x %y $id 1"
778 # level here is an index in displist
779 proc drawcommitline {level} {
780 global parents children nparents displist
781 global canv canv2 canv3 mainfont namefont canvy linespc
782 global lineid linehtag linentag linedtag commitinfo
783 global colormap numcommits currentparents dupparents
784 global idtags idline idheads idotherrefs
785 global lineno lthickness mainline mainlinearrow sidelines
786 global commitlisted rowtextx idpos lastuse displist
787 global oldnlines olddlevel olddisplist
791 set id [lindex $displist $level]
792 set lastuse($id) $lineno
793 set lineid($lineno) $id
794 set idline($id) $lineno
795 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
796 if {![info exists commitinfo($id)]} {
798 if {![info exists commitinfo($id)]} {
799 set commitinfo($id) {"No commit information available"}
804 set currentparents {}
806 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
807 foreach p $parents($id) {
808 if {[lsearch -exact $currentparents $p] < 0} {
809 lappend currentparents $p
811 # remember that this parent was listed twice
812 lappend dupparents $p
816 set x [xcoord $level $level $lineno]
818 set canvy [expr $canvy + $linespc]
819 allcanvs conf -scrollregion \
820 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
821 if {[info exists mainline($id)]} {
822 lappend mainline($id) $x $y1
823 if {$mainlinearrow($id) ne "none"} {
824 set mainline($id) [trimdiagstart $mainline($id)]
826 set t [$canv create line $mainline($id) \
827 -width $lthickness -fill $colormap($id) \
828 -arrow $mainlinearrow($id)]
832 if {[info exists sidelines($id)]} {
833 foreach ls $sidelines($id) {
834 set coords [lindex $ls 0]
835 set thick [lindex $ls 1]
836 set arrow [lindex $ls 2]
837 set t [$canv create line $coords -fill $colormap($id) \
838 -width [expr {$thick * $lthickness}] -arrow $arrow]
843 set orad [expr {$linespc / 3}]
844 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
845 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
846 -fill $ofill -outline black -width 1]
848 $canv bind $t <1> {selcanvline {} %x %y}
849 set xt [xcoord [llength $displist] $level $lineno]
850 if {[llength $currentparents] > 2} {
851 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
853 set rowtextx($lineno) $xt
854 set idpos($id) [list $x $xt $y1]
855 if {[info exists idtags($id)] || [info exists idheads($id)]
856 || [info exists idotherrefs($id)]} {
857 set xt [drawtags $id $x $xt $y1]
859 set headline [lindex $commitinfo($id) 0]
860 set name [lindex $commitinfo($id) 1]
861 set date [lindex $commitinfo($id) 2]
862 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
863 -text $headline -font $mainfont ]
864 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
865 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
866 -text $name -font $namefont]
867 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
868 -text $date -font $mainfont]
871 set olddisplist $displist
872 set oldnlines [llength $displist]
875 proc drawtags {id x xt y1} {
876 global idtags idheads idotherrefs
877 global linespc lthickness
878 global canv mainfont idline rowtextx
883 if {[info exists idtags($id)]} {
884 set marks $idtags($id)
885 set ntags [llength $marks]
887 if {[info exists idheads($id)]} {
888 set marks [concat $marks $idheads($id)]
889 set nheads [llength $idheads($id)]
891 if {[info exists idotherrefs($id)]} {
892 set marks [concat $marks $idotherrefs($id)]
898 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
899 set yt [expr $y1 - 0.5 * $linespc]
900 set yb [expr $yt + $linespc - 1]
904 set wid [font measure $mainfont $tag]
907 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
909 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
910 -width $lthickness -fill black -tags tag.$id]
912 foreach tag $marks x $xvals wid $wvals {
913 set xl [expr $x + $delta]
914 set xr [expr $x + $delta + $wid + $lthickness]
915 if {[incr ntags -1] >= 0} {
917 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
918 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
919 -width 1 -outline black -fill yellow -tags tag.$id]
920 $canv bind $t <1> [list showtag $tag 1]
921 set rowtextx($idline($id)) [expr {$xr + $linespc}]
923 # draw a head or other ref
924 if {[incr nheads -1] >= 0} {
929 set xl [expr $xl - $delta/2]
930 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
931 -width 1 -outline black -fill $col -tags tag.$id
933 set t [$canv create text $xl $y1 -anchor w -text $tag \
934 -font $mainfont -tags tag.$id]
936 $canv bind $t <1> [list showtag $tag 1]
942 proc notecrossings {id lo hi corner} {
943 global olddisplist crossings cornercrossings
945 for {set i $lo} {[incr i] < $hi} {} {
946 set p [lindex $olddisplist $i]
947 if {$p == {}} continue
949 if {![info exists cornercrossings($id)]
950 || [lsearch -exact $cornercrossings($id) $p] < 0} {
951 lappend cornercrossings($id) $p
953 if {![info exists cornercrossings($p)]
954 || [lsearch -exact $cornercrossings($p) $id] < 0} {
955 lappend cornercrossings($p) $id
958 if {![info exists crossings($id)]
959 || [lsearch -exact $crossings($id) $p] < 0} {
960 lappend crossings($id) $p
962 if {![info exists crossings($p)]
963 || [lsearch -exact $crossings($p) $id] < 0} {
964 lappend crossings($p) $id
970 proc xcoord {i level ln} {
971 global canvx0 xspc1 xspc2
973 set x [expr {$canvx0 + $i * $xspc1($ln)}]
974 if {$i > 0 && $i == $level} {
975 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
976 } elseif {$i > $level} {
977 set x [expr {$x + $xspc2 - $xspc1($ln)}]
982 # it seems Tk can't draw arrows on the end of diagonal line segments...
983 proc trimdiagend {line} {
984 while {[llength $line] > 4} {
985 set x1 [lindex $line end-3]
986 set y1 [lindex $line end-2]
987 set x2 [lindex $line end-1]
988 set y2 [lindex $line end]
989 if {($x1 == $x2) != ($y1 == $y2)} break
990 set line [lreplace $line end-1 end]
995 proc trimdiagstart {line} {
996 while {[llength $line] > 4} {
997 set x1 [lindex $line 0]
998 set y1 [lindex $line 1]
999 set x2 [lindex $line 2]
1000 set y2 [lindex $line 3]
1001 if {($x1 == $x2) != ($y1 == $y2)} break
1002 set line [lreplace $line 0 1]
1007 proc drawslants {id needonscreen nohs} {
1008 global canv mainline mainlinearrow sidelines
1009 global canvx0 canvy xspc1 xspc2 lthickness
1010 global currentparents dupparents
1011 global lthickness linespc canvy colormap lineno geometry
1012 global maxgraphpct maxwidth
1013 global displist onscreen lastuse
1014 global parents commitlisted
1015 global oldnlines olddlevel olddisplist
1016 global nhyperspace numcommits nnewparents
1019 lappend displist $id
1024 set y1 [expr {$canvy - $linespc}]
1027 # work out what we need to get back on screen
1029 if {$onscreen($id) < 0} {
1030 # next to do isn't displayed, better get it on screen...
1031 lappend reins [list $id 0]
1033 # make sure all the previous commits's parents are on the screen
1034 foreach p $currentparents {
1035 if {$onscreen($p) < 0} {
1036 lappend reins [list $p 0]
1039 # bring back anything requested by caller
1040 if {$needonscreen ne {}} {
1041 lappend reins $needonscreen
1045 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1046 set dlevel $olddlevel
1047 set x [xcoord $dlevel $dlevel $lineno]
1048 set mainline($id) [list $x $y1]
1049 set mainlinearrow($id) none
1050 set lastuse($id) $lineno
1051 set displist [lreplace $displist $dlevel $dlevel $id]
1053 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1058 set displist [lreplace $displist $olddlevel $olddlevel]
1060 foreach p $currentparents {
1061 set lastuse($p) $lineno
1062 if {$onscreen($p) == 0} {
1063 set displist [linsert $displist $j $p]
1068 if {$onscreen($id) == 0} {
1069 lappend displist $id
1073 # remove the null entry if present
1074 set nullentry [lsearch -exact $displist {}]
1075 if {$nullentry >= 0} {
1076 set displist [lreplace $displist $nullentry $nullentry]
1079 # bring back the ones we need now (if we did it earlier
1080 # it would change displist and invalidate olddlevel)
1082 # test again in case of duplicates in reins
1083 set p [lindex $pi 0]
1084 if {$onscreen($p) < 0} {
1086 set lastuse($p) $lineno
1087 set displist [linsert $displist [lindex $pi 1] $p]
1092 set lastuse($id) $lineno
1094 # see if we need to make any lines jump off into hyperspace
1095 set displ [llength $displist]
1096 if {$displ > $maxwidth} {
1098 foreach x $displist {
1099 lappend ages [list $lastuse($x) $x]
1101 set ages [lsort -integer -index 0 $ages]
1103 while {$displ > $maxwidth} {
1104 set use [lindex $ages $k 0]
1105 set victim [lindex $ages $k 1]
1106 if {$use >= $lineno - 5} break
1108 if {[lsearch -exact $nohs $victim] >= 0} continue
1109 set i [lsearch -exact $displist $victim]
1110 set displist [lreplace $displist $i $i]
1111 set onscreen($victim) -1
1114 if {$i < $nullentry} {
1117 set x [lindex $mainline($victim) end-1]
1118 lappend mainline($victim) $x $y1
1119 set line [trimdiagend $mainline($victim)]
1121 if {$mainlinearrow($victim) ne "none"} {
1122 set line [trimdiagstart $line]
1125 lappend sidelines($victim) [list $line 1 $arrow]
1126 unset mainline($victim)
1130 set dlevel [lsearch -exact $displist $id]
1132 # If we are reducing, put in a null entry
1133 if {$displ < $oldnlines} {
1134 # does the next line look like a merge?
1135 # i.e. does it have > 1 new parent?
1136 if {$nnewparents($id) > 1} {
1137 set i [expr {$dlevel + 1}]
1138 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1140 if {$nullentry >= 0 && $nullentry < $i} {
1143 } elseif {$nullentry >= 0} {
1146 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1151 if {$dlevel >= $i} {
1156 set displist [linsert $displist $i {}]
1158 if {$dlevel >= $i} {
1164 # decide on the line spacing for the next line
1165 set lj [expr {$lineno + 1}]
1166 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1167 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1168 set xspc1($lj) $xspc2
1170 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1171 if {$xspc1($lj) < $lthickness} {
1172 set xspc1($lj) $lthickness
1176 foreach idi $reins {
1177 set id [lindex $idi 0]
1178 set j [lsearch -exact $displist $id]
1179 set xj [xcoord $j $dlevel $lj]
1180 set mainline($id) [list $xj $y2]
1181 set mainlinearrow($id) first
1185 foreach id $olddisplist {
1187 if {$id == {}} continue
1188 if {$onscreen($id) <= 0} continue
1189 set xi [xcoord $i $olddlevel $lineno]
1190 if {$i == $olddlevel} {
1191 foreach p $currentparents {
1192 set j [lsearch -exact $displist $p]
1193 set coords [list $xi $y1]
1194 set xj [xcoord $j $dlevel $lj]
1195 if {$xj < $xi - $linespc} {
1196 lappend coords [expr {$xj + $linespc}] $y1
1197 notecrossings $p $j $i [expr {$j + 1}]
1198 } elseif {$xj > $xi + $linespc} {
1199 lappend coords [expr {$xj - $linespc}] $y1
1200 notecrossings $p $i $j [expr {$j - 1}]
1202 if {[lsearch -exact $dupparents $p] >= 0} {
1203 # draw a double-width line to indicate the doubled parent
1204 lappend coords $xj $y2
1205 lappend sidelines($p) [list $coords 2 none]
1206 if {![info exists mainline($p)]} {
1207 set mainline($p) [list $xj $y2]
1208 set mainlinearrow($p) none
1211 # normal case, no parent duplicated
1213 set dx [expr {abs($xi - $xj)}]
1214 if {0 && $dx < $linespc} {
1215 set yb [expr {$y1 + $dx}]
1217 if {![info exists mainline($p)]} {
1219 lappend coords $xj $yb
1221 set mainline($p) $coords
1222 set mainlinearrow($p) none
1224 lappend coords $xj $yb
1226 lappend coords $xj $y2
1228 lappend sidelines($p) [list $coords 1 none]
1234 if {[lindex $displist $i] != $id} {
1235 set j [lsearch -exact $displist $id]
1237 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1238 || ($olddlevel < $i && $i < $dlevel)
1239 || ($dlevel < $i && $i < $olddlevel)} {
1240 set xj [xcoord $j $dlevel $lj]
1241 lappend mainline($id) $xi $y1 $xj $y2
1248 # search for x in a list of lists
1249 proc llsearch {llist x} {
1252 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1260 proc drawmore {reading} {
1261 global displayorder numcommits ncmupdate nextupdate
1262 global stopped nhyperspace parents commitlisted
1263 global maxwidth onscreen displist currentparents olddlevel
1265 set n [llength $displayorder]
1266 while {$numcommits < $n} {
1267 set id [lindex $displayorder $numcommits]
1268 set ctxend [expr {$numcommits + 10}]
1269 if {!$reading && $ctxend > $n} {
1273 if {$numcommits > 0} {
1274 set dlist [lreplace $displist $olddlevel $olddlevel]
1276 foreach p $currentparents {
1277 if {$onscreen($p) == 0} {
1278 set dlist [linsert $dlist $i $p]
1285 set isfat [expr {[llength $dlist] > $maxwidth}]
1286 if {$nhyperspace > 0 || $isfat} {
1287 if {$ctxend > $n} break
1288 # work out what to bring back and
1289 # what we want to don't want to send into hyperspace
1291 for {set k $numcommits} {$k < $ctxend} {incr k} {
1292 set x [lindex $displayorder $k]
1293 set i [llsearch $dlist $x]
1295 set i [llength $dlist]
1298 if {[lsearch -exact $nohs $x] < 0} {
1301 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1302 set reins [list $x $i]
1305 if {[info exists commitlisted($x)]} {
1307 foreach p $parents($x) {
1308 if {[llsearch $dlist $p] < 0} {
1310 if {[lsearch -exact $nohs $p] < 0} {
1313 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1314 set reins [list $p [expr {$i + $right}]]
1320 set l [lindex $dlist $i]
1321 if {[llength $l] == 1} {
1324 set j [lsearch -exact $l $x]
1325 set l [concat [lreplace $l $j $j] $newp]
1327 set dlist [lreplace $dlist $i $i $l]
1328 if {$room && $isfat && [llength $newp] <= 1} {
1334 set dlevel [drawslants $id $reins $nohs]
1335 drawcommitline $dlevel
1336 if {[clock clicks -milliseconds] >= $nextupdate
1337 && $numcommits >= $ncmupdate} {
1344 # level here is an index in todo
1345 proc updatetodo {level noshortcut} {
1346 global ncleft todo nnewparents
1347 global commitlisted parents onscreen
1349 set id [lindex $todo $level]
1351 if {[info exists commitlisted($id)]} {
1352 foreach p $parents($id) {
1353 if {[lsearch -exact $olds $p] < 0} {
1358 if {!$noshortcut && [llength $olds] == 1} {
1359 set p [lindex $olds 0]
1360 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1362 set todo [lreplace $todo $level $level $p]
1364 set nnewparents($id) 1
1369 set todo [lreplace $todo $level $level]
1374 set k [lsearch -exact $todo $p]
1376 set todo [linsert $todo $i $p]
1382 set nnewparents($id) $n
1387 proc decidenext {{noread 0}} {
1389 global datemode cdate
1392 # choose which one to do next time around
1393 set todol [llength $todo]
1396 for {set k $todol} {[incr k -1] >= 0} {} {
1397 set p [lindex $todo $k]
1398 if {$ncleft($p) == 0} {
1400 if {![info exists commitinfo($p)]} {
1406 if {$latest == {} || $cdate($p) > $latest} {
1408 set latest $cdate($p)
1418 puts "ERROR: none of the pending commits can be done yet:"
1420 puts " $p ($ncleft($p))"
1429 proc drawcommit {id} {
1430 global phase todo nchildren datemode nextupdate
1431 global numcommits ncmupdate displayorder todo onscreen
1433 if {$phase != "incrdraw"} {
1439 if {$nchildren($id) == 0} {
1443 set level [decidenext 1]
1444 if {$level == {} || $id != [lindex $todo $level]} {
1448 lappend displayorder [lindex $todo $level]
1449 if {[updatetodo $level $datemode]} {
1450 set level [decidenext 1]
1451 if {$level == {}} break
1453 set id [lindex $todo $level]
1454 if {![info exists commitlisted($id)]} {
1461 proc finishcommits {} {
1463 global canv mainfont ctext maincursor textcursor
1465 if {$phase != "incrdraw"} {
1467 $canv create text 3 3 -anchor nw -text "No commits selected" \
1468 -font $mainfont -tags textitems
1473 . config -cursor $maincursor
1474 settextcursor $textcursor
1477 # Don't change the text pane cursor if it is currently the hand cursor,
1478 # showing that we are over a sha1 ID link.
1479 proc settextcursor {c} {
1480 global ctext curtextcursor
1482 if {[$ctext cget -cursor] == $curtextcursor} {
1483 $ctext config -cursor $c
1485 set curtextcursor $c
1489 global nextupdate startmsecs ncmupdate
1490 global displayorder onscreen
1492 if {$displayorder == {}} return
1493 set startmsecs [clock clicks -milliseconds]
1494 set nextupdate [expr $startmsecs + 100]
1497 foreach id $displayorder {
1504 global phase stopped redisplaying selectedline
1505 global datemode todo displayorder
1506 global numcommits ncmupdate
1507 global nextupdate startmsecs
1509 set level [decidenext]
1513 lappend displayorder [lindex $todo $level]
1514 set hard [updatetodo $level $datemode]
1516 set level [decidenext]
1517 if {$level < 0} break
1523 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1524 #puts "overall $drawmsecs ms for $numcommits commits"
1525 if {$redisplaying} {
1526 if {$stopped == 0 && [info exists selectedline]} {
1527 selectline $selectedline 0
1529 if {$stopped == 1} {
1531 after idle drawgraph
1538 proc findmatches {f} {
1539 global findtype foundstring foundstrlen
1540 if {$findtype == "Regexp"} {
1541 set matches [regexp -indices -all -inline $foundstring $f]
1543 if {$findtype == "IgnCase"} {
1544 set str [string tolower $f]
1550 while {[set j [string first $foundstring $str $i]] >= 0} {
1551 lappend matches [list $j [expr $j+$foundstrlen-1]]
1552 set i [expr $j + $foundstrlen]
1559 global findtype findloc findstring markedmatches commitinfo
1560 global numcommits lineid linehtag linentag linedtag
1561 global mainfont namefont canv canv2 canv3 selectedline
1562 global matchinglines foundstring foundstrlen
1567 set matchinglines {}
1568 if {$findloc == "Pickaxe"} {
1572 if {$findtype == "IgnCase"} {
1573 set foundstring [string tolower $findstring]
1575 set foundstring $findstring
1577 set foundstrlen [string length $findstring]
1578 if {$foundstrlen == 0} return
1579 if {$findloc == "Files"} {
1583 if {![info exists selectedline]} {
1586 set oldsel $selectedline
1589 set fldtypes {Headline Author Date Committer CDate Comment}
1590 for {set l 0} {$l < $numcommits} {incr l} {
1592 set info $commitinfo($id)
1594 foreach f $info ty $fldtypes {
1595 if {$findloc != "All fields" && $findloc != $ty} {
1598 set matches [findmatches $f]
1599 if {$matches == {}} continue
1601 if {$ty == "Headline"} {
1602 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1603 } elseif {$ty == "Author"} {
1604 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1605 } elseif {$ty == "Date"} {
1606 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1610 lappend matchinglines $l
1611 if {!$didsel && $l > $oldsel} {
1617 if {$matchinglines == {}} {
1619 } elseif {!$didsel} {
1620 findselectline [lindex $matchinglines 0]
1624 proc findselectline {l} {
1625 global findloc commentend ctext
1627 if {$findloc == "All fields" || $findloc == "Comments"} {
1628 # highlight the matches in the comments
1629 set f [$ctext get 1.0 $commentend]
1630 set matches [findmatches $f]
1631 foreach match $matches {
1632 set start [lindex $match 0]
1633 set end [expr [lindex $match 1] + 1]
1634 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1639 proc findnext {restart} {
1640 global matchinglines selectedline
1641 if {![info exists matchinglines]} {
1647 if {![info exists selectedline]} return
1648 foreach l $matchinglines {
1649 if {$l > $selectedline} {
1658 global matchinglines selectedline
1659 if {![info exists matchinglines]} {
1663 if {![info exists selectedline]} return
1665 foreach l $matchinglines {
1666 if {$l >= $selectedline} break
1670 findselectline $prev
1676 proc findlocchange {name ix op} {
1677 global findloc findtype findtypemenu
1678 if {$findloc == "Pickaxe"} {
1684 $findtypemenu entryconf 1 -state $state
1685 $findtypemenu entryconf 2 -state $state
1688 proc stopfindproc {{done 0}} {
1689 global findprocpid findprocfile findids
1690 global ctext findoldcursor phase maincursor textcursor
1691 global findinprogress
1693 catch {unset findids}
1694 if {[info exists findprocpid]} {
1696 catch {exec kill $findprocpid}
1698 catch {close $findprocfile}
1701 if {[info exists findinprogress]} {
1702 unset findinprogress
1703 if {$phase != "incrdraw"} {
1704 . config -cursor $maincursor
1705 settextcursor $textcursor
1710 proc findpatches {} {
1711 global findstring selectedline numcommits
1712 global findprocpid findprocfile
1713 global finddidsel ctext lineid findinprogress
1714 global findinsertpos
1716 if {$numcommits == 0} return
1718 # make a list of all the ids to search, starting at the one
1719 # after the selected line (if any)
1720 if {[info exists selectedline]} {
1726 for {set i 0} {$i < $numcommits} {incr i} {
1727 if {[incr l] >= $numcommits} {
1730 append inputids $lineid($l) "\n"
1734 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1737 error_popup "Error starting search process: $err"
1741 set findinsertpos end
1743 set findprocpid [pid $f]
1744 fconfigure $f -blocking 0
1745 fileevent $f readable readfindproc
1747 . config -cursor watch
1749 set findinprogress 1
1752 proc readfindproc {} {
1753 global findprocfile finddidsel
1754 global idline matchinglines findinsertpos
1756 set n [gets $findprocfile line]
1758 if {[eof $findprocfile]} {
1766 if {![regexp {^[0-9a-f]{40}} $line id]} {
1767 error_popup "Can't parse git-diff-tree output: $line"
1771 if {![info exists idline($id)]} {
1772 puts stderr "spurious id: $id"
1779 proc insertmatch {l id} {
1780 global matchinglines findinsertpos finddidsel
1782 if {$findinsertpos == "end"} {
1783 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1784 set matchinglines [linsert $matchinglines 0 $l]
1787 lappend matchinglines $l
1790 set matchinglines [linsert $matchinglines $findinsertpos $l]
1801 global selectedline numcommits lineid ctext
1802 global ffileline finddidsel parents nparents
1803 global findinprogress findstartline findinsertpos
1804 global treediffs fdiffids fdiffsneeded fdiffpos
1805 global findmergefiles
1807 if {$numcommits == 0} return
1809 if {[info exists selectedline]} {
1810 set l [expr {$selectedline + 1}]
1815 set findstartline $l
1820 if {$findmergefiles || $nparents($id) == 1} {
1821 foreach p $parents($id) {
1822 if {![info exists treediffs([list $id $p])]} {
1823 append diffsneeded "$id $p\n"
1824 lappend fdiffsneeded [list $id $p]
1828 if {[incr l] >= $numcommits} {
1831 if {$l == $findstartline} break
1834 # start off a git-diff-tree process if needed
1835 if {$diffsneeded ne {}} {
1837 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1839 error_popup "Error starting search process: $err"
1842 catch {unset fdiffids}
1844 fconfigure $df -blocking 0
1845 fileevent $df readable [list readfilediffs $df]
1849 set findinsertpos end
1851 set p [lindex $parents($id) 0]
1852 . config -cursor watch
1854 set findinprogress 1
1855 findcont [list $id $p]
1859 proc readfilediffs {df} {
1860 global findids fdiffids fdiffs
1862 set n [gets $df line]
1866 if {[catch {close $df} err]} {
1869 error_popup "Error in git-diff-tree: $err"
1870 } elseif {[info exists findids]} {
1874 error_popup "Couldn't find diffs for {$ids}"
1879 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1880 # start of a new string of diffs
1882 set fdiffids [list $id $p]
1884 } elseif {[string match ":*" $line]} {
1885 lappend fdiffs [lindex $line 5]
1889 proc donefilediff {} {
1890 global fdiffids fdiffs treediffs findids
1891 global fdiffsneeded fdiffpos
1893 if {[info exists fdiffids]} {
1894 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1895 && $fdiffpos < [llength $fdiffsneeded]} {
1896 # git-diff-tree doesn't output anything for a commit
1897 # which doesn't change anything
1898 set nullids [lindex $fdiffsneeded $fdiffpos]
1899 set treediffs($nullids) {}
1900 if {[info exists findids] && $nullids eq $findids} {
1908 if {![info exists treediffs($fdiffids)]} {
1909 set treediffs($fdiffids) $fdiffs
1911 if {[info exists findids] && $fdiffids eq $findids} {
1918 proc findcont {ids} {
1919 global findids treediffs parents nparents
1920 global ffileline findstartline finddidsel
1921 global lineid numcommits matchinglines findinprogress
1922 global findmergefiles
1924 set id [lindex $ids 0]
1925 set p [lindex $ids 1]
1926 set pi [lsearch -exact $parents($id) $p]
1929 if {$findmergefiles || $nparents($id) == 1} {
1930 if {![info exists treediffs($ids)]} {
1936 foreach f $treediffs($ids) {
1937 set x [findmatches $f]
1945 set pi $nparents($id)
1948 set pi $nparents($id)
1950 if {[incr pi] >= $nparents($id)} {
1952 if {[incr l] >= $numcommits} {
1955 if {$l == $findstartline} break
1958 set p [lindex $parents($id) $pi]
1959 set ids [list $id $p]
1967 # mark a commit as matching by putting a yellow background
1968 # behind the headline
1969 proc markheadline {l id} {
1970 global canv mainfont linehtag commitinfo
1972 set bbox [$canv bbox $linehtag($l)]
1973 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1977 # mark the bits of a headline, author or date that match a find string
1978 proc markmatches {canv l str tag matches font} {
1979 set bbox [$canv bbox $tag]
1980 set x0 [lindex $bbox 0]
1981 set y0 [lindex $bbox 1]
1982 set y1 [lindex $bbox 3]
1983 foreach match $matches {
1984 set start [lindex $match 0]
1985 set end [lindex $match 1]
1986 if {$start > $end} continue
1987 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1988 set xlen [font measure $font [string range $str 0 [expr $end]]]
1989 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1990 -outline {} -tags matches -fill yellow]
1995 proc unmarkmatches {} {
1996 global matchinglines findids
1997 allcanvs delete matches
1998 catch {unset matchinglines}
1999 catch {unset findids}
2002 proc selcanvline {w x y} {
2003 global canv canvy0 ctext linespc
2004 global lineid linehtag linentag linedtag rowtextx
2005 set ymax [lindex [$canv cget -scrollregion] 3]
2006 if {$ymax == {}} return
2007 set yfrac [lindex [$canv yview] 0]
2008 set y [expr {$y + $yfrac * $ymax}]
2009 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2014 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2020 proc commit_descriptor {p} {
2023 if {[info exists commitinfo($p)]} {
2024 set l [lindex $commitinfo($p) 0]
2029 # append some text to the ctext widget, and make any SHA1 ID
2030 # that we know about be a clickable link.
2031 proc appendwithlinks {text} {
2032 global ctext idline linknum
2034 set start [$ctext index "end - 1c"]
2035 $ctext insert end $text
2036 $ctext insert end "\n"
2037 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2041 set linkid [string range $text $s $e]
2042 if {![info exists idline($linkid)]} continue
2044 $ctext tag add link "$start + $s c" "$start + $e c"
2045 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2046 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2049 $ctext tag conf link -foreground blue -underline 1
2050 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2051 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2054 proc selectline {l isnew} {
2055 global canv canv2 canv3 ctext commitinfo selectedline
2056 global lineid linehtag linentag linedtag
2057 global canvy0 linespc parents nparents children
2058 global cflist currentid sha1entry
2059 global commentend idtags idline linknum
2062 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2064 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2065 -tags secsel -fill [$canv cget -selectbackground]]
2067 $canv2 delete secsel
2068 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2069 -tags secsel -fill [$canv2 cget -selectbackground]]
2071 $canv3 delete secsel
2072 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2073 -tags secsel -fill [$canv3 cget -selectbackground]]
2075 set y [expr {$canvy0 + $l * $linespc}]
2076 set ymax [lindex [$canv cget -scrollregion] 3]
2077 set ytop [expr {$y - $linespc - 1}]
2078 set ybot [expr {$y + $linespc + 1}]
2079 set wnow [$canv yview]
2080 set wtop [expr [lindex $wnow 0] * $ymax]
2081 set wbot [expr [lindex $wnow 1] * $ymax]
2082 set wh [expr {$wbot - $wtop}]
2084 if {$ytop < $wtop} {
2085 if {$ybot < $wtop} {
2086 set newtop [expr {$y - $wh / 2.0}]
2089 if {$newtop > $wtop - $linespc} {
2090 set newtop [expr {$wtop - $linespc}]
2093 } elseif {$ybot > $wbot} {
2094 if {$ytop > $wbot} {
2095 set newtop [expr {$y - $wh / 2.0}]
2097 set newtop [expr {$ybot - $wh}]
2098 if {$newtop < $wtop + $linespc} {
2099 set newtop [expr {$wtop + $linespc}]
2103 if {$newtop != $wtop} {
2107 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2111 addtohistory [list selectline $l 0]
2118 $sha1entry delete 0 end
2119 $sha1entry insert 0 $id
2120 $sha1entry selection from 0
2121 $sha1entry selection to end
2123 $ctext conf -state normal
2124 $ctext delete 0.0 end
2126 $ctext mark set fmark.0 0.0
2127 $ctext mark gravity fmark.0 left
2128 set info $commitinfo($id)
2129 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
2130 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
2131 if {[info exists idtags($id)]} {
2132 $ctext insert end "Tags:"
2133 foreach tag $idtags($id) {
2134 $ctext insert end " $tag"
2136 $ctext insert end "\n"
2140 if {[info exists parents($id)]} {
2141 foreach p $parents($id) {
2142 append comment "Parent: [commit_descriptor $p]\n"
2145 if {[info exists children($id)]} {
2146 foreach c $children($id) {
2147 append comment "Child: [commit_descriptor $c]\n"
2151 append comment [lindex $info 5]
2153 # make anything that looks like a SHA1 ID be a clickable link
2154 appendwithlinks $comment
2156 $ctext tag delete Comments
2157 $ctext tag remove found 1.0 end
2158 $ctext conf -state disabled
2159 set commentend [$ctext index "end - 1c"]
2161 $cflist delete 0 end
2162 $cflist insert end "Comments"
2163 if {$nparents($id) == 1} {
2164 startdiff [concat $id $parents($id)]
2165 } elseif {$nparents($id) > 1} {
2170 proc selnextline {dir} {
2172 if {![info exists selectedline]} return
2173 set l [expr $selectedline + $dir]
2178 proc unselectline {} {
2181 catch {unset selectedline}
2182 allcanvs delete secsel
2185 proc addtohistory {cmd} {
2186 global history historyindex
2188 if {$historyindex > 0
2189 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2193 if {$historyindex < [llength $history]} {
2194 set history [lreplace $history $historyindex end $cmd]
2196 lappend history $cmd
2199 if {$historyindex > 1} {
2200 .ctop.top.bar.leftbut conf -state normal
2202 .ctop.top.bar.leftbut conf -state disabled
2204 .ctop.top.bar.rightbut conf -state disabled
2208 global history historyindex
2210 if {$historyindex > 1} {
2211 incr historyindex -1
2212 set cmd [lindex $history [expr {$historyindex - 1}]]
2214 .ctop.top.bar.rightbut conf -state normal
2216 if {$historyindex <= 1} {
2217 .ctop.top.bar.leftbut conf -state disabled
2222 global history historyindex
2224 if {$historyindex < [llength $history]} {
2225 set cmd [lindex $history $historyindex]
2228 .ctop.top.bar.leftbut conf -state normal
2230 if {$historyindex >= [llength $history]} {
2231 .ctop.top.bar.rightbut conf -state disabled
2235 proc mergediff {id} {
2236 global parents diffmergeid diffmergegca mergefilelist diffpindex
2240 set diffmergegca [findgca $parents($id)]
2241 if {[info exists mergefilelist($id)]} {
2242 if {$mergefilelist($id) ne {}} {
2250 proc findgca {ids} {
2257 set gca [exec git-merge-base $gca $id]
2266 proc contmergediff {ids} {
2267 global diffmergeid diffpindex parents nparents diffmergegca
2268 global treediffs mergefilelist diffids treepending
2270 # diff the child against each of the parents, and diff
2271 # each of the parents against the GCA.
2273 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2274 set ids [list [lindex $ids 1] $diffmergegca]
2276 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2277 set p [lindex $parents($diffmergeid) $diffpindex]
2278 set ids [list $diffmergeid $p]
2280 if {![info exists treediffs($ids)]} {
2282 if {![info exists treepending]} {
2289 # If a file in some parent is different from the child and also
2290 # different from the GCA, then it's interesting.
2291 # If we don't have a GCA, then a file is interesting if it is
2292 # different from the child in all the parents.
2293 if {$diffmergegca ne {}} {
2295 foreach p $parents($diffmergeid) {
2296 set gcadiffs $treediffs([list $p $diffmergegca])
2297 foreach f $treediffs([list $diffmergeid $p]) {
2298 if {[lsearch -exact $files $f] < 0
2299 && [lsearch -exact $gcadiffs $f] >= 0} {
2304 set files [lsort $files]
2306 set p [lindex $parents($diffmergeid) 0]
2307 set files $treediffs([list $diffmergeid $p])
2308 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2309 set p [lindex $parents($diffmergeid) $i]
2310 set df $treediffs([list $diffmergeid $p])
2313 if {[lsearch -exact $df $f] >= 0} {
2321 set mergefilelist($diffmergeid) $files
2327 proc showmergediff {} {
2328 global cflist diffmergeid mergefilelist parents
2329 global diffopts diffinhunk currentfile currenthunk filelines
2330 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2332 set files $mergefilelist($diffmergeid)
2334 $cflist insert end $f
2336 set env(GIT_DIFF_OPTS) $diffopts
2338 catch {unset currentfile}
2339 catch {unset currenthunk}
2340 catch {unset filelines}
2341 catch {unset groupfilenum}
2342 catch {unset grouphunks}
2343 set groupfilelast -1
2344 foreach p $parents($diffmergeid) {
2345 set cmd [list | git-diff-tree -p $p $diffmergeid]
2346 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2347 if {[catch {set f [open $cmd r]} err]} {
2348 error_popup "Error getting diffs: $err"
2355 set ids [list $diffmergeid $p]
2356 set mergefds($ids) $f
2357 set diffinhunk($ids) 0
2358 set diffblocked($ids) 0
2359 fconfigure $f -blocking 0
2360 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2364 proc getmergediffline {f ids id} {
2365 global diffmergeid diffinhunk diffoldlines diffnewlines
2366 global currentfile currenthunk
2367 global diffoldstart diffnewstart diffoldlno diffnewlno
2368 global diffblocked mergefilelist
2369 global noldlines nnewlines difflcounts filelines
2371 set n [gets $f line]
2373 if {![eof $f]} return
2376 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2383 if {$diffinhunk($ids) != 0} {
2384 set fi $currentfile($ids)
2385 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2386 # continuing an existing hunk
2387 set line [string range $line 1 end]
2388 set p [lindex $ids 1]
2389 if {$match eq "-" || $match eq " "} {
2390 set filelines($p,$fi,$diffoldlno($ids)) $line
2391 incr diffoldlno($ids)
2393 if {$match eq "+" || $match eq " "} {
2394 set filelines($id,$fi,$diffnewlno($ids)) $line
2395 incr diffnewlno($ids)
2397 if {$match eq " "} {
2398 if {$diffinhunk($ids) == 2} {
2399 lappend difflcounts($ids) \
2400 [list $noldlines($ids) $nnewlines($ids)]
2401 set noldlines($ids) 0
2402 set diffinhunk($ids) 1
2404 incr noldlines($ids)
2405 } elseif {$match eq "-" || $match eq "+"} {
2406 if {$diffinhunk($ids) == 1} {
2407 lappend difflcounts($ids) [list $noldlines($ids)]
2408 set noldlines($ids) 0
2409 set nnewlines($ids) 0
2410 set diffinhunk($ids) 2
2412 if {$match eq "-"} {
2413 incr noldlines($ids)
2415 incr nnewlines($ids)
2418 # and if it's \ No newline at end of line, then what?
2422 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2423 lappend difflcounts($ids) [list $noldlines($ids)]
2424 } elseif {$diffinhunk($ids) == 2
2425 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2426 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2428 set currenthunk($ids) [list $currentfile($ids) \
2429 $diffoldstart($ids) $diffnewstart($ids) \
2430 $diffoldlno($ids) $diffnewlno($ids) \
2432 set diffinhunk($ids) 0
2433 # -1 = need to block, 0 = unblocked, 1 = is blocked
2434 set diffblocked($ids) -1
2436 if {$diffblocked($ids) == -1} {
2437 fileevent $f readable {}
2438 set diffblocked($ids) 1
2444 if {!$diffblocked($ids)} {
2446 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2447 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2450 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2451 # start of a new file
2452 set currentfile($ids) \
2453 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2454 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2455 $line match f1l f1c f2l f2c rest]} {
2456 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2457 # start of a new hunk
2458 if {$f1l == 0 && $f1c == 0} {
2461 if {$f2l == 0 && $f2c == 0} {
2464 set diffinhunk($ids) 1
2465 set diffoldstart($ids) $f1l
2466 set diffnewstart($ids) $f2l
2467 set diffoldlno($ids) $f1l
2468 set diffnewlno($ids) $f2l
2469 set difflcounts($ids) {}
2470 set noldlines($ids) 0
2471 set nnewlines($ids) 0
2476 proc processhunks {} {
2477 global diffmergeid parents nparents currenthunk
2478 global mergefilelist diffblocked mergefds
2479 global grouphunks grouplinestart grouplineend groupfilenum
2481 set nfiles [llength $mergefilelist($diffmergeid)]
2485 # look for the earliest hunk
2486 foreach p $parents($diffmergeid) {
2487 set ids [list $diffmergeid $p]
2488 if {![info exists currenthunk($ids)]} return
2489 set i [lindex $currenthunk($ids) 0]
2490 set l [lindex $currenthunk($ids) 2]
2491 if {$i < $fi || ($i == $fi && $l < $lno)} {
2498 if {$fi < $nfiles} {
2499 set ids [list $diffmergeid $pi]
2500 set hunk $currenthunk($ids)
2501 unset currenthunk($ids)
2502 if {$diffblocked($ids) > 0} {
2503 fileevent $mergefds($ids) readable \
2504 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2506 set diffblocked($ids) 0
2508 if {[info exists groupfilenum] && $groupfilenum == $fi
2509 && $lno <= $grouplineend} {
2510 # add this hunk to the pending group
2511 lappend grouphunks($pi) $hunk
2512 set endln [lindex $hunk 4]
2513 if {$endln > $grouplineend} {
2514 set grouplineend $endln
2520 # succeeding stuff doesn't belong in this group, so
2521 # process the group now
2522 if {[info exists groupfilenum]} {
2528 if {$fi >= $nfiles} break
2531 set groupfilenum $fi
2532 set grouphunks($pi) [list $hunk]
2533 set grouplinestart $lno
2534 set grouplineend [lindex $hunk 4]
2538 proc processgroup {} {
2539 global groupfilelast groupfilenum difffilestart
2540 global mergefilelist diffmergeid ctext filelines
2541 global parents diffmergeid diffoffset
2542 global grouphunks grouplinestart grouplineend nparents
2545 $ctext conf -state normal
2548 if {$groupfilelast != $f} {
2549 $ctext insert end "\n"
2550 set here [$ctext index "end - 1c"]
2551 set difffilestart($f) $here
2552 set mark fmark.[expr {$f + 1}]
2553 $ctext mark set $mark $here
2554 $ctext mark gravity $mark left
2555 set header [lindex $mergefilelist($id) $f]
2556 set l [expr {(78 - [string length $header]) / 2}]
2557 set pad [string range "----------------------------------------" 1 $l]
2558 $ctext insert end "$pad $header $pad\n" filesep
2559 set groupfilelast $f
2560 foreach p $parents($id) {
2561 set diffoffset($p) 0
2565 $ctext insert end "@@" msep
2566 set nlines [expr {$grouplineend - $grouplinestart}]
2569 foreach p $parents($id) {
2570 set startline [expr {$grouplinestart + $diffoffset($p)}]
2572 set nl $grouplinestart
2573 if {[info exists grouphunks($p)]} {
2574 foreach h $grouphunks($p) {
2577 for {} {$nl < $l} {incr nl} {
2578 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2582 foreach chunk [lindex $h 5] {
2583 if {[llength $chunk] == 2} {
2584 set olc [lindex $chunk 0]
2585 set nlc [lindex $chunk 1]
2586 set nnl [expr {$nl + $nlc}]
2587 lappend events [list $nl $nnl $pnum $olc $nlc]
2591 incr ol [lindex $chunk 0]
2592 incr nl [lindex $chunk 0]
2597 if {$nl < $grouplineend} {
2598 for {} {$nl < $grouplineend} {incr nl} {
2599 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2603 set nlines [expr {$ol - $startline}]
2604 $ctext insert end " -$startline,$nlines" msep
2608 set nlines [expr {$grouplineend - $grouplinestart}]
2609 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2611 set events [lsort -integer -index 0 $events]
2612 set nevents [llength $events]
2613 set nmerge $nparents($diffmergeid)
2614 set l $grouplinestart
2615 for {set i 0} {$i < $nevents} {set i $j} {
2616 set nl [lindex $events $i 0]
2618 $ctext insert end " $filelines($id,$f,$l)\n"
2621 set e [lindex $events $i]
2622 set enl [lindex $e 1]
2626 set pnum [lindex $e 2]
2627 set olc [lindex $e 3]
2628 set nlc [lindex $e 4]
2629 if {![info exists delta($pnum)]} {
2630 set delta($pnum) [expr {$olc - $nlc}]
2631 lappend active $pnum
2633 incr delta($pnum) [expr {$olc - $nlc}]
2635 if {[incr j] >= $nevents} break
2636 set e [lindex $events $j]
2637 if {[lindex $e 0] >= $enl} break
2638 if {[lindex $e 1] > $enl} {
2639 set enl [lindex $e 1]
2642 set nlc [expr {$enl - $l}]
2645 if {[llength $active] == $nmerge - 1} {
2646 # no diff for one of the parents, i.e. it's identical
2647 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2648 if {![info exists delta($pnum)]} {
2649 if {$pnum < $mergemax} {
2657 } elseif {[llength $active] == $nmerge} {
2658 # all parents are different, see if one is very similar
2660 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2661 set sim [similarity $pnum $l $nlc $f \
2662 [lrange $events $i [expr {$j-1}]]]
2663 if {$sim > $bestsim} {
2669 lappend ncol m$bestpn
2673 foreach p $parents($id) {
2675 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2676 set olc [expr {$nlc + $delta($pnum)}]
2677 set ol [expr {$l + $diffoffset($p)}]
2678 incr diffoffset($p) $delta($pnum)
2680 for {} {$olc > 0} {incr olc -1} {
2681 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2685 set endl [expr {$l + $nlc}]
2687 # show this pretty much as a normal diff
2688 set p [lindex $parents($id) $bestpn]
2689 set ol [expr {$l + $diffoffset($p)}]
2690 incr diffoffset($p) $delta($bestpn)
2691 unset delta($bestpn)
2692 for {set k $i} {$k < $j} {incr k} {
2693 set e [lindex $events $k]
2694 if {[lindex $e 2] != $bestpn} continue
2695 set nl [lindex $e 0]
2696 set ol [expr {$ol + $nl - $l}]
2697 for {} {$l < $nl} {incr l} {
2698 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2701 for {} {$c > 0} {incr c -1} {
2702 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2705 set nl [lindex $e 1]
2706 for {} {$l < $nl} {incr l} {
2707 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2711 for {} {$l < $endl} {incr l} {
2712 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2715 while {$l < $grouplineend} {
2716 $ctext insert end " $filelines($id,$f,$l)\n"
2719 $ctext conf -state disabled
2722 proc similarity {pnum l nlc f events} {
2723 global diffmergeid parents diffoffset filelines
2726 set p [lindex $parents($id) $pnum]
2727 set ol [expr {$l + $diffoffset($p)}]
2728 set endl [expr {$l + $nlc}]
2732 if {[lindex $e 2] != $pnum} continue
2733 set nl [lindex $e 0]
2734 set ol [expr {$ol + $nl - $l}]
2735 for {} {$l < $nl} {incr l} {
2736 incr same [string length $filelines($id,$f,$l)]
2739 set oc [lindex $e 3]
2740 for {} {$oc > 0} {incr oc -1} {
2741 incr diff [string length $filelines($p,$f,$ol)]
2745 set nl [lindex $e 1]
2746 for {} {$l < $nl} {incr l} {
2747 incr diff [string length $filelines($id,$f,$l)]
2751 for {} {$l < $endl} {incr l} {
2752 incr same [string length $filelines($id,$f,$l)]
2758 return [expr {200 * $same / (2 * $same + $diff)}]
2761 proc startdiff {ids} {
2762 global treediffs diffids treepending diffmergeid
2765 catch {unset diffmergeid}
2766 if {![info exists treediffs($ids)]} {
2767 if {![info exists treepending]} {
2775 proc addtocflist {ids} {
2776 global treediffs cflist
2777 foreach f $treediffs($ids) {
2778 $cflist insert end $f
2783 proc gettreediffs {ids} {
2784 global treediff parents treepending
2785 set treepending $ids
2787 set id [lindex $ids 0]
2788 set p [lindex $ids 1]
2789 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2790 fconfigure $gdtf -blocking 0
2791 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2794 proc gettreediffline {gdtf ids} {
2795 global treediff treediffs treepending diffids diffmergeid
2797 set n [gets $gdtf line]
2799 if {![eof $gdtf]} return
2801 set treediffs($ids) $treediff
2803 if {$ids != $diffids} {
2804 gettreediffs $diffids
2806 if {[info exists diffmergeid]} {
2814 set file [lindex $line 5]
2815 lappend treediff $file
2818 proc getblobdiffs {ids} {
2819 global diffopts blobdifffd diffids env curdifftag curtagstart
2820 global difffilestart nextupdate diffinhdr treediffs
2822 set id [lindex $ids 0]
2823 set p [lindex $ids 1]
2824 set env(GIT_DIFF_OPTS) $diffopts
2825 set cmd [list | git-diff-tree -r -p -C $p $id]
2826 if {[catch {set bdf [open $cmd r]} err]} {
2827 puts "error getting diffs: $err"
2831 fconfigure $bdf -blocking 0
2832 set blobdifffd($ids) $bdf
2833 set curdifftag Comments
2835 catch {unset difffilestart}
2836 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2837 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2840 proc getblobdiffline {bdf ids} {
2841 global diffids blobdifffd ctext curdifftag curtagstart
2842 global diffnexthead diffnextnote difffilestart
2843 global nextupdate diffinhdr treediffs
2846 set n [gets $bdf line]
2850 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2851 $ctext tag add $curdifftag $curtagstart end
2856 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2859 $ctext conf -state normal
2860 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2861 # start of a new file
2862 $ctext insert end "\n"
2863 $ctext tag add $curdifftag $curtagstart end
2864 set curtagstart [$ctext index "end - 1c"]
2866 set here [$ctext index "end - 1c"]
2867 set i [lsearch -exact $treediffs($diffids) $fname]
2869 set difffilestart($i) $here
2871 $ctext mark set fmark.$i $here
2872 $ctext mark gravity fmark.$i left
2874 if {$newname != $fname} {
2875 set i [lsearch -exact $treediffs($diffids) $newname]
2877 set difffilestart($i) $here
2879 $ctext mark set fmark.$i $here
2880 $ctext mark gravity fmark.$i left
2883 set curdifftag "f:$fname"
2884 $ctext tag delete $curdifftag
2885 set l [expr {(78 - [string length $header]) / 2}]
2886 set pad [string range "----------------------------------------" 1 $l]
2887 $ctext insert end "$pad $header $pad\n" filesep
2889 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2891 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2892 $line match f1l f1c f2l f2c rest]} {
2894 $ctext insert end "\t" hunksep
2895 $ctext insert end " $f1l " d0 " $f2l " d1
2896 $ctext insert end " $rest \n" hunksep
2898 $ctext insert end "$line\n" hunksep
2902 set x [string range $line 0 0]
2903 if {$x == "-" || $x == "+"} {
2904 set tag [expr {$x == "+"}]
2906 set line [string range $line 1 end]
2908 $ctext insert end "$line\n" d$tag
2909 } elseif {$x == " "} {
2911 set line [string range $line 1 end]
2913 $ctext insert end "$line\n"
2914 } elseif {$diffinhdr || $x == "\\"} {
2915 # e.g. "\ No newline at end of file"
2916 $ctext insert end "$line\n" filesep
2918 # Something else we don't recognize
2919 if {$curdifftag != "Comments"} {
2920 $ctext insert end "\n"
2921 $ctext tag add $curdifftag $curtagstart end
2922 set curtagstart [$ctext index "end - 1c"]
2923 set curdifftag Comments
2925 $ctext insert end "$line\n" filesep
2928 $ctext conf -state disabled
2929 if {[clock clicks -milliseconds] >= $nextupdate} {
2931 fileevent $bdf readable {}
2933 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2938 global difffilestart ctext
2939 set here [$ctext index @0,0]
2940 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2941 if {[$ctext compare $difffilestart($i) > $here]} {
2942 if {![info exists pos]
2943 || [$ctext compare $difffilestart($i) < $pos]} {
2944 set pos $difffilestart($i)
2948 if {[info exists pos]} {
2953 proc listboxsel {} {
2954 global ctext cflist currentid
2955 if {![info exists currentid]} return
2956 set sel [lsort [$cflist curselection]]
2957 if {$sel eq {}} return
2958 set first [lindex $sel 0]
2959 catch {$ctext yview fmark.$first}
2963 global linespc charspc canvx0 canvy0 mainfont
2964 global xspc1 xspc2 lthickness
2966 set linespc [font metrics $mainfont -linespace]
2967 set charspc [font measure $mainfont "m"]
2968 set canvy0 [expr 3 + 0.5 * $linespc]
2969 set canvx0 [expr 3 + 0.5 * $linespc]
2970 set lthickness [expr {int($linespc / 9) + 1}]
2971 set xspc1(0) $linespc
2976 global stopped redisplaying phase
2977 if {$stopped > 1} return
2978 if {$phase == "getcommits"} return
2980 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2987 proc incrfont {inc} {
2988 global mainfont namefont textfont ctext canv phase
2989 global stopped entries
2991 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2992 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2993 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2995 $ctext conf -font $textfont
2996 $ctext tag conf filesep -font [concat $textfont bold]
2997 foreach e $entries {
2998 $e conf -font $mainfont
3000 if {$phase == "getcommits"} {
3001 $canv itemconf textitems -font $mainfont
3007 global sha1entry sha1string
3008 if {[string length $sha1string] == 40} {
3009 $sha1entry delete 0 end
3013 proc sha1change {n1 n2 op} {
3014 global sha1string currentid sha1but
3015 if {$sha1string == {}
3016 || ([info exists currentid] && $sha1string == $currentid)} {
3021 if {[$sha1but cget -state] == $state} return
3022 if {$state == "normal"} {
3023 $sha1but conf -state normal -relief raised -text "Goto: "
3025 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3029 proc gotocommit {} {
3030 global sha1string currentid idline tagids
3031 global lineid numcommits
3033 if {$sha1string == {}
3034 || ([info exists currentid] && $sha1string == $currentid)} return
3035 if {[info exists tagids($sha1string)]} {
3036 set id $tagids($sha1string)
3038 set id [string tolower $sha1string]
3039 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3041 for {set l 0} {$l < $numcommits} {incr l} {
3042 if {[string match $id* $lineid($l)]} {
3043 lappend matches $lineid($l)
3046 if {$matches ne {}} {
3047 if {[llength $matches] > 1} {
3048 error_popup "Short SHA1 id $id is ambiguous"
3051 set id [lindex $matches 0]
3055 if {[info exists idline($id)]} {
3056 selectline $idline($id) 1
3059 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3064 error_popup "$type $sha1string is not known"
3067 proc lineenter {x y id} {
3068 global hoverx hovery hoverid hovertimer
3069 global commitinfo canv
3071 if {![info exists commitinfo($id)]} return
3075 if {[info exists hovertimer]} {
3076 after cancel $hovertimer
3078 set hovertimer [after 500 linehover]
3082 proc linemotion {x y id} {
3083 global hoverx hovery hoverid hovertimer
3085 if {[info exists hoverid] && $id == $hoverid} {
3088 if {[info exists hovertimer]} {
3089 after cancel $hovertimer
3091 set hovertimer [after 500 linehover]
3095 proc lineleave {id} {
3096 global hoverid hovertimer canv
3098 if {[info exists hoverid] && $id == $hoverid} {
3100 if {[info exists hovertimer]} {
3101 after cancel $hovertimer
3109 global hoverx hovery hoverid hovertimer
3110 global canv linespc lthickness
3111 global commitinfo mainfont
3113 set text [lindex $commitinfo($hoverid) 0]
3114 set ymax [lindex [$canv cget -scrollregion] 3]
3115 if {$ymax == {}} return
3116 set yfrac [lindex [$canv yview] 0]
3117 set x [expr {$hoverx + 2 * $linespc}]
3118 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3119 set x0 [expr {$x - 2 * $lthickness}]
3120 set y0 [expr {$y - 2 * $lthickness}]
3121 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3122 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3123 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3124 -fill \#ffff80 -outline black -width 1 -tags hover]
3126 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3130 proc lineclick {x y id isnew} {
3131 global ctext commitinfo children cflist canv
3136 addtohistory [list lineclick $x $x $id 0]
3139 # fill the details pane with info about this line
3140 $ctext conf -state normal
3141 $ctext delete 0.0 end
3142 $ctext tag conf link -foreground blue -underline 1
3143 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3144 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3145 $ctext insert end "Parent:\t"
3146 $ctext insert end $id [list link link0]
3147 $ctext tag bind link0 <1> [list selbyid $id]
3148 set info $commitinfo($id)
3149 $ctext insert end "\n\t[lindex $info 0]\n"
3150 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3151 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3152 if {[info exists children($id)]} {
3153 $ctext insert end "\nChildren:"
3155 foreach child $children($id) {
3157 set info $commitinfo($child)
3158 $ctext insert end "\n\t"
3159 $ctext insert end $child [list link link$i]
3160 $ctext tag bind link$i <1> [list selbyid $child]
3161 $ctext insert end "\n\t[lindex $info 0]"
3162 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3163 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3166 $ctext conf -state disabled
3168 $cflist delete 0 end
3173 if {[info exists idline($id)]} {
3174 selectline $idline($id) 1
3180 if {![info exists startmstime]} {
3181 set startmstime [clock clicks -milliseconds]
3183 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3186 proc rowmenu {x y id} {
3187 global rowctxmenu idline selectedline rowmenuid
3189 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3194 $rowctxmenu entryconfigure 0 -state $state
3195 $rowctxmenu entryconfigure 1 -state $state
3196 $rowctxmenu entryconfigure 2 -state $state
3198 tk_popup $rowctxmenu $x $y
3201 proc diffvssel {dirn} {
3202 global rowmenuid selectedline lineid
3204 if {![info exists selectedline]} return
3206 set oldid $lineid($selectedline)
3207 set newid $rowmenuid
3209 set oldid $rowmenuid
3210 set newid $lineid($selectedline)
3212 addtohistory [list doseldiff $oldid $newid]
3213 doseldiff $oldid $newid
3216 proc doseldiff {oldid newid} {
3220 $ctext conf -state normal
3221 $ctext delete 0.0 end
3222 $ctext mark set fmark.0 0.0
3223 $ctext mark gravity fmark.0 left
3224 $cflist delete 0 end
3225 $cflist insert end "Top"
3226 $ctext insert end "From "
3227 $ctext tag conf link -foreground blue -underline 1
3228 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3229 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3230 $ctext tag bind link0 <1> [list selbyid $oldid]
3231 $ctext insert end $oldid [list link link0]
3232 $ctext insert end "\n "
3233 $ctext insert end [lindex $commitinfo($oldid) 0]
3234 $ctext insert end "\n\nTo "
3235 $ctext tag bind link1 <1> [list selbyid $newid]
3236 $ctext insert end $newid [list link link1]
3237 $ctext insert end "\n "
3238 $ctext insert end [lindex $commitinfo($newid) 0]
3239 $ctext insert end "\n"
3240 $ctext conf -state disabled
3241 $ctext tag delete Comments
3242 $ctext tag remove found 1.0 end
3243 startdiff [list $newid $oldid]
3247 global rowmenuid currentid commitinfo patchtop patchnum
3249 if {![info exists currentid]} return
3250 set oldid $currentid
3251 set oldhead [lindex $commitinfo($oldid) 0]
3252 set newid $rowmenuid
3253 set newhead [lindex $commitinfo($newid) 0]
3256 catch {destroy $top}
3258 label $top.title -text "Generate patch"
3259 grid $top.title - -pady 10
3260 label $top.from -text "From:"
3261 entry $top.fromsha1 -width 40 -relief flat
3262 $top.fromsha1 insert 0 $oldid
3263 $top.fromsha1 conf -state readonly
3264 grid $top.from $top.fromsha1 -sticky w
3265 entry $top.fromhead -width 60 -relief flat
3266 $top.fromhead insert 0 $oldhead
3267 $top.fromhead conf -state readonly
3268 grid x $top.fromhead -sticky w
3269 label $top.to -text "To:"
3270 entry $top.tosha1 -width 40 -relief flat
3271 $top.tosha1 insert 0 $newid
3272 $top.tosha1 conf -state readonly
3273 grid $top.to $top.tosha1 -sticky w
3274 entry $top.tohead -width 60 -relief flat
3275 $top.tohead insert 0 $newhead
3276 $top.tohead conf -state readonly
3277 grid x $top.tohead -sticky w
3278 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3279 grid $top.rev x -pady 10
3280 label $top.flab -text "Output file:"
3281 entry $top.fname -width 60
3282 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3284 grid $top.flab $top.fname -sticky w
3286 button $top.buts.gen -text "Generate" -command mkpatchgo
3287 button $top.buts.can -text "Cancel" -command mkpatchcan
3288 grid $top.buts.gen $top.buts.can
3289 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3290 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3291 grid $top.buts - -pady 10 -sticky ew
3295 proc mkpatchrev {} {
3298 set oldid [$patchtop.fromsha1 get]
3299 set oldhead [$patchtop.fromhead get]
3300 set newid [$patchtop.tosha1 get]
3301 set newhead [$patchtop.tohead get]
3302 foreach e [list fromsha1 fromhead tosha1 tohead] \
3303 v [list $newid $newhead $oldid $oldhead] {
3304 $patchtop.$e conf -state normal
3305 $patchtop.$e delete 0 end
3306 $patchtop.$e insert 0 $v
3307 $patchtop.$e conf -state readonly
3314 set oldid [$patchtop.fromsha1 get]
3315 set newid [$patchtop.tosha1 get]
3316 set fname [$patchtop.fname get]
3317 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3318 error_popup "Error creating patch: $err"
3320 catch {destroy $patchtop}
3324 proc mkpatchcan {} {
3327 catch {destroy $patchtop}
3332 global rowmenuid mktagtop commitinfo
3336 catch {destroy $top}
3338 label $top.title -text "Create tag"
3339 grid $top.title - -pady 10
3340 label $top.id -text "ID:"
3341 entry $top.sha1 -width 40 -relief flat
3342 $top.sha1 insert 0 $rowmenuid
3343 $top.sha1 conf -state readonly
3344 grid $top.id $top.sha1 -sticky w
3345 entry $top.head -width 60 -relief flat
3346 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3347 $top.head conf -state readonly
3348 grid x $top.head -sticky w
3349 label $top.tlab -text "Tag name:"
3350 entry $top.tag -width 60
3351 grid $top.tlab $top.tag -sticky w
3353 button $top.buts.gen -text "Create" -command mktaggo
3354 button $top.buts.can -text "Cancel" -command mktagcan
3355 grid $top.buts.gen $top.buts.can
3356 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3357 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3358 grid $top.buts - -pady 10 -sticky ew
3363 global mktagtop env tagids idtags
3365 set id [$mktagtop.sha1 get]
3366 set tag [$mktagtop.tag get]
3368 error_popup "No tag name specified"
3371 if {[info exists tagids($tag)]} {
3372 error_popup "Tag \"$tag\" already exists"
3377 set fname [file join $dir "refs/tags" $tag]
3378 set f [open $fname w]
3382 error_popup "Error creating tag: $err"
3386 set tagids($tag) $id
3387 lappend idtags($id) $tag
3391 proc redrawtags {id} {
3392 global canv linehtag idline idpos selectedline
3394 if {![info exists idline($id)]} return
3395 $canv delete tag.$id
3396 set xt [eval drawtags $id $idpos($id)]
3397 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3398 if {[info exists selectedline] && $selectedline == $idline($id)} {
3399 selectline $selectedline 0
3406 catch {destroy $mktagtop}
3415 proc writecommit {} {
3416 global rowmenuid wrcomtop commitinfo wrcomcmd
3418 set top .writecommit
3420 catch {destroy $top}
3422 label $top.title -text "Write commit to file"
3423 grid $top.title - -pady 10
3424 label $top.id -text "ID:"
3425 entry $top.sha1 -width 40 -relief flat
3426 $top.sha1 insert 0 $rowmenuid
3427 $top.sha1 conf -state readonly
3428 grid $top.id $top.sha1 -sticky w
3429 entry $top.head -width 60 -relief flat
3430 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3431 $top.head conf -state readonly
3432 grid x $top.head -sticky w
3433 label $top.clab -text "Command:"
3434 entry $top.cmd -width 60 -textvariable wrcomcmd
3435 grid $top.clab $top.cmd -sticky w -pady 10
3436 label $top.flab -text "Output file:"
3437 entry $top.fname -width 60
3438 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3439 grid $top.flab $top.fname -sticky w
3441 button $top.buts.gen -text "Write" -command wrcomgo
3442 button $top.buts.can -text "Cancel" -command wrcomcan
3443 grid $top.buts.gen $top.buts.can
3444 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3445 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3446 grid $top.buts - -pady 10 -sticky ew
3453 set id [$wrcomtop.sha1 get]
3454 set cmd "echo $id | [$wrcomtop.cmd get]"
3455 set fname [$wrcomtop.fname get]
3456 if {[catch {exec sh -c $cmd >$fname &} err]} {
3457 error_popup "Error writing commit: $err"
3459 catch {destroy $wrcomtop}
3466 catch {destroy $wrcomtop}
3470 proc listrefs {id} {
3471 global idtags idheads idotherrefs
3474 if {[info exists idtags($id)]} {
3478 if {[info exists idheads($id)]} {
3482 if {[info exists idotherrefs($id)]} {
3483 set z $idotherrefs($id)
3485 return [list $x $y $z]
3488 proc rereadrefs {} {
3489 global idtags idheads idotherrefs
3490 global tagids headids otherrefids
3492 set refids [concat [array names idtags] \
3493 [array names idheads] [array names idotherrefs]]
3494 foreach id $refids {
3495 if {![info exists ref($id)]} {
3496 set ref($id) [listrefs $id]
3499 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3503 set refids [lsort -unique [concat $refids [array names idtags] \
3504 [array names idheads] [array names idotherrefs]]]
3505 foreach id $refids {
3506 set v [listrefs $id]
3507 if {![info exists ref($id)] || $ref($id) != $v} {
3513 proc showtag {tag isnew} {
3514 global ctext cflist tagcontents tagids linknum
3517 addtohistory [list showtag $tag 0]
3519 $ctext conf -state normal
3520 $ctext delete 0.0 end
3522 if {[info exists tagcontents($tag)]} {
3523 set text $tagcontents($tag)
3525 set text "Tag: $tag\nId: $tagids($tag)"
3527 appendwithlinks $text
3528 $ctext conf -state disabled
3529 $cflist delete 0 end
3541 set diffopts "-U 5 -p"
3542 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3544 set mainfont {Helvetica 9}
3545 set textfont {Courier 9}
3546 set findmergefiles 0
3551 set colors {green red blue magenta darkgrey brown orange}
3553 catch {source ~/.gitk}
3555 set namefont $mainfont
3557 lappend namefont bold
3562 switch -regexp -- $arg {
3564 "^-b" { set boldnames 1 }
3565 "^-d" { set datemode 1 }
3567 lappend revtreeargs $arg
3582 getcommits $revtreeargs