2 # Tcl ignores the next line -*- tcl -*- \
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 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 set hdrend [string first "\n\n" $contents]
201 # should never happen...
202 set hdrend [string length $contents]
204 set header [string range $contents 0 [expr {$hdrend - 1}]]
205 set comment [string range $contents [expr {$hdrend + 2}] end]
206 foreach line [split $header "\n"] {
207 set tag [lindex $line 0]
208 if {$tag == "author"} {
209 set audate [lindex $line end-1]
210 set auname [lrange $line 1 end-2]
211 } elseif {$tag == "committer"} {
212 set comdate [lindex $line end-1]
213 set comname [lrange $line 1 end-2]
217 # take the first line of the comment as the headline
218 set i [string first "\n" $comment]
220 set headline [string trim [string range $comment 0 $i]]
223 # git-rev-list indents the comment by 4 spaces;
224 # if we got this via git-cat-file, add the indentation
226 foreach line [split $comment "\n"] {
227 append newcomment " "
228 append newcomment $line
230 set comment $newcomment
232 if {$comdate != {}} {
233 set cdate($id) $comdate
235 set commitinfo($id) [list $headline $auname $audate \
236 $comname $comdate $comment]
240 global tagids idtags headids idheads tagcontents
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 tagblob [exec git-cat-file tag $id]
252 set contents [split $tagblob "\n"]
256 foreach l $contents {
258 switch -- [lindex $l 0] {
259 "object" {set obj [lindex $l 1]}
260 "type" {set type [lindex $l 1]}
261 "tag" {set tag [string range $l 4 end]}
264 if {$obj != {} && $type == "commit" && $tag != {}} {
265 set tagids($tag) $obj
266 lappend idtags($obj) $tag
267 set tagcontents($tag) $tagblob
273 set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
277 set line [read $fd 40]
278 if {[regexp {^[0-9a-f]{40}} $line id]} {
279 set head [file tail $f]
280 set headids($head) $line
281 lappend idheads($line) $head
286 readotherrefs refs {} {tags heads}
289 proc readotherrefs {base dname excl} {
290 global otherrefids idotherrefs
293 set files [glob -nocomplain -types f [file join $git $base *]]
297 set line [read $fd 40]
298 if {[regexp {^[0-9a-f]{40}} $line id]} {
299 set name "$dname[file tail $f]"
300 set otherrefids($name) $id
301 lappend idotherrefs($id) $name
306 set dirs [glob -nocomplain -types d [file join $git $base *]]
308 set dir [file tail $d]
309 if {[lsearch -exact $excl $dir] >= 0} continue
310 readotherrefs [file join $base $dir] "$dname$dir/" {}
314 proc error_popup msg {
318 message $w.m -text $msg -justify center -aspect 400
319 pack $w.m -side top -fill x -padx 20 -pady 20
320 button $w.ok -text OK -command "destroy $w"
321 pack $w.ok -side bottom -fill x
322 bind $w <Visibility> "grab $w; focus $w"
327 global canv canv2 canv3 linespc charspc ctext cflist textfont
328 global findtype findtypemenu findloc findstring fstring geometry
329 global entries sha1entry sha1string sha1but
330 global maincursor textcursor curtextcursor
331 global rowctxmenu gaudydiff mergemax
334 .bar add cascade -label "File" -menu .bar.file
336 .bar.file add command -label "Reread references" -command rereadrefs
337 .bar.file add command -label "Quit" -command doquit
339 .bar add cascade -label "Help" -menu .bar.help
340 .bar.help add command -label "About gitk" -command about
341 . configure -menu .bar
343 if {![info exists geometry(canv1)]} {
344 set geometry(canv1) [expr 45 * $charspc]
345 set geometry(canv2) [expr 30 * $charspc]
346 set geometry(canv3) [expr 15 * $charspc]
347 set geometry(canvh) [expr 25 * $linespc + 4]
348 set geometry(ctextw) 80
349 set geometry(ctexth) 30
350 set geometry(cflistw) 30
352 panedwindow .ctop -orient vertical
353 if {[info exists geometry(width)]} {
354 .ctop conf -width $geometry(width) -height $geometry(height)
355 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
356 set geometry(ctexth) [expr {($texth - 8) /
357 [font metrics $textfont -linespace]}]
361 pack .ctop.top.bar -side bottom -fill x
362 set cscroll .ctop.top.csb
363 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
364 pack $cscroll -side right -fill y
365 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
366 pack .ctop.top.clist -side top -fill both -expand 1
368 set canv .ctop.top.clist.canv
369 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
371 -yscrollincr $linespc -yscrollcommand "$cscroll set"
372 .ctop.top.clist add $canv
373 set canv2 .ctop.top.clist.canv2
374 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
375 -bg white -bd 0 -yscrollincr $linespc
376 .ctop.top.clist add $canv2
377 set canv3 .ctop.top.clist.canv3
378 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
379 -bg white -bd 0 -yscrollincr $linespc
380 .ctop.top.clist add $canv3
381 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
383 set sha1entry .ctop.top.bar.sha1
384 set entries $sha1entry
385 set sha1but .ctop.top.bar.sha1label
386 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
387 -command gotocommit -width 8
388 $sha1but conf -disabledforeground [$sha1but cget -foreground]
389 pack .ctop.top.bar.sha1label -side left
390 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
391 trace add variable sha1string write sha1change
392 pack $sha1entry -side left -pady 2
394 image create bitmap bm-left -data {
395 #define left_width 16
396 #define left_height 16
397 static unsigned char left_bits[] = {
398 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
399 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
400 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
402 image create bitmap bm-right -data {
403 #define right_width 16
404 #define right_height 16
405 static unsigned char right_bits[] = {
406 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
407 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
408 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
410 button .ctop.top.bar.leftbut -image bm-left -command goback \
411 -state disabled -width 26
412 pack .ctop.top.bar.leftbut -side left -fill y
413 button .ctop.top.bar.rightbut -image bm-right -command goforw \
414 -state disabled -width 26
415 pack .ctop.top.bar.rightbut -side left -fill y
417 button .ctop.top.bar.findbut -text "Find" -command dofind
418 pack .ctop.top.bar.findbut -side left
420 set fstring .ctop.top.bar.findstring
421 lappend entries $fstring
422 entry $fstring -width 30 -font $textfont -textvariable findstring
423 pack $fstring -side left -expand 1 -fill x
425 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
426 findtype Exact IgnCase Regexp]
427 set findloc "All fields"
428 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
429 Comments Author Committer Files Pickaxe
430 pack .ctop.top.bar.findloc -side right
431 pack .ctop.top.bar.findtype -side right
432 # for making sure type==Exact whenever loc==Pickaxe
433 trace add variable findloc write findlocchange
435 panedwindow .ctop.cdet -orient horizontal
437 frame .ctop.cdet.left
438 set ctext .ctop.cdet.left.ctext
439 text $ctext -bg white -state disabled -font $textfont \
440 -width $geometry(ctextw) -height $geometry(ctexth) \
441 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
442 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
443 pack .ctop.cdet.left.sb -side right -fill y
444 pack $ctext -side left -fill both -expand 1
445 .ctop.cdet add .ctop.cdet.left
447 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
449 $ctext tag conf hunksep -back blue -fore white
450 $ctext tag conf d0 -back "#ff8080"
451 $ctext tag conf d1 -back green
453 $ctext tag conf hunksep -fore blue
454 $ctext tag conf d0 -fore red
455 $ctext tag conf d1 -fore "#00a000"
456 $ctext tag conf m0 -fore red
457 $ctext tag conf m1 -fore blue
458 $ctext tag conf m2 -fore green
459 $ctext tag conf m3 -fore purple
460 $ctext tag conf m4 -fore brown
461 $ctext tag conf mmax -fore darkgrey
463 $ctext tag conf mresult -font [concat $textfont bold]
464 $ctext tag conf msep -font [concat $textfont bold]
465 $ctext tag conf found -back yellow
468 frame .ctop.cdet.right
469 set cflist .ctop.cdet.right.cfiles
470 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
471 -yscrollcommand ".ctop.cdet.right.sb set"
472 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
473 pack .ctop.cdet.right.sb -side right -fill y
474 pack $cflist -side left -fill both -expand 1
475 .ctop.cdet add .ctop.cdet.right
476 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
478 pack .ctop -side top -fill both -expand 1
480 bindall <1> {selcanvline %W %x %y}
481 #bindall <B1-Motion> {selcanvline %W %x %y}
482 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
483 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
484 bindall <2> "allcanvs scan mark 0 %y"
485 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
486 bind . <Key-Up> "selnextline -1"
487 bind . <Key-Down> "selnextline 1"
488 bind . <Key-Right> "goforw"
489 bind . <Key-Left> "goback"
490 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
491 bind . <Key-Next> "allcanvs yview scroll 1 pages"
492 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
493 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
494 bindkey <Key-space> "$ctext yview scroll 1 pages"
495 bindkey p "selnextline -1"
496 bindkey n "selnextline 1"
499 bindkey i "selnextline -1"
500 bindkey k "selnextline 1"
503 bindkey b "$ctext yview scroll -1 pages"
504 bindkey d "$ctext yview scroll 18 units"
505 bindkey u "$ctext yview scroll -18 units"
506 bindkey / {findnext 1}
507 bindkey <Key-Return> {findnext 0}
510 bind . <Control-q> doquit
511 bind . <Control-f> dofind
512 bind . <Control-g> {findnext 0}
513 bind . <Control-r> findprev
514 bind . <Control-equal> {incrfont 1}
515 bind . <Control-KP_Add> {incrfont 1}
516 bind . <Control-minus> {incrfont -1}
517 bind . <Control-KP_Subtract> {incrfont -1}
518 bind $cflist <<ListboxSelect>> listboxsel
519 bind . <Destroy> {savestuff %W}
520 bind . <Button-1> "click %W"
521 bind $fstring <Key-Return> dofind
522 bind $sha1entry <Key-Return> gotocommit
523 bind $sha1entry <<PasteSelection>> clearsha1
525 set maincursor [. cget -cursor]
526 set textcursor [$ctext cget -cursor]
527 set curtextcursor $textcursor
529 set rowctxmenu .rowctxmenu
530 menu $rowctxmenu -tearoff 0
531 $rowctxmenu add command -label "Diff this -> selected" \
532 -command {diffvssel 0}
533 $rowctxmenu add command -label "Diff selected -> this" \
534 -command {diffvssel 1}
535 $rowctxmenu add command -label "Make patch" -command mkpatch
536 $rowctxmenu add command -label "Create tag" -command mktag
537 $rowctxmenu add command -label "Write commit to file" -command writecommit
540 # when we make a key binding for the toplevel, make sure
541 # it doesn't get triggered when that key is pressed in the
542 # find string entry widget.
543 proc bindkey {ev script} {
546 set escript [bind Entry $ev]
547 if {$escript == {}} {
548 set escript [bind Entry <Key>]
551 bind $e $ev "$escript; break"
555 # set the focus back to the toplevel for any click outside
566 global canv canv2 canv3 ctext cflist mainfont textfont
567 global stuffsaved findmergefiles gaudydiff maxgraphpct
570 if {$stuffsaved} return
571 if {![winfo viewable .]} return
573 set f [open "~/.gitk-new" w]
574 puts $f [list set mainfont $mainfont]
575 puts $f [list set textfont $textfont]
576 puts $f [list set findmergefiles $findmergefiles]
577 puts $f [list set gaudydiff $gaudydiff]
578 puts $f [list set maxgraphpct $maxgraphpct]
579 puts $f [list set maxwidth $maxwidth]
580 puts $f "set geometry(width) [winfo width .ctop]"
581 puts $f "set geometry(height) [winfo height .ctop]"
582 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
583 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
584 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
585 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
586 set wid [expr {([winfo width $ctext] - 8) \
587 / [font measure $textfont "0"]}]
588 puts $f "set geometry(ctextw) $wid"
589 set wid [expr {([winfo width $cflist] - 11) \
590 / [font measure [$cflist cget -font] "0"]}]
591 puts $f "set geometry(cflistw) $wid"
593 file rename -force "~/.gitk-new" "~/.gitk"
598 proc resizeclistpanes {win w} {
600 if [info exists oldwidth($win)] {
601 set s0 [$win sash coord 0]
602 set s1 [$win sash coord 1]
604 set sash0 [expr {int($w/2 - 2)}]
605 set sash1 [expr {int($w*5/6 - 2)}]
607 set factor [expr {1.0 * $w / $oldwidth($win)}]
608 set sash0 [expr {int($factor * [lindex $s0 0])}]
609 set sash1 [expr {int($factor * [lindex $s1 0])}]
613 if {$sash1 < $sash0 + 20} {
614 set sash1 [expr $sash0 + 20]
616 if {$sash1 > $w - 10} {
617 set sash1 [expr $w - 10]
618 if {$sash0 > $sash1 - 20} {
619 set sash0 [expr $sash1 - 20]
623 $win sash place 0 $sash0 [lindex $s0 1]
624 $win sash place 1 $sash1 [lindex $s1 1]
626 set oldwidth($win) $w
629 proc resizecdetpanes {win w} {
631 if [info exists oldwidth($win)] {
632 set s0 [$win sash coord 0]
634 set sash0 [expr {int($w*3/4 - 2)}]
636 set factor [expr {1.0 * $w / $oldwidth($win)}]
637 set sash0 [expr {int($factor * [lindex $s0 0])}]
641 if {$sash0 > $w - 15} {
642 set sash0 [expr $w - 15]
645 $win sash place 0 $sash0 [lindex $s0 1]
647 set oldwidth($win) $w
651 global canv canv2 canv3
657 proc bindall {event action} {
658 global canv canv2 canv3
659 bind $canv $event $action
660 bind $canv2 $event $action
661 bind $canv3 $event $action
666 if {[winfo exists $w]} {
671 wm title $w "About gitk"
675 Copyright © 2005 Paul Mackerras
677 Use and redistribute under the terms of the GNU General Public License} \
678 -justify center -aspect 400
679 pack $w.m -side top -fill x -padx 20 -pady 20
680 button $w.ok -text Close -command "destroy $w"
681 pack $w.ok -side bottom
684 proc assigncolor {id} {
685 global colormap commcolors colors nextcolor
686 global parents nparents children nchildren
687 global cornercrossings crossings
689 if [info exists colormap($id)] return
690 set ncolors [llength $colors]
691 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
692 set child [lindex $children($id) 0]
693 if {[info exists colormap($child)]
694 && $nparents($child) == 1} {
695 set colormap($id) $colormap($child)
700 if {[info exists cornercrossings($id)]} {
701 foreach x $cornercrossings($id) {
702 if {[info exists colormap($x)]
703 && [lsearch -exact $badcolors $colormap($x)] < 0} {
704 lappend badcolors $colormap($x)
707 if {[llength $badcolors] >= $ncolors} {
711 set origbad $badcolors
712 if {[llength $badcolors] < $ncolors - 1} {
713 if {[info exists crossings($id)]} {
714 foreach x $crossings($id) {
715 if {[info exists colormap($x)]
716 && [lsearch -exact $badcolors $colormap($x)] < 0} {
717 lappend badcolors $colormap($x)
720 if {[llength $badcolors] >= $ncolors} {
721 set badcolors $origbad
724 set origbad $badcolors
726 if {[llength $badcolors] < $ncolors - 1} {
727 foreach child $children($id) {
728 if {[info exists colormap($child)]
729 && [lsearch -exact $badcolors $colormap($child)] < 0} {
730 lappend badcolors $colormap($child)
732 if {[info exists parents($child)]} {
733 foreach p $parents($child) {
734 if {[info exists colormap($p)]
735 && [lsearch -exact $badcolors $colormap($p)] < 0} {
736 lappend badcolors $colormap($p)
741 if {[llength $badcolors] >= $ncolors} {
742 set badcolors $origbad
745 for {set i 0} {$i <= $ncolors} {incr i} {
746 set c [lindex $colors $nextcolor]
747 if {[incr nextcolor] >= $ncolors} {
750 if {[lsearch -exact $badcolors $c]} break
756 global canvy canvy0 lineno numcommits nextcolor linespc
757 global mainline mainlinearrow sidelines
758 global nchildren ncleft
759 global displist nhyperspace
766 catch {unset mainline}
767 catch {unset mainlinearrow}
768 catch {unset sidelines}
769 foreach id [array names nchildren] {
770 set ncleft($id) $nchildren($id)
776 proc bindline {t id} {
779 $canv bind $t <Enter> "lineenter %x %y $id"
780 $canv bind $t <Motion> "linemotion %x %y $id"
781 $canv bind $t <Leave> "lineleave $id"
782 $canv bind $t <Button-1> "lineclick %x %y $id 1"
785 proc drawlines {id xtra delold} {
786 global mainline mainlinearrow sidelines lthickness colormap canv
789 $canv delete lines.$id
791 if {[info exists mainline($id)]} {
792 set t [$canv create line $mainline($id) \
793 -width [expr {($xtra + 1) * $lthickness}] \
794 -fill $colormap($id) -tags lines.$id \
795 -arrow $mainlinearrow($id)]
799 if {[info exists sidelines($id)]} {
800 foreach ls $sidelines($id) {
801 set coords [lindex $ls 0]
802 set thick [lindex $ls 1]
803 set arrow [lindex $ls 2]
804 set t [$canv create line $coords -fill $colormap($id) \
805 -width [expr {($thick + $xtra) * $lthickness}] \
806 -arrow $arrow -tags lines.$id]
813 # level here is an index in displist
814 proc drawcommitline {level} {
815 global parents children nparents displist
816 global canv canv2 canv3 mainfont namefont canvy linespc
817 global lineid linehtag linentag linedtag commitinfo
818 global colormap numcommits currentparents dupparents
819 global idtags idline idheads idotherrefs
820 global lineno lthickness mainline mainlinearrow sidelines
821 global commitlisted rowtextx idpos lastuse displist
822 global oldnlines olddlevel olddisplist
826 set id [lindex $displist $level]
827 set lastuse($id) $lineno
828 set lineid($lineno) $id
829 set idline($id) $lineno
830 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
831 if {![info exists commitinfo($id)]} {
833 if {![info exists commitinfo($id)]} {
834 set commitinfo($id) {"No commit information available"}
839 set currentparents {}
841 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
842 foreach p $parents($id) {
843 if {[lsearch -exact $currentparents $p] < 0} {
844 lappend currentparents $p
846 # remember that this parent was listed twice
847 lappend dupparents $p
851 set x [xcoord $level $level $lineno]
853 set canvy [expr $canvy + $linespc]
854 allcanvs conf -scrollregion \
855 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
856 if {[info exists mainline($id)]} {
857 lappend mainline($id) $x $y1
858 if {$mainlinearrow($id) ne "none"} {
859 set mainline($id) [trimdiagstart $mainline($id)]
863 set orad [expr {$linespc / 3}]
864 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
865 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
866 -fill $ofill -outline black -width 1]
868 $canv bind $t <1> {selcanvline {} %x %y}
869 set xt [xcoord [llength $displist] $level $lineno]
870 if {[llength $currentparents] > 2} {
871 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
873 set rowtextx($lineno) $xt
874 set idpos($id) [list $x $xt $y1]
875 if {[info exists idtags($id)] || [info exists idheads($id)]
876 || [info exists idotherrefs($id)]} {
877 set xt [drawtags $id $x $xt $y1]
879 set headline [lindex $commitinfo($id) 0]
880 set name [lindex $commitinfo($id) 1]
881 set date [lindex $commitinfo($id) 2]
882 set date [formatdate $date]
883 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
884 -text $headline -font $mainfont ]
885 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
886 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
887 -text $name -font $namefont]
888 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
889 -text $date -font $mainfont]
892 set olddisplist $displist
893 set oldnlines [llength $displist]
896 proc drawtags {id x xt y1} {
897 global idtags idheads idotherrefs
898 global linespc lthickness
899 global canv mainfont idline rowtextx
904 if {[info exists idtags($id)]} {
905 set marks $idtags($id)
906 set ntags [llength $marks]
908 if {[info exists idheads($id)]} {
909 set marks [concat $marks $idheads($id)]
910 set nheads [llength $idheads($id)]
912 if {[info exists idotherrefs($id)]} {
913 set marks [concat $marks $idotherrefs($id)]
919 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
920 set yt [expr $y1 - 0.5 * $linespc]
921 set yb [expr $yt + $linespc - 1]
925 set wid [font measure $mainfont $tag]
928 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
930 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
931 -width $lthickness -fill black -tags tag.$id]
933 foreach tag $marks x $xvals wid $wvals {
934 set xl [expr $x + $delta]
935 set xr [expr $x + $delta + $wid + $lthickness]
936 if {[incr ntags -1] >= 0} {
938 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
939 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
940 -width 1 -outline black -fill yellow -tags tag.$id]
941 $canv bind $t <1> [list showtag $tag 1]
942 set rowtextx($idline($id)) [expr {$xr + $linespc}]
944 # draw a head or other ref
945 if {[incr nheads -1] >= 0} {
950 set xl [expr $xl - $delta/2]
951 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
952 -width 1 -outline black -fill $col -tags tag.$id
954 set t [$canv create text $xl $y1 -anchor w -text $tag \
955 -font $mainfont -tags tag.$id]
957 $canv bind $t <1> [list showtag $tag 1]
963 proc notecrossings {id lo hi corner} {
964 global olddisplist crossings cornercrossings
966 for {set i $lo} {[incr i] < $hi} {} {
967 set p [lindex $olddisplist $i]
968 if {$p == {}} continue
970 if {![info exists cornercrossings($id)]
971 || [lsearch -exact $cornercrossings($id) $p] < 0} {
972 lappend cornercrossings($id) $p
974 if {![info exists cornercrossings($p)]
975 || [lsearch -exact $cornercrossings($p) $id] < 0} {
976 lappend cornercrossings($p) $id
979 if {![info exists crossings($id)]
980 || [lsearch -exact $crossings($id) $p] < 0} {
981 lappend crossings($id) $p
983 if {![info exists crossings($p)]
984 || [lsearch -exact $crossings($p) $id] < 0} {
985 lappend crossings($p) $id
991 proc xcoord {i level ln} {
992 global canvx0 xspc1 xspc2
994 set x [expr {$canvx0 + $i * $xspc1($ln)}]
995 if {$i > 0 && $i == $level} {
996 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
997 } elseif {$i > $level} {
998 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1003 # it seems Tk can't draw arrows on the end of diagonal line segments...
1004 proc trimdiagend {line} {
1005 while {[llength $line] > 4} {
1006 set x1 [lindex $line end-3]
1007 set y1 [lindex $line end-2]
1008 set x2 [lindex $line end-1]
1009 set y2 [lindex $line end]
1010 if {($x1 == $x2) != ($y1 == $y2)} break
1011 set line [lreplace $line end-1 end]
1016 proc trimdiagstart {line} {
1017 while {[llength $line] > 4} {
1018 set x1 [lindex $line 0]
1019 set y1 [lindex $line 1]
1020 set x2 [lindex $line 2]
1021 set y2 [lindex $line 3]
1022 if {($x1 == $x2) != ($y1 == $y2)} break
1023 set line [lreplace $line 0 1]
1028 proc drawslants {id needonscreen nohs} {
1029 global canv mainline mainlinearrow sidelines
1030 global canvx0 canvy xspc1 xspc2 lthickness
1031 global currentparents dupparents
1032 global lthickness linespc canvy colormap lineno geometry
1033 global maxgraphpct maxwidth
1034 global displist onscreen lastuse
1035 global parents commitlisted
1036 global oldnlines olddlevel olddisplist
1037 global nhyperspace numcommits nnewparents
1040 lappend displist $id
1045 set y1 [expr {$canvy - $linespc}]
1048 # work out what we need to get back on screen
1050 if {$onscreen($id) < 0} {
1051 # next to do isn't displayed, better get it on screen...
1052 lappend reins [list $id 0]
1054 # make sure all the previous commits's parents are on the screen
1055 foreach p $currentparents {
1056 if {$onscreen($p) < 0} {
1057 lappend reins [list $p 0]
1060 # bring back anything requested by caller
1061 if {$needonscreen ne {}} {
1062 lappend reins $needonscreen
1066 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1067 set dlevel $olddlevel
1068 set x [xcoord $dlevel $dlevel $lineno]
1069 set mainline($id) [list $x $y1]
1070 set mainlinearrow($id) none
1071 set lastuse($id) $lineno
1072 set displist [lreplace $displist $dlevel $dlevel $id]
1074 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1079 set displist [lreplace $displist $olddlevel $olddlevel]
1081 foreach p $currentparents {
1082 set lastuse($p) $lineno
1083 if {$onscreen($p) == 0} {
1084 set displist [linsert $displist $j $p]
1089 if {$onscreen($id) == 0} {
1090 lappend displist $id
1094 # remove the null entry if present
1095 set nullentry [lsearch -exact $displist {}]
1096 if {$nullentry >= 0} {
1097 set displist [lreplace $displist $nullentry $nullentry]
1100 # bring back the ones we need now (if we did it earlier
1101 # it would change displist and invalidate olddlevel)
1103 # test again in case of duplicates in reins
1104 set p [lindex $pi 0]
1105 if {$onscreen($p) < 0} {
1107 set lastuse($p) $lineno
1108 set displist [linsert $displist [lindex $pi 1] $p]
1113 set lastuse($id) $lineno
1115 # see if we need to make any lines jump off into hyperspace
1116 set displ [llength $displist]
1117 if {$displ > $maxwidth} {
1119 foreach x $displist {
1120 lappend ages [list $lastuse($x) $x]
1122 set ages [lsort -integer -index 0 $ages]
1124 while {$displ > $maxwidth} {
1125 set use [lindex $ages $k 0]
1126 set victim [lindex $ages $k 1]
1127 if {$use >= $lineno - 5} break
1129 if {[lsearch -exact $nohs $victim] >= 0} continue
1130 set i [lsearch -exact $displist $victim]
1131 set displist [lreplace $displist $i $i]
1132 set onscreen($victim) -1
1135 if {$i < $nullentry} {
1138 set x [lindex $mainline($victim) end-1]
1139 lappend mainline($victim) $x $y1
1140 set line [trimdiagend $mainline($victim)]
1142 if {$mainlinearrow($victim) ne "none"} {
1143 set line [trimdiagstart $line]
1146 lappend sidelines($victim) [list $line 1 $arrow]
1147 unset mainline($victim)
1151 set dlevel [lsearch -exact $displist $id]
1153 # If we are reducing, put in a null entry
1154 if {$displ < $oldnlines} {
1155 # does the next line look like a merge?
1156 # i.e. does it have > 1 new parent?
1157 if {$nnewparents($id) > 1} {
1158 set i [expr {$dlevel + 1}]
1159 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1161 if {$nullentry >= 0 && $nullentry < $i} {
1164 } elseif {$nullentry >= 0} {
1167 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1172 if {$dlevel >= $i} {
1177 set displist [linsert $displist $i {}]
1179 if {$dlevel >= $i} {
1185 # decide on the line spacing for the next line
1186 set lj [expr {$lineno + 1}]
1187 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1188 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1189 set xspc1($lj) $xspc2
1191 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1192 if {$xspc1($lj) < $lthickness} {
1193 set xspc1($lj) $lthickness
1197 foreach idi $reins {
1198 set id [lindex $idi 0]
1199 set j [lsearch -exact $displist $id]
1200 set xj [xcoord $j $dlevel $lj]
1201 set mainline($id) [list $xj $y2]
1202 set mainlinearrow($id) first
1206 foreach id $olddisplist {
1208 if {$id == {}} continue
1209 if {$onscreen($id) <= 0} continue
1210 set xi [xcoord $i $olddlevel $lineno]
1211 if {$i == $olddlevel} {
1212 foreach p $currentparents {
1213 set j [lsearch -exact $displist $p]
1214 set coords [list $xi $y1]
1215 set xj [xcoord $j $dlevel $lj]
1216 if {$xj < $xi - $linespc} {
1217 lappend coords [expr {$xj + $linespc}] $y1
1218 notecrossings $p $j $i [expr {$j + 1}]
1219 } elseif {$xj > $xi + $linespc} {
1220 lappend coords [expr {$xj - $linespc}] $y1
1221 notecrossings $p $i $j [expr {$j - 1}]
1223 if {[lsearch -exact $dupparents $p] >= 0} {
1224 # draw a double-width line to indicate the doubled parent
1225 lappend coords $xj $y2
1226 lappend sidelines($p) [list $coords 2 none]
1227 if {![info exists mainline($p)]} {
1228 set mainline($p) [list $xj $y2]
1229 set mainlinearrow($p) none
1232 # normal case, no parent duplicated
1234 set dx [expr {abs($xi - $xj)}]
1235 if {0 && $dx < $linespc} {
1236 set yb [expr {$y1 + $dx}]
1238 if {![info exists mainline($p)]} {
1240 lappend coords $xj $yb
1242 set mainline($p) $coords
1243 set mainlinearrow($p) none
1245 lappend coords $xj $yb
1247 lappend coords $xj $y2
1249 lappend sidelines($p) [list $coords 1 none]
1255 if {[lindex $displist $i] != $id} {
1256 set j [lsearch -exact $displist $id]
1258 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1259 || ($olddlevel < $i && $i < $dlevel)
1260 || ($dlevel < $i && $i < $olddlevel)} {
1261 set xj [xcoord $j $dlevel $lj]
1262 lappend mainline($id) $xi $y1 $xj $y2
1269 # search for x in a list of lists
1270 proc llsearch {llist x} {
1273 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1281 proc drawmore {reading} {
1282 global displayorder numcommits ncmupdate nextupdate
1283 global stopped nhyperspace parents commitlisted
1284 global maxwidth onscreen displist currentparents olddlevel
1286 set n [llength $displayorder]
1287 while {$numcommits < $n} {
1288 set id [lindex $displayorder $numcommits]
1289 set ctxend [expr {$numcommits + 10}]
1290 if {!$reading && $ctxend > $n} {
1294 if {$numcommits > 0} {
1295 set dlist [lreplace $displist $olddlevel $olddlevel]
1297 foreach p $currentparents {
1298 if {$onscreen($p) == 0} {
1299 set dlist [linsert $dlist $i $p]
1306 set isfat [expr {[llength $dlist] > $maxwidth}]
1307 if {$nhyperspace > 0 || $isfat} {
1308 if {$ctxend > $n} break
1309 # work out what to bring back and
1310 # what we want to don't want to send into hyperspace
1312 for {set k $numcommits} {$k < $ctxend} {incr k} {
1313 set x [lindex $displayorder $k]
1314 set i [llsearch $dlist $x]
1316 set i [llength $dlist]
1319 if {[lsearch -exact $nohs $x] < 0} {
1322 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1323 set reins [list $x $i]
1326 if {[info exists commitlisted($x)]} {
1328 foreach p $parents($x) {
1329 if {[llsearch $dlist $p] < 0} {
1331 if {[lsearch -exact $nohs $p] < 0} {
1334 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1335 set reins [list $p [expr {$i + $right}]]
1341 set l [lindex $dlist $i]
1342 if {[llength $l] == 1} {
1345 set j [lsearch -exact $l $x]
1346 set l [concat [lreplace $l $j $j] $newp]
1348 set dlist [lreplace $dlist $i $i $l]
1349 if {$room && $isfat && [llength $newp] <= 1} {
1355 set dlevel [drawslants $id $reins $nohs]
1356 drawcommitline $dlevel
1357 if {[clock clicks -milliseconds] >= $nextupdate
1358 && $numcommits >= $ncmupdate} {
1365 # level here is an index in todo
1366 proc updatetodo {level noshortcut} {
1367 global ncleft todo nnewparents
1368 global commitlisted parents onscreen
1370 set id [lindex $todo $level]
1372 if {[info exists commitlisted($id)]} {
1373 foreach p $parents($id) {
1374 if {[lsearch -exact $olds $p] < 0} {
1379 if {!$noshortcut && [llength $olds] == 1} {
1380 set p [lindex $olds 0]
1381 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1383 set todo [lreplace $todo $level $level $p]
1385 set nnewparents($id) 1
1390 set todo [lreplace $todo $level $level]
1395 set k [lsearch -exact $todo $p]
1397 set todo [linsert $todo $i $p]
1403 set nnewparents($id) $n
1408 proc decidenext {{noread 0}} {
1410 global datemode cdate
1413 # choose which one to do next time around
1414 set todol [llength $todo]
1417 for {set k $todol} {[incr k -1] >= 0} {} {
1418 set p [lindex $todo $k]
1419 if {$ncleft($p) == 0} {
1421 if {![info exists commitinfo($p)]} {
1427 if {$latest == {} || $cdate($p) > $latest} {
1429 set latest $cdate($p)
1439 puts "ERROR: none of the pending commits can be done yet:"
1441 puts " $p ($ncleft($p))"
1450 proc drawcommit {id} {
1451 global phase todo nchildren datemode nextupdate revlistorder
1452 global numcommits ncmupdate displayorder todo onscreen parents
1454 if {$phase != "incrdraw"} {
1460 if {$nchildren($id) == 0} {
1464 if {$revlistorder} {
1465 set level [lsearch -exact $todo $id]
1467 error_popup "oops, $id isn't in todo"
1470 lappend displayorder $id
1473 set level [decidenext 1]
1474 if {$level == {} || $id != [lindex $todo $level]} {
1478 lappend displayorder [lindex $todo $level]
1479 if {[updatetodo $level $datemode]} {
1480 set level [decidenext 1]
1481 if {$level == {}} break
1483 set id [lindex $todo $level]
1484 if {![info exists commitlisted($id)]} {
1492 proc finishcommits {} {
1494 global canv mainfont ctext maincursor textcursor
1496 if {$phase != "incrdraw"} {
1498 $canv create text 3 3 -anchor nw -text "No commits selected" \
1499 -font $mainfont -tags textitems
1504 . config -cursor $maincursor
1505 settextcursor $textcursor
1508 # Don't change the text pane cursor if it is currently the hand cursor,
1509 # showing that we are over a sha1 ID link.
1510 proc settextcursor {c} {
1511 global ctext curtextcursor
1513 if {[$ctext cget -cursor] == $curtextcursor} {
1514 $ctext config -cursor $c
1516 set curtextcursor $c
1520 global nextupdate startmsecs ncmupdate
1521 global displayorder onscreen
1523 if {$displayorder == {}} return
1524 set startmsecs [clock clicks -milliseconds]
1525 set nextupdate [expr $startmsecs + 100]
1528 foreach id $displayorder {
1535 global phase stopped redisplaying selectedline
1536 global datemode todo displayorder
1537 global numcommits ncmupdate
1538 global nextupdate startmsecs revlistorder
1540 if {!$revlistorder} {
1541 set level [decidenext]
1545 lappend displayorder [lindex $todo $level]
1546 set hard [updatetodo $level $datemode]
1548 set level [decidenext]
1549 if {$level < 0} break
1556 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1557 #puts "overall $drawmsecs ms for $numcommits commits"
1558 if {$redisplaying} {
1559 if {$stopped == 0 && [info exists selectedline]} {
1560 selectline $selectedline 0
1562 if {$stopped == 1} {
1564 after idle drawgraph
1571 proc findmatches {f} {
1572 global findtype foundstring foundstrlen
1573 if {$findtype == "Regexp"} {
1574 set matches [regexp -indices -all -inline $foundstring $f]
1576 if {$findtype == "IgnCase"} {
1577 set str [string tolower $f]
1583 while {[set j [string first $foundstring $str $i]] >= 0} {
1584 lappend matches [list $j [expr $j+$foundstrlen-1]]
1585 set i [expr $j + $foundstrlen]
1592 global findtype findloc findstring markedmatches commitinfo
1593 global numcommits lineid linehtag linentag linedtag
1594 global mainfont namefont canv canv2 canv3 selectedline
1595 global matchinglines foundstring foundstrlen
1600 set matchinglines {}
1601 if {$findloc == "Pickaxe"} {
1605 if {$findtype == "IgnCase"} {
1606 set foundstring [string tolower $findstring]
1608 set foundstring $findstring
1610 set foundstrlen [string length $findstring]
1611 if {$foundstrlen == 0} return
1612 if {$findloc == "Files"} {
1616 if {![info exists selectedline]} {
1619 set oldsel $selectedline
1622 set fldtypes {Headline Author Date Committer CDate Comment}
1623 for {set l 0} {$l < $numcommits} {incr l} {
1625 set info $commitinfo($id)
1627 foreach f $info ty $fldtypes {
1628 if {$findloc != "All fields" && $findloc != $ty} {
1631 set matches [findmatches $f]
1632 if {$matches == {}} continue
1634 if {$ty == "Headline"} {
1635 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1636 } elseif {$ty == "Author"} {
1637 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1638 } elseif {$ty == "Date"} {
1639 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1643 lappend matchinglines $l
1644 if {!$didsel && $l > $oldsel} {
1650 if {$matchinglines == {}} {
1652 } elseif {!$didsel} {
1653 findselectline [lindex $matchinglines 0]
1657 proc findselectline {l} {
1658 global findloc commentend ctext
1660 if {$findloc == "All fields" || $findloc == "Comments"} {
1661 # highlight the matches in the comments
1662 set f [$ctext get 1.0 $commentend]
1663 set matches [findmatches $f]
1664 foreach match $matches {
1665 set start [lindex $match 0]
1666 set end [expr [lindex $match 1] + 1]
1667 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1672 proc findnext {restart} {
1673 global matchinglines selectedline
1674 if {![info exists matchinglines]} {
1680 if {![info exists selectedline]} return
1681 foreach l $matchinglines {
1682 if {$l > $selectedline} {
1691 global matchinglines selectedline
1692 if {![info exists matchinglines]} {
1696 if {![info exists selectedline]} return
1698 foreach l $matchinglines {
1699 if {$l >= $selectedline} break
1703 findselectline $prev
1709 proc findlocchange {name ix op} {
1710 global findloc findtype findtypemenu
1711 if {$findloc == "Pickaxe"} {
1717 $findtypemenu entryconf 1 -state $state
1718 $findtypemenu entryconf 2 -state $state
1721 proc stopfindproc {{done 0}} {
1722 global findprocpid findprocfile findids
1723 global ctext findoldcursor phase maincursor textcursor
1724 global findinprogress
1726 catch {unset findids}
1727 if {[info exists findprocpid]} {
1729 catch {exec kill $findprocpid}
1731 catch {close $findprocfile}
1734 if {[info exists findinprogress]} {
1735 unset findinprogress
1736 if {$phase != "incrdraw"} {
1737 . config -cursor $maincursor
1738 settextcursor $textcursor
1743 proc findpatches {} {
1744 global findstring selectedline numcommits
1745 global findprocpid findprocfile
1746 global finddidsel ctext lineid findinprogress
1747 global findinsertpos
1749 if {$numcommits == 0} return
1751 # make a list of all the ids to search, starting at the one
1752 # after the selected line (if any)
1753 if {[info exists selectedline]} {
1759 for {set i 0} {$i < $numcommits} {incr i} {
1760 if {[incr l] >= $numcommits} {
1763 append inputids $lineid($l) "\n"
1767 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1770 error_popup "Error starting search process: $err"
1774 set findinsertpos end
1776 set findprocpid [pid $f]
1777 fconfigure $f -blocking 0
1778 fileevent $f readable readfindproc
1780 . config -cursor watch
1782 set findinprogress 1
1785 proc readfindproc {} {
1786 global findprocfile finddidsel
1787 global idline matchinglines findinsertpos
1789 set n [gets $findprocfile line]
1791 if {[eof $findprocfile]} {
1799 if {![regexp {^[0-9a-f]{40}} $line id]} {
1800 error_popup "Can't parse git-diff-tree output: $line"
1804 if {![info exists idline($id)]} {
1805 puts stderr "spurious id: $id"
1812 proc insertmatch {l id} {
1813 global matchinglines findinsertpos finddidsel
1815 if {$findinsertpos == "end"} {
1816 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1817 set matchinglines [linsert $matchinglines 0 $l]
1820 lappend matchinglines $l
1823 set matchinglines [linsert $matchinglines $findinsertpos $l]
1834 global selectedline numcommits lineid ctext
1835 global ffileline finddidsel parents nparents
1836 global findinprogress findstartline findinsertpos
1837 global treediffs fdiffids fdiffsneeded fdiffpos
1838 global findmergefiles
1840 if {$numcommits == 0} return
1842 if {[info exists selectedline]} {
1843 set l [expr {$selectedline + 1}]
1848 set findstartline $l
1853 if {$findmergefiles || $nparents($id) == 1} {
1854 foreach p $parents($id) {
1855 if {![info exists treediffs([list $id $p])]} {
1856 append diffsneeded "$id $p\n"
1857 lappend fdiffsneeded [list $id $p]
1861 if {[incr l] >= $numcommits} {
1864 if {$l == $findstartline} break
1867 # start off a git-diff-tree process if needed
1868 if {$diffsneeded ne {}} {
1870 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1872 error_popup "Error starting search process: $err"
1875 catch {unset fdiffids}
1877 fconfigure $df -blocking 0
1878 fileevent $df readable [list readfilediffs $df]
1882 set findinsertpos end
1884 set p [lindex $parents($id) 0]
1885 . config -cursor watch
1887 set findinprogress 1
1888 findcont [list $id $p]
1892 proc readfilediffs {df} {
1893 global findids fdiffids fdiffs
1895 set n [gets $df line]
1899 if {[catch {close $df} err]} {
1902 error_popup "Error in git-diff-tree: $err"
1903 } elseif {[info exists findids]} {
1907 error_popup "Couldn't find diffs for {$ids}"
1912 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1913 # start of a new string of diffs
1915 set fdiffids [list $id $p]
1917 } elseif {[string match ":*" $line]} {
1918 lappend fdiffs [lindex $line 5]
1922 proc donefilediff {} {
1923 global fdiffids fdiffs treediffs findids
1924 global fdiffsneeded fdiffpos
1926 if {[info exists fdiffids]} {
1927 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1928 && $fdiffpos < [llength $fdiffsneeded]} {
1929 # git-diff-tree doesn't output anything for a commit
1930 # which doesn't change anything
1931 set nullids [lindex $fdiffsneeded $fdiffpos]
1932 set treediffs($nullids) {}
1933 if {[info exists findids] && $nullids eq $findids} {
1941 if {![info exists treediffs($fdiffids)]} {
1942 set treediffs($fdiffids) $fdiffs
1944 if {[info exists findids] && $fdiffids eq $findids} {
1951 proc findcont {ids} {
1952 global findids treediffs parents nparents
1953 global ffileline findstartline finddidsel
1954 global lineid numcommits matchinglines findinprogress
1955 global findmergefiles
1957 set id [lindex $ids 0]
1958 set p [lindex $ids 1]
1959 set pi [lsearch -exact $parents($id) $p]
1962 if {$findmergefiles || $nparents($id) == 1} {
1963 if {![info exists treediffs($ids)]} {
1969 foreach f $treediffs($ids) {
1970 set x [findmatches $f]
1978 set pi $nparents($id)
1981 set pi $nparents($id)
1983 if {[incr pi] >= $nparents($id)} {
1985 if {[incr l] >= $numcommits} {
1988 if {$l == $findstartline} break
1991 set p [lindex $parents($id) $pi]
1992 set ids [list $id $p]
2000 # mark a commit as matching by putting a yellow background
2001 # behind the headline
2002 proc markheadline {l id} {
2003 global canv mainfont linehtag commitinfo
2005 set bbox [$canv bbox $linehtag($l)]
2006 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2010 # mark the bits of a headline, author or date that match a find string
2011 proc markmatches {canv l str tag matches font} {
2012 set bbox [$canv bbox $tag]
2013 set x0 [lindex $bbox 0]
2014 set y0 [lindex $bbox 1]
2015 set y1 [lindex $bbox 3]
2016 foreach match $matches {
2017 set start [lindex $match 0]
2018 set end [lindex $match 1]
2019 if {$start > $end} continue
2020 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2021 set xlen [font measure $font [string range $str 0 [expr $end]]]
2022 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2023 -outline {} -tags matches -fill yellow]
2028 proc unmarkmatches {} {
2029 global matchinglines findids
2030 allcanvs delete matches
2031 catch {unset matchinglines}
2032 catch {unset findids}
2035 proc selcanvline {w x y} {
2036 global canv canvy0 ctext linespc
2037 global lineid linehtag linentag linedtag rowtextx
2038 set ymax [lindex [$canv cget -scrollregion] 3]
2039 if {$ymax == {}} return
2040 set yfrac [lindex [$canv yview] 0]
2041 set y [expr {$y + $yfrac * $ymax}]
2042 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2047 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2053 proc commit_descriptor {p} {
2056 if {[info exists commitinfo($p)]} {
2057 set l [lindex $commitinfo($p) 0]
2062 # append some text to the ctext widget, and make any SHA1 ID
2063 # that we know about be a clickable link.
2064 proc appendwithlinks {text} {
2065 global ctext idline linknum
2067 set start [$ctext index "end - 1c"]
2068 $ctext insert end $text
2069 $ctext insert end "\n"
2070 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2074 set linkid [string range $text $s $e]
2075 if {![info exists idline($linkid)]} continue
2077 $ctext tag add link "$start + $s c" "$start + $e c"
2078 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2079 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2082 $ctext tag conf link -foreground blue -underline 1
2083 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2084 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2087 proc selectline {l isnew} {
2088 global canv canv2 canv3 ctext commitinfo selectedline
2089 global lineid linehtag linentag linedtag
2090 global canvy0 linespc parents nparents children
2091 global cflist currentid sha1entry
2092 global commentend idtags idline linknum
2096 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2098 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2099 -tags secsel -fill [$canv cget -selectbackground]]
2101 $canv2 delete secsel
2102 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2103 -tags secsel -fill [$canv2 cget -selectbackground]]
2105 $canv3 delete secsel
2106 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2107 -tags secsel -fill [$canv3 cget -selectbackground]]
2109 set y [expr {$canvy0 + $l * $linespc}]
2110 set ymax [lindex [$canv cget -scrollregion] 3]
2111 set ytop [expr {$y - $linespc - 1}]
2112 set ybot [expr {$y + $linespc + 1}]
2113 set wnow [$canv yview]
2114 set wtop [expr [lindex $wnow 0] * $ymax]
2115 set wbot [expr [lindex $wnow 1] * $ymax]
2116 set wh [expr {$wbot - $wtop}]
2118 if {$ytop < $wtop} {
2119 if {$ybot < $wtop} {
2120 set newtop [expr {$y - $wh / 2.0}]
2123 if {$newtop > $wtop - $linespc} {
2124 set newtop [expr {$wtop - $linespc}]
2127 } elseif {$ybot > $wbot} {
2128 if {$ytop > $wbot} {
2129 set newtop [expr {$y - $wh / 2.0}]
2131 set newtop [expr {$ybot - $wh}]
2132 if {$newtop < $wtop + $linespc} {
2133 set newtop [expr {$wtop + $linespc}]
2137 if {$newtop != $wtop} {
2141 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2145 addtohistory [list selectline $l 0]
2152 $sha1entry delete 0 end
2153 $sha1entry insert 0 $id
2154 $sha1entry selection from 0
2155 $sha1entry selection to end
2157 $ctext conf -state normal
2158 $ctext delete 0.0 end
2160 $ctext mark set fmark.0 0.0
2161 $ctext mark gravity fmark.0 left
2162 set info $commitinfo($id)
2163 set date [formatdate [lindex $info 2]]
2164 $ctext insert end "Author: [lindex $info 1] $date\n"
2165 set date [formatdate [lindex $info 4]]
2166 $ctext insert end "Committer: [lindex $info 3] $date\n"
2167 if {[info exists idtags($id)]} {
2168 $ctext insert end "Tags:"
2169 foreach tag $idtags($id) {
2170 $ctext insert end " $tag"
2172 $ctext insert end "\n"
2176 if {[info exists parents($id)]} {
2177 foreach p $parents($id) {
2178 append comment "Parent: [commit_descriptor $p]\n"
2181 if {[info exists children($id)]} {
2182 foreach c $children($id) {
2183 append comment "Child: [commit_descriptor $c]\n"
2187 append comment [lindex $info 5]
2189 # make anything that looks like a SHA1 ID be a clickable link
2190 appendwithlinks $comment
2192 $ctext tag delete Comments
2193 $ctext tag remove found 1.0 end
2194 $ctext conf -state disabled
2195 set commentend [$ctext index "end - 1c"]
2197 $cflist delete 0 end
2198 $cflist insert end "Comments"
2199 if {$nparents($id) == 1} {
2200 startdiff [concat $id $parents($id)]
2201 } elseif {$nparents($id) > 1} {
2206 proc selnextline {dir} {
2208 if {![info exists selectedline]} return
2209 set l [expr $selectedline + $dir]
2214 proc unselectline {} {
2217 catch {unset selectedline}
2218 allcanvs delete secsel
2221 proc addtohistory {cmd} {
2222 global history historyindex
2224 if {$historyindex > 0
2225 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2229 if {$historyindex < [llength $history]} {
2230 set history [lreplace $history $historyindex end $cmd]
2232 lappend history $cmd
2235 if {$historyindex > 1} {
2236 .ctop.top.bar.leftbut conf -state normal
2238 .ctop.top.bar.leftbut conf -state disabled
2240 .ctop.top.bar.rightbut conf -state disabled
2244 global history historyindex
2246 if {$historyindex > 1} {
2247 incr historyindex -1
2248 set cmd [lindex $history [expr {$historyindex - 1}]]
2250 .ctop.top.bar.rightbut conf -state normal
2252 if {$historyindex <= 1} {
2253 .ctop.top.bar.leftbut conf -state disabled
2258 global history historyindex
2260 if {$historyindex < [llength $history]} {
2261 set cmd [lindex $history $historyindex]
2264 .ctop.top.bar.leftbut conf -state normal
2266 if {$historyindex >= [llength $history]} {
2267 .ctop.top.bar.rightbut conf -state disabled
2271 proc mergediff {id} {
2272 global parents diffmergeid diffmergegca mergefilelist diffpindex
2276 set diffmergegca [findgca $parents($id)]
2277 if {[info exists mergefilelist($id)]} {
2278 if {$mergefilelist($id) ne {}} {
2286 proc findgca {ids} {
2293 set gca [exec git-merge-base $gca $id]
2302 proc contmergediff {ids} {
2303 global diffmergeid diffpindex parents nparents diffmergegca
2304 global treediffs mergefilelist diffids treepending
2306 # diff the child against each of the parents, and diff
2307 # each of the parents against the GCA.
2309 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2310 set ids [list [lindex $ids 1] $diffmergegca]
2312 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2313 set p [lindex $parents($diffmergeid) $diffpindex]
2314 set ids [list $diffmergeid $p]
2316 if {![info exists treediffs($ids)]} {
2318 if {![info exists treepending]} {
2325 # If a file in some parent is different from the child and also
2326 # different from the GCA, then it's interesting.
2327 # If we don't have a GCA, then a file is interesting if it is
2328 # different from the child in all the parents.
2329 if {$diffmergegca ne {}} {
2331 foreach p $parents($diffmergeid) {
2332 set gcadiffs $treediffs([list $p $diffmergegca])
2333 foreach f $treediffs([list $diffmergeid $p]) {
2334 if {[lsearch -exact $files $f] < 0
2335 && [lsearch -exact $gcadiffs $f] >= 0} {
2340 set files [lsort $files]
2342 set p [lindex $parents($diffmergeid) 0]
2343 set files $treediffs([list $diffmergeid $p])
2344 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2345 set p [lindex $parents($diffmergeid) $i]
2346 set df $treediffs([list $diffmergeid $p])
2349 if {[lsearch -exact $df $f] >= 0} {
2357 set mergefilelist($diffmergeid) $files
2363 proc showmergediff {} {
2364 global cflist diffmergeid mergefilelist parents
2365 global diffopts diffinhunk currentfile currenthunk filelines
2366 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2368 set files $mergefilelist($diffmergeid)
2370 $cflist insert end $f
2372 set env(GIT_DIFF_OPTS) $diffopts
2374 catch {unset currentfile}
2375 catch {unset currenthunk}
2376 catch {unset filelines}
2377 catch {unset groupfilenum}
2378 catch {unset grouphunks}
2379 set groupfilelast -1
2380 foreach p $parents($diffmergeid) {
2381 set cmd [list | git-diff-tree -p $p $diffmergeid]
2382 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2383 if {[catch {set f [open $cmd r]} err]} {
2384 error_popup "Error getting diffs: $err"
2391 set ids [list $diffmergeid $p]
2392 set mergefds($ids) $f
2393 set diffinhunk($ids) 0
2394 set diffblocked($ids) 0
2395 fconfigure $f -blocking 0
2396 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2400 proc getmergediffline {f ids id} {
2401 global diffmergeid diffinhunk diffoldlines diffnewlines
2402 global currentfile currenthunk
2403 global diffoldstart diffnewstart diffoldlno diffnewlno
2404 global diffblocked mergefilelist
2405 global noldlines nnewlines difflcounts filelines
2407 set n [gets $f line]
2409 if {![eof $f]} return
2412 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2419 if {$diffinhunk($ids) != 0} {
2420 set fi $currentfile($ids)
2421 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2422 # continuing an existing hunk
2423 set line [string range $line 1 end]
2424 set p [lindex $ids 1]
2425 if {$match eq "-" || $match eq " "} {
2426 set filelines($p,$fi,$diffoldlno($ids)) $line
2427 incr diffoldlno($ids)
2429 if {$match eq "+" || $match eq " "} {
2430 set filelines($id,$fi,$diffnewlno($ids)) $line
2431 incr diffnewlno($ids)
2433 if {$match eq " "} {
2434 if {$diffinhunk($ids) == 2} {
2435 lappend difflcounts($ids) \
2436 [list $noldlines($ids) $nnewlines($ids)]
2437 set noldlines($ids) 0
2438 set diffinhunk($ids) 1
2440 incr noldlines($ids)
2441 } elseif {$match eq "-" || $match eq "+"} {
2442 if {$diffinhunk($ids) == 1} {
2443 lappend difflcounts($ids) [list $noldlines($ids)]
2444 set noldlines($ids) 0
2445 set nnewlines($ids) 0
2446 set diffinhunk($ids) 2
2448 if {$match eq "-"} {
2449 incr noldlines($ids)
2451 incr nnewlines($ids)
2454 # and if it's \ No newline at end of line, then what?
2458 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2459 lappend difflcounts($ids) [list $noldlines($ids)]
2460 } elseif {$diffinhunk($ids) == 2
2461 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2462 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2464 set currenthunk($ids) [list $currentfile($ids) \
2465 $diffoldstart($ids) $diffnewstart($ids) \
2466 $diffoldlno($ids) $diffnewlno($ids) \
2468 set diffinhunk($ids) 0
2469 # -1 = need to block, 0 = unblocked, 1 = is blocked
2470 set diffblocked($ids) -1
2472 if {$diffblocked($ids) == -1} {
2473 fileevent $f readable {}
2474 set diffblocked($ids) 1
2480 if {!$diffblocked($ids)} {
2482 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2483 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2486 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2487 # start of a new file
2488 set currentfile($ids) \
2489 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2490 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2491 $line match f1l f1c f2l f2c rest]} {
2492 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2493 # start of a new hunk
2494 if {$f1l == 0 && $f1c == 0} {
2497 if {$f2l == 0 && $f2c == 0} {
2500 set diffinhunk($ids) 1
2501 set diffoldstart($ids) $f1l
2502 set diffnewstart($ids) $f2l
2503 set diffoldlno($ids) $f1l
2504 set diffnewlno($ids) $f2l
2505 set difflcounts($ids) {}
2506 set noldlines($ids) 0
2507 set nnewlines($ids) 0
2512 proc processhunks {} {
2513 global diffmergeid parents nparents currenthunk
2514 global mergefilelist diffblocked mergefds
2515 global grouphunks grouplinestart grouplineend groupfilenum
2517 set nfiles [llength $mergefilelist($diffmergeid)]
2521 # look for the earliest hunk
2522 foreach p $parents($diffmergeid) {
2523 set ids [list $diffmergeid $p]
2524 if {![info exists currenthunk($ids)]} return
2525 set i [lindex $currenthunk($ids) 0]
2526 set l [lindex $currenthunk($ids) 2]
2527 if {$i < $fi || ($i == $fi && $l < $lno)} {
2534 if {$fi < $nfiles} {
2535 set ids [list $diffmergeid $pi]
2536 set hunk $currenthunk($ids)
2537 unset currenthunk($ids)
2538 if {$diffblocked($ids) > 0} {
2539 fileevent $mergefds($ids) readable \
2540 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2542 set diffblocked($ids) 0
2544 if {[info exists groupfilenum] && $groupfilenum == $fi
2545 && $lno <= $grouplineend} {
2546 # add this hunk to the pending group
2547 lappend grouphunks($pi) $hunk
2548 set endln [lindex $hunk 4]
2549 if {$endln > $grouplineend} {
2550 set grouplineend $endln
2556 # succeeding stuff doesn't belong in this group, so
2557 # process the group now
2558 if {[info exists groupfilenum]} {
2564 if {$fi >= $nfiles} break
2567 set groupfilenum $fi
2568 set grouphunks($pi) [list $hunk]
2569 set grouplinestart $lno
2570 set grouplineend [lindex $hunk 4]
2574 proc processgroup {} {
2575 global groupfilelast groupfilenum difffilestart
2576 global mergefilelist diffmergeid ctext filelines
2577 global parents diffmergeid diffoffset
2578 global grouphunks grouplinestart grouplineend nparents
2581 $ctext conf -state normal
2584 if {$groupfilelast != $f} {
2585 $ctext insert end "\n"
2586 set here [$ctext index "end - 1c"]
2587 set difffilestart($f) $here
2588 set mark fmark.[expr {$f + 1}]
2589 $ctext mark set $mark $here
2590 $ctext mark gravity $mark left
2591 set header [lindex $mergefilelist($id) $f]
2592 set l [expr {(78 - [string length $header]) / 2}]
2593 set pad [string range "----------------------------------------" 1 $l]
2594 $ctext insert end "$pad $header $pad\n" filesep
2595 set groupfilelast $f
2596 foreach p $parents($id) {
2597 set diffoffset($p) 0
2601 $ctext insert end "@@" msep
2602 set nlines [expr {$grouplineend - $grouplinestart}]
2605 foreach p $parents($id) {
2606 set startline [expr {$grouplinestart + $diffoffset($p)}]
2608 set nl $grouplinestart
2609 if {[info exists grouphunks($p)]} {
2610 foreach h $grouphunks($p) {
2613 for {} {$nl < $l} {incr nl} {
2614 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2618 foreach chunk [lindex $h 5] {
2619 if {[llength $chunk] == 2} {
2620 set olc [lindex $chunk 0]
2621 set nlc [lindex $chunk 1]
2622 set nnl [expr {$nl + $nlc}]
2623 lappend events [list $nl $nnl $pnum $olc $nlc]
2627 incr ol [lindex $chunk 0]
2628 incr nl [lindex $chunk 0]
2633 if {$nl < $grouplineend} {
2634 for {} {$nl < $grouplineend} {incr nl} {
2635 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2639 set nlines [expr {$ol - $startline}]
2640 $ctext insert end " -$startline,$nlines" msep
2644 set nlines [expr {$grouplineend - $grouplinestart}]
2645 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2647 set events [lsort -integer -index 0 $events]
2648 set nevents [llength $events]
2649 set nmerge $nparents($diffmergeid)
2650 set l $grouplinestart
2651 for {set i 0} {$i < $nevents} {set i $j} {
2652 set nl [lindex $events $i 0]
2654 $ctext insert end " $filelines($id,$f,$l)\n"
2657 set e [lindex $events $i]
2658 set enl [lindex $e 1]
2662 set pnum [lindex $e 2]
2663 set olc [lindex $e 3]
2664 set nlc [lindex $e 4]
2665 if {![info exists delta($pnum)]} {
2666 set delta($pnum) [expr {$olc - $nlc}]
2667 lappend active $pnum
2669 incr delta($pnum) [expr {$olc - $nlc}]
2671 if {[incr j] >= $nevents} break
2672 set e [lindex $events $j]
2673 if {[lindex $e 0] >= $enl} break
2674 if {[lindex $e 1] > $enl} {
2675 set enl [lindex $e 1]
2678 set nlc [expr {$enl - $l}]
2681 if {[llength $active] == $nmerge - 1} {
2682 # no diff for one of the parents, i.e. it's identical
2683 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2684 if {![info exists delta($pnum)]} {
2685 if {$pnum < $mergemax} {
2693 } elseif {[llength $active] == $nmerge} {
2694 # all parents are different, see if one is very similar
2696 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2697 set sim [similarity $pnum $l $nlc $f \
2698 [lrange $events $i [expr {$j-1}]]]
2699 if {$sim > $bestsim} {
2705 lappend ncol m$bestpn
2709 foreach p $parents($id) {
2711 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2712 set olc [expr {$nlc + $delta($pnum)}]
2713 set ol [expr {$l + $diffoffset($p)}]
2714 incr diffoffset($p) $delta($pnum)
2716 for {} {$olc > 0} {incr olc -1} {
2717 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2721 set endl [expr {$l + $nlc}]
2723 # show this pretty much as a normal diff
2724 set p [lindex $parents($id) $bestpn]
2725 set ol [expr {$l + $diffoffset($p)}]
2726 incr diffoffset($p) $delta($bestpn)
2727 unset delta($bestpn)
2728 for {set k $i} {$k < $j} {incr k} {
2729 set e [lindex $events $k]
2730 if {[lindex $e 2] != $bestpn} continue
2731 set nl [lindex $e 0]
2732 set ol [expr {$ol + $nl - $l}]
2733 for {} {$l < $nl} {incr l} {
2734 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2737 for {} {$c > 0} {incr c -1} {
2738 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2741 set nl [lindex $e 1]
2742 for {} {$l < $nl} {incr l} {
2743 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2747 for {} {$l < $endl} {incr l} {
2748 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2751 while {$l < $grouplineend} {
2752 $ctext insert end " $filelines($id,$f,$l)\n"
2755 $ctext conf -state disabled
2758 proc similarity {pnum l nlc f events} {
2759 global diffmergeid parents diffoffset filelines
2762 set p [lindex $parents($id) $pnum]
2763 set ol [expr {$l + $diffoffset($p)}]
2764 set endl [expr {$l + $nlc}]
2768 if {[lindex $e 2] != $pnum} continue
2769 set nl [lindex $e 0]
2770 set ol [expr {$ol + $nl - $l}]
2771 for {} {$l < $nl} {incr l} {
2772 incr same [string length $filelines($id,$f,$l)]
2775 set oc [lindex $e 3]
2776 for {} {$oc > 0} {incr oc -1} {
2777 incr diff [string length $filelines($p,$f,$ol)]
2781 set nl [lindex $e 1]
2782 for {} {$l < $nl} {incr l} {
2783 incr diff [string length $filelines($id,$f,$l)]
2787 for {} {$l < $endl} {incr l} {
2788 incr same [string length $filelines($id,$f,$l)]
2794 return [expr {200 * $same / (2 * $same + $diff)}]
2797 proc startdiff {ids} {
2798 global treediffs diffids treepending diffmergeid
2801 catch {unset diffmergeid}
2802 if {![info exists treediffs($ids)]} {
2803 if {![info exists treepending]} {
2811 proc addtocflist {ids} {
2812 global treediffs cflist
2813 foreach f $treediffs($ids) {
2814 $cflist insert end $f
2819 proc gettreediffs {ids} {
2820 global treediff parents treepending
2821 set treepending $ids
2823 set id [lindex $ids 0]
2824 set p [lindex $ids 1]
2825 if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return
2826 fconfigure $gdtf -blocking 0
2827 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2830 proc gettreediffline {gdtf ids} {
2831 global treediff treediffs treepending diffids diffmergeid
2833 set n [gets $gdtf line]
2835 if {![eof $gdtf]} return
2837 set treediffs($ids) $treediff
2839 if {$ids != $diffids} {
2840 gettreediffs $diffids
2842 if {[info exists diffmergeid]} {
2850 set file [lindex $line 5]
2851 lappend treediff $file
2854 proc getblobdiffs {ids} {
2855 global diffopts blobdifffd diffids env curdifftag curtagstart
2856 global difffilestart nextupdate diffinhdr treediffs
2858 set id [lindex $ids 0]
2859 set p [lindex $ids 1]
2860 set env(GIT_DIFF_OPTS) $diffopts
2861 set cmd [list | git-diff-tree -r -p -C $id]
2862 if {[catch {set bdf [open $cmd r]} err]} {
2863 puts "error getting diffs: $err"
2867 fconfigure $bdf -blocking 0
2868 set blobdifffd($ids) $bdf
2869 set curdifftag Comments
2871 catch {unset difffilestart}
2872 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2873 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2876 proc getblobdiffline {bdf ids} {
2877 global diffids blobdifffd ctext curdifftag curtagstart
2878 global diffnexthead diffnextnote difffilestart
2879 global nextupdate diffinhdr treediffs
2882 set n [gets $bdf line]
2886 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2887 $ctext tag add $curdifftag $curtagstart end
2892 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2895 $ctext conf -state normal
2896 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2897 # start of a new file
2898 $ctext insert end "\n"
2899 $ctext tag add $curdifftag $curtagstart end
2900 set curtagstart [$ctext index "end - 1c"]
2902 set here [$ctext index "end - 1c"]
2903 set i [lsearch -exact $treediffs($diffids) $fname]
2905 set difffilestart($i) $here
2907 $ctext mark set fmark.$i $here
2908 $ctext mark gravity fmark.$i left
2910 if {$newname != $fname} {
2911 set i [lsearch -exact $treediffs($diffids) $newname]
2913 set difffilestart($i) $here
2915 $ctext mark set fmark.$i $here
2916 $ctext mark gravity fmark.$i left
2919 set curdifftag "f:$fname"
2920 $ctext tag delete $curdifftag
2921 set l [expr {(78 - [string length $header]) / 2}]
2922 set pad [string range "----------------------------------------" 1 $l]
2923 $ctext insert end "$pad $header $pad\n" filesep
2925 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2927 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2928 $line match f1l f1c f2l f2c rest]} {
2930 $ctext insert end "\t" hunksep
2931 $ctext insert end " $f1l " d0 " $f2l " d1
2932 $ctext insert end " $rest \n" hunksep
2934 $ctext insert end "$line\n" hunksep
2938 set x [string range $line 0 0]
2939 if {$x == "-" || $x == "+"} {
2940 set tag [expr {$x == "+"}]
2942 set line [string range $line 1 end]
2944 $ctext insert end "$line\n" d$tag
2945 } elseif {$x == " "} {
2947 set line [string range $line 1 end]
2949 $ctext insert end "$line\n"
2950 } elseif {$diffinhdr || $x == "\\"} {
2951 # e.g. "\ No newline at end of file"
2952 $ctext insert end "$line\n" filesep
2954 # Something else we don't recognize
2955 if {$curdifftag != "Comments"} {
2956 $ctext insert end "\n"
2957 $ctext tag add $curdifftag $curtagstart end
2958 set curtagstart [$ctext index "end - 1c"]
2959 set curdifftag Comments
2961 $ctext insert end "$line\n" filesep
2964 $ctext conf -state disabled
2965 if {[clock clicks -milliseconds] >= $nextupdate} {
2967 fileevent $bdf readable {}
2969 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2974 global difffilestart ctext
2975 set here [$ctext index @0,0]
2976 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2977 if {[$ctext compare $difffilestart($i) > $here]} {
2978 if {![info exists pos]
2979 || [$ctext compare $difffilestart($i) < $pos]} {
2980 set pos $difffilestart($i)
2984 if {[info exists pos]} {
2989 proc listboxsel {} {
2990 global ctext cflist currentid
2991 if {![info exists currentid]} return
2992 set sel [lsort [$cflist curselection]]
2993 if {$sel eq {}} return
2994 set first [lindex $sel 0]
2995 catch {$ctext yview fmark.$first}
2999 global linespc charspc canvx0 canvy0 mainfont
3000 global xspc1 xspc2 lthickness
3002 set linespc [font metrics $mainfont -linespace]
3003 set charspc [font measure $mainfont "m"]
3004 set canvy0 [expr 3 + 0.5 * $linespc]
3005 set canvx0 [expr 3 + 0.5 * $linespc]
3006 set lthickness [expr {int($linespc / 9) + 1}]
3007 set xspc1(0) $linespc
3012 global stopped redisplaying phase
3013 if {$stopped > 1} return
3014 if {$phase == "getcommits"} return
3016 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3023 proc incrfont {inc} {
3024 global mainfont namefont textfont ctext canv phase
3025 global stopped entries
3027 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3028 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3029 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3031 $ctext conf -font $textfont
3032 $ctext tag conf filesep -font [concat $textfont bold]
3033 foreach e $entries {
3034 $e conf -font $mainfont
3036 if {$phase == "getcommits"} {
3037 $canv itemconf textitems -font $mainfont
3043 global sha1entry sha1string
3044 if {[string length $sha1string] == 40} {
3045 $sha1entry delete 0 end
3049 proc sha1change {n1 n2 op} {
3050 global sha1string currentid sha1but
3051 if {$sha1string == {}
3052 || ([info exists currentid] && $sha1string == $currentid)} {
3057 if {[$sha1but cget -state] == $state} return
3058 if {$state == "normal"} {
3059 $sha1but conf -state normal -relief raised -text "Goto: "
3061 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3065 proc gotocommit {} {
3066 global sha1string currentid idline tagids
3067 global lineid numcommits
3069 if {$sha1string == {}
3070 || ([info exists currentid] && $sha1string == $currentid)} return
3071 if {[info exists tagids($sha1string)]} {
3072 set id $tagids($sha1string)
3074 set id [string tolower $sha1string]
3075 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3077 for {set l 0} {$l < $numcommits} {incr l} {
3078 if {[string match $id* $lineid($l)]} {
3079 lappend matches $lineid($l)
3082 if {$matches ne {}} {
3083 if {[llength $matches] > 1} {
3084 error_popup "Short SHA1 id $id is ambiguous"
3087 set id [lindex $matches 0]
3091 if {[info exists idline($id)]} {
3092 selectline $idline($id) 1
3095 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3100 error_popup "$type $sha1string is not known"
3103 proc lineenter {x y id} {
3104 global hoverx hovery hoverid hovertimer
3105 global commitinfo canv
3107 if {![info exists commitinfo($id)]} return
3111 if {[info exists hovertimer]} {
3112 after cancel $hovertimer
3114 set hovertimer [after 500 linehover]
3118 proc linemotion {x y id} {
3119 global hoverx hovery hoverid hovertimer
3121 if {[info exists hoverid] && $id == $hoverid} {
3124 if {[info exists hovertimer]} {
3125 after cancel $hovertimer
3127 set hovertimer [after 500 linehover]
3131 proc lineleave {id} {
3132 global hoverid hovertimer canv
3134 if {[info exists hoverid] && $id == $hoverid} {
3136 if {[info exists hovertimer]} {
3137 after cancel $hovertimer
3145 global hoverx hovery hoverid hovertimer
3146 global canv linespc lthickness
3147 global commitinfo mainfont
3149 set text [lindex $commitinfo($hoverid) 0]
3150 set ymax [lindex [$canv cget -scrollregion] 3]
3151 if {$ymax == {}} return
3152 set yfrac [lindex [$canv yview] 0]
3153 set x [expr {$hoverx + 2 * $linespc}]
3154 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3155 set x0 [expr {$x - 2 * $lthickness}]
3156 set y0 [expr {$y - 2 * $lthickness}]
3157 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3158 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3159 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3160 -fill \#ffff80 -outline black -width 1 -tags hover]
3162 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3166 proc clickisonarrow {id y} {
3167 global mainline mainlinearrow sidelines lthickness
3169 set thresh [expr {2 * $lthickness + 6}]
3170 if {[info exists mainline($id)]} {
3171 if {$mainlinearrow($id) ne "none"} {
3172 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3177 if {[info exists sidelines($id)]} {
3178 foreach ls $sidelines($id) {
3179 set coords [lindex $ls 0]
3180 set arrow [lindex $ls 2]
3181 if {$arrow eq "first" || $arrow eq "both"} {
3182 if {abs([lindex $coords 1] - $y) < $thresh} {
3186 if {$arrow eq "last" || $arrow eq "both"} {
3187 if {abs([lindex $coords end] - $y) < $thresh} {
3196 proc arrowjump {id dirn y} {
3197 global mainline sidelines canv
3200 if {$dirn eq "down"} {
3201 if {[info exists mainline($id)]} {
3202 set y1 [lindex $mainline($id) 1]
3207 if {[info exists sidelines($id)]} {
3208 foreach ls $sidelines($id) {
3209 set y1 [lindex $ls 0 1]
3210 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3216 if {[info exists sidelines($id)]} {
3217 foreach ls $sidelines($id) {
3218 set y1 [lindex $ls 0 end]
3219 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3225 if {$yt eq {}} return
3226 set ymax [lindex [$canv cget -scrollregion] 3]
3227 if {$ymax eq {} || $ymax <= 0} return
3228 set view [$canv yview]
3229 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3230 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3234 $canv yview moveto $yfrac
3237 proc lineclick {x y id isnew} {
3238 global ctext commitinfo children cflist canv thickerline
3244 # draw this line thicker than normal
3248 set ymax [lindex [$canv cget -scrollregion] 3]
3249 if {$ymax eq {}} return
3250 set yfrac [lindex [$canv yview] 0]
3251 set y [expr {$y + $yfrac * $ymax}]
3253 set dirn [clickisonarrow $id $y]
3255 arrowjump $id $dirn $y
3260 addtohistory [list lineclick $x $y $id 0]
3262 # fill the details pane with info about this line
3263 $ctext conf -state normal
3264 $ctext delete 0.0 end
3265 $ctext tag conf link -foreground blue -underline 1
3266 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3267 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3268 $ctext insert end "Parent:\t"
3269 $ctext insert end $id [list link link0]
3270 $ctext tag bind link0 <1> [list selbyid $id]
3271 set info $commitinfo($id)
3272 $ctext insert end "\n\t[lindex $info 0]\n"
3273 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
3274 set date [formatdate [lindex $info 2]]
3275 $ctext insert end "\tDate:\t$date\n"
3276 if {[info exists children($id)]} {
3277 $ctext insert end "\nChildren:"
3279 foreach child $children($id) {
3281 set info $commitinfo($child)
3282 $ctext insert end "\n\t"
3283 $ctext insert end $child [list link link$i]
3284 $ctext tag bind link$i <1> [list selbyid $child]
3285 $ctext insert end "\n\t[lindex $info 0]"
3286 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
3287 set date [formatdate [lindex $info 2]]
3288 $ctext insert end "\n\tDate:\t$date\n"
3291 $ctext conf -state disabled
3293 $cflist delete 0 end
3296 proc normalline {} {
3298 if {[info exists thickerline]} {
3299 drawlines $thickerline 0 1
3306 if {[info exists idline($id)]} {
3307 selectline $idline($id) 1
3313 if {![info exists startmstime]} {
3314 set startmstime [clock clicks -milliseconds]
3316 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3319 proc rowmenu {x y id} {
3320 global rowctxmenu idline selectedline rowmenuid
3322 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3327 $rowctxmenu entryconfigure 0 -state $state
3328 $rowctxmenu entryconfigure 1 -state $state
3329 $rowctxmenu entryconfigure 2 -state $state
3331 tk_popup $rowctxmenu $x $y
3334 proc diffvssel {dirn} {
3335 global rowmenuid selectedline lineid
3337 if {![info exists selectedline]} return
3339 set oldid $lineid($selectedline)
3340 set newid $rowmenuid
3342 set oldid $rowmenuid
3343 set newid $lineid($selectedline)
3345 addtohistory [list doseldiff $oldid $newid]
3346 doseldiff $oldid $newid
3349 proc doseldiff {oldid newid} {
3353 $ctext conf -state normal
3354 $ctext delete 0.0 end
3355 $ctext mark set fmark.0 0.0
3356 $ctext mark gravity fmark.0 left
3357 $cflist delete 0 end
3358 $cflist insert end "Top"
3359 $ctext insert end "From "
3360 $ctext tag conf link -foreground blue -underline 1
3361 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3362 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3363 $ctext tag bind link0 <1> [list selbyid $oldid]
3364 $ctext insert end $oldid [list link link0]
3365 $ctext insert end "\n "
3366 $ctext insert end [lindex $commitinfo($oldid) 0]
3367 $ctext insert end "\n\nTo "
3368 $ctext tag bind link1 <1> [list selbyid $newid]
3369 $ctext insert end $newid [list link link1]
3370 $ctext insert end "\n "
3371 $ctext insert end [lindex $commitinfo($newid) 0]
3372 $ctext insert end "\n"
3373 $ctext conf -state disabled
3374 $ctext tag delete Comments
3375 $ctext tag remove found 1.0 end
3376 startdiff [list $newid $oldid]
3380 global rowmenuid currentid commitinfo patchtop patchnum
3382 if {![info exists currentid]} return
3383 set oldid $currentid
3384 set oldhead [lindex $commitinfo($oldid) 0]
3385 set newid $rowmenuid
3386 set newhead [lindex $commitinfo($newid) 0]
3389 catch {destroy $top}
3391 label $top.title -text "Generate patch"
3392 grid $top.title - -pady 10
3393 label $top.from -text "From:"
3394 entry $top.fromsha1 -width 40 -relief flat
3395 $top.fromsha1 insert 0 $oldid
3396 $top.fromsha1 conf -state readonly
3397 grid $top.from $top.fromsha1 -sticky w
3398 entry $top.fromhead -width 60 -relief flat
3399 $top.fromhead insert 0 $oldhead
3400 $top.fromhead conf -state readonly
3401 grid x $top.fromhead -sticky w
3402 label $top.to -text "To:"
3403 entry $top.tosha1 -width 40 -relief flat
3404 $top.tosha1 insert 0 $newid
3405 $top.tosha1 conf -state readonly
3406 grid $top.to $top.tosha1 -sticky w
3407 entry $top.tohead -width 60 -relief flat
3408 $top.tohead insert 0 $newhead
3409 $top.tohead conf -state readonly
3410 grid x $top.tohead -sticky w
3411 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3412 grid $top.rev x -pady 10
3413 label $top.flab -text "Output file:"
3414 entry $top.fname -width 60
3415 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3417 grid $top.flab $top.fname -sticky w
3419 button $top.buts.gen -text "Generate" -command mkpatchgo
3420 button $top.buts.can -text "Cancel" -command mkpatchcan
3421 grid $top.buts.gen $top.buts.can
3422 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3423 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3424 grid $top.buts - -pady 10 -sticky ew
3428 proc mkpatchrev {} {
3431 set oldid [$patchtop.fromsha1 get]
3432 set oldhead [$patchtop.fromhead get]
3433 set newid [$patchtop.tosha1 get]
3434 set newhead [$patchtop.tohead get]
3435 foreach e [list fromsha1 fromhead tosha1 tohead] \
3436 v [list $newid $newhead $oldid $oldhead] {
3437 $patchtop.$e conf -state normal
3438 $patchtop.$e delete 0 end
3439 $patchtop.$e insert 0 $v
3440 $patchtop.$e conf -state readonly
3447 set oldid [$patchtop.fromsha1 get]
3448 set newid [$patchtop.tosha1 get]
3449 set fname [$patchtop.fname get]
3450 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3451 error_popup "Error creating patch: $err"
3453 catch {destroy $patchtop}
3457 proc mkpatchcan {} {
3460 catch {destroy $patchtop}
3465 global rowmenuid mktagtop commitinfo
3469 catch {destroy $top}
3471 label $top.title -text "Create tag"
3472 grid $top.title - -pady 10
3473 label $top.id -text "ID:"
3474 entry $top.sha1 -width 40 -relief flat
3475 $top.sha1 insert 0 $rowmenuid
3476 $top.sha1 conf -state readonly
3477 grid $top.id $top.sha1 -sticky w
3478 entry $top.head -width 60 -relief flat
3479 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3480 $top.head conf -state readonly
3481 grid x $top.head -sticky w
3482 label $top.tlab -text "Tag name:"
3483 entry $top.tag -width 60
3484 grid $top.tlab $top.tag -sticky w
3486 button $top.buts.gen -text "Create" -command mktaggo
3487 button $top.buts.can -text "Cancel" -command mktagcan
3488 grid $top.buts.gen $top.buts.can
3489 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3490 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3491 grid $top.buts - -pady 10 -sticky ew
3496 global mktagtop env tagids idtags
3498 set id [$mktagtop.sha1 get]
3499 set tag [$mktagtop.tag get]
3501 error_popup "No tag name specified"
3504 if {[info exists tagids($tag)]} {
3505 error_popup "Tag \"$tag\" already exists"
3510 set fname [file join $dir "refs/tags" $tag]
3511 set f [open $fname w]
3515 error_popup "Error creating tag: $err"
3519 set tagids($tag) $id
3520 lappend idtags($id) $tag
3524 proc redrawtags {id} {
3525 global canv linehtag idline idpos selectedline
3527 if {![info exists idline($id)]} return
3528 $canv delete tag.$id
3529 set xt [eval drawtags $id $idpos($id)]
3530 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3531 if {[info exists selectedline] && $selectedline == $idline($id)} {
3532 selectline $selectedline 0
3539 catch {destroy $mktagtop}
3548 proc writecommit {} {
3549 global rowmenuid wrcomtop commitinfo wrcomcmd
3551 set top .writecommit
3553 catch {destroy $top}
3555 label $top.title -text "Write commit to file"
3556 grid $top.title - -pady 10
3557 label $top.id -text "ID:"
3558 entry $top.sha1 -width 40 -relief flat
3559 $top.sha1 insert 0 $rowmenuid
3560 $top.sha1 conf -state readonly
3561 grid $top.id $top.sha1 -sticky w
3562 entry $top.head -width 60 -relief flat
3563 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3564 $top.head conf -state readonly
3565 grid x $top.head -sticky w
3566 label $top.clab -text "Command:"
3567 entry $top.cmd -width 60 -textvariable wrcomcmd
3568 grid $top.clab $top.cmd -sticky w -pady 10
3569 label $top.flab -text "Output file:"
3570 entry $top.fname -width 60
3571 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3572 grid $top.flab $top.fname -sticky w
3574 button $top.buts.gen -text "Write" -command wrcomgo
3575 button $top.buts.can -text "Cancel" -command wrcomcan
3576 grid $top.buts.gen $top.buts.can
3577 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3578 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3579 grid $top.buts - -pady 10 -sticky ew
3586 set id [$wrcomtop.sha1 get]
3587 set cmd "echo $id | [$wrcomtop.cmd get]"
3588 set fname [$wrcomtop.fname get]
3589 if {[catch {exec sh -c $cmd >$fname &} err]} {
3590 error_popup "Error writing commit: $err"
3592 catch {destroy $wrcomtop}
3599 catch {destroy $wrcomtop}
3603 proc listrefs {id} {
3604 global idtags idheads idotherrefs
3607 if {[info exists idtags($id)]} {
3611 if {[info exists idheads($id)]} {
3615 if {[info exists idotherrefs($id)]} {
3616 set z $idotherrefs($id)
3618 return [list $x $y $z]
3621 proc rereadrefs {} {
3622 global idtags idheads idotherrefs
3623 global tagids headids otherrefids
3625 set refids [concat [array names idtags] \
3626 [array names idheads] [array names idotherrefs]]
3627 foreach id $refids {
3628 if {![info exists ref($id)]} {
3629 set ref($id) [listrefs $id]
3632 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
3636 set refids [lsort -unique [concat $refids [array names idtags] \
3637 [array names idheads] [array names idotherrefs]]]
3638 foreach id $refids {
3639 set v [listrefs $id]
3640 if {![info exists ref($id)] || $ref($id) != $v} {
3646 proc showtag {tag isnew} {
3647 global ctext cflist tagcontents tagids linknum
3650 addtohistory [list showtag $tag 0]
3652 $ctext conf -state normal
3653 $ctext delete 0.0 end
3655 if {[info exists tagcontents($tag)]} {
3656 set text $tagcontents($tag)
3658 set text "Tag: $tag\nId: $tagids($tag)"
3660 appendwithlinks $text
3661 $ctext conf -state disabled
3662 $cflist delete 0 end
3671 proc formatdate {d} {
3672 global hours nhours tfd
3674 set hr [expr {$d / 3600}]
3675 set ms [expr {$d % 3600}]
3676 if {![info exists hours($hr)]} {
3677 set hours($hr) [clock format $d -format "%Y-%m-%d %H"]
3681 set minsec [format "%.2d:%.2d" [expr {$ms/60}] [expr {$ms%60}]]
3682 return "$hours($hr):$minsec"
3688 set diffopts "-U 5 -p"
3689 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3691 set mainfont {Helvetica 9}
3692 set textfont {Courier 9}
3693 set findmergefiles 0
3699 set colors {green red blue magenta darkgrey brown orange}
3701 catch {source ~/.gitk}
3703 set namefont $mainfont
3705 lappend namefont bold
3710 switch -regexp -- $arg {
3712 "^-b" { set boldnames 1 }
3713 "^-d" { set datemode 1 }
3714 "^-r" { set revlistorder 1 }
3716 lappend revtreeargs $arg
3731 getcommits $revtreeargs