2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish
"$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
12 if {[info exists env
(GIT_DIR
)]} {
19 proc getcommits
{rargs
} {
20 global commits commfd phase canv mainfont env
21 global startmsecs nextupdate
22 global ctext maincursor textcursor leftover
24 # check that we can find a .git directory somewhere...
26 if {![file isdirectory
$gitdir]} {
27 error_popup
"Cannot find the git directory \"$gitdir\"."
32 set startmsecs
[clock clicks
-milliseconds]
33 set nextupdate
[expr $startmsecs + 100]
35 set parse_args
[concat
--default HEAD
$rargs]
36 set parsed_args
[split [eval exec git-rev-parse
$parse_args] "\n"]
38 # if git-rev-parse failed for some reason...
42 set parsed_args
$rargs
45 set commfd
[open
"|git-rev-list --header --topo-order $parsed_args" r
]
47 puts stderr
"Error executing git-rev-list: $err"
51 fconfigure
$commfd -blocking 0 -translation binary
52 fileevent
$commfd readable
"getcommitlines $commfd"
54 $canv create text
3 3 -anchor nw
-text "Reading commits..." \
55 -font $mainfont -tags textitems
56 . config
-cursor watch
57 $ctext config
-cursor watch
60 proc getcommitlines
{commfd
} {
61 global commits parents cdate children nchildren
62 global commitlisted phase commitinfo nextupdate
63 global stopped redisplaying leftover
65 set stuff
[read $commfd]
67 if {![eof
$commfd]} return
68 # set it blocking so we wait for the process to terminate
69 fconfigure
$commfd -blocking 1
70 if {![catch
{close
$commfd} err
]} {
71 after idle finishcommits
74 if {[string range
$err 0 4] == "usage"} {
76 {Gitk
: error reading commits
: bad arguments to git-rev-list.
77 (Note
: arguments to gitk are passed to git-rev-list
78 to allow selection of commits to be displayed.
)}
80 set err
"Error reading commits: $err"
87 set i
[string first
"\0" $stuff $start]
89 append leftover
[string range
$stuff $start end
]
92 set cmit
[string range
$stuff $start [expr {$i - 1}]]
94 set cmit
"$leftover$cmit"
97 set start
[expr {$i + 1}]
98 if {![regexp
{^
([0-9a-f]{40})\n} $cmit match id
]} {
100 if {[string length
$shortcmit] > 80} {
101 set shortcmit
"[string range $shortcmit 0 80]..."
103 error_popup
"Can't parse git-rev-list output: {$shortcmit}"
106 set cmit
[string range
$cmit 41 end
]
108 set commitlisted
($id) 1
109 parsecommit
$id $cmit 1
111 if {[clock clicks
-milliseconds] >= $nextupdate} {
114 while {$redisplaying} {
118 set phase
"getcommits"
119 foreach id
$commits {
122 if {[clock clicks
-milliseconds] >= $nextupdate} {
132 global commfd nextupdate
135 fileevent
$commfd readable
{}
137 fileevent
$commfd readable
"getcommitlines $commfd"
140 proc readcommit
{id
} {
141 if [catch
{set contents
[exec git-cat-file commit
$id]}] return
142 parsecommit
$id $contents 0
145 proc parsecommit
{id contents listed
} {
146 global commitinfo children nchildren parents nparents cdate ncleft
155 if {![info exists nchildren
($id)]} {
162 foreach line
[split $contents "\n"] {
167 set tag
[lindex
$line 0]
168 if {$tag == "parent"} {
169 set p
[lindex
$line 1]
170 if {![info exists nchildren
($p)]} {
175 lappend parents
($id) $p
177 # sometimes we get a commit that lists a parent twice...
178 if {$listed && [lsearch
-exact $children($p) $id] < 0} {
179 lappend children
($p) $id
183 } elseif
{$tag == "author"} {
184 set x
[expr {[llength
$line] - 2}]
185 set audate
[lindex
$line $x]
186 set auname
[lrange
$line 1 [expr {$x - 1}]]
187 } elseif
{$tag == "committer"} {
188 set x
[expr {[llength
$line] - 2}]
189 set comdate
[lindex
$line $x]
190 set comname
[lrange
$line 1 [expr {$x - 1}]]
194 if {$comment == {}} {
195 set headline
[string trim
$line]
200 # git-rev-list indents the comment by 4 spaces;
201 # if we got this via git-cat-file, add the indentation
208 set audate
[clock format
$audate -format "%Y-%m-%d %H:%M:%S"]
210 if {$comdate != {}} {
211 set cdate
($id) $comdate
212 set comdate
[clock format
$comdate -format "%Y-%m-%d %H:%M:%S"]
214 set commitinfo
($id) [list
$headline $auname $audate \
215 $comname $comdate $comment]
219 global tagids idtags headids idheads
220 set tags
[glob
-nocomplain -types f
[gitdir
]/refs
/tags
/*]
225 if {[regexp
{^
[0-9a-f]{40}} $line id
]} {
226 set direct
[file tail $f]
227 set tagids
($direct) $id
228 lappend idtags
($id) $direct
229 set contents
[split [exec git-cat-file tag
$id] "\n"]
233 foreach l
$contents {
235 switch
-- [lindex
$l 0] {
236 "object" {set obj
[lindex
$l 1]}
237 "type" {set type [lindex
$l 1]}
238 "tag" {set tag
[string range
$l 4 end
]}
241 if {$obj != {} && $type == "commit" && $tag != {}} {
242 set tagids
($tag) $obj
243 lappend idtags
($obj) $tag
249 set heads
[glob
-nocomplain -types f
[gitdir
]/refs
/heads
/*]
253 set line
[read $fd 40]
254 if {[regexp
{^
[0-9a-f]{40}} $line id
]} {
255 set head [file tail $f]
256 set headids
($head) $line
257 lappend idheads
($line) $head
264 proc error_popup msg
{
268 message
$w.m
-text $msg -justify center
-aspect 400
269 pack
$w.m
-side top
-fill x
-padx 20 -pady 20
270 button
$w.ok
-text OK
-command "destroy $w"
271 pack
$w.ok
-side bottom
-fill x
272 bind $w <Visibility
> "grab $w; focus $w"
277 global canv canv2 canv3 linespc charspc ctext cflist textfont
278 global findtype findtypemenu findloc findstring fstring geometry
279 global entries sha1entry sha1string sha1but
280 global maincursor textcursor
281 global rowctxmenu gaudydiff mergemax
284 .bar add cascade
-label "File" -menu .bar.
file
286 .bar.
file add
command -label "Quit" -command doquit
288 .bar add cascade
-label "Help" -menu .bar.
help
289 .bar.
help add
command -label "About gitk" -command about
290 . configure
-menu .bar
292 if {![info exists geometry
(canv1
)]} {
293 set geometry
(canv1
) [expr 45 * $charspc]
294 set geometry
(canv2
) [expr 30 * $charspc]
295 set geometry
(canv3
) [expr 15 * $charspc]
296 set geometry
(canvh
) [expr 25 * $linespc + 4]
297 set geometry
(ctextw
) 80
298 set geometry
(ctexth
) 30
299 set geometry
(cflistw
) 30
301 panedwindow .ctop
-orient vertical
302 if {[info exists geometry
(width
)]} {
303 .ctop conf
-width $geometry(width
) -height $geometry(height
)
304 set texth
[expr {$geometry(height
) - $geometry(canvh
) - 56}]
305 set geometry
(ctexth
) [expr {($texth - 8) /
306 [font metrics
$textfont -linespace]}]
310 pack .ctop.top.bar
-side bottom
-fill x
311 set cscroll .ctop.top.csb
312 scrollbar
$cscroll -command {allcanvs yview
} -highlightthickness 0
313 pack
$cscroll -side right
-fill y
314 panedwindow .ctop.top.clist
-orient horizontal
-sashpad 0 -handlesize 4
315 pack .ctop.top.clist
-side top
-fill both
-expand 1
317 set canv .ctop.top.clist.canv
318 canvas
$canv -height $geometry(canvh
) -width $geometry(canv1
) \
320 -yscrollincr $linespc -yscrollcommand "$cscroll set"
321 .ctop.top.clist add
$canv
322 set canv2 .ctop.top.clist.canv2
323 canvas
$canv2 -height $geometry(canvh
) -width $geometry(canv2
) \
324 -bg white
-bd 0 -yscrollincr $linespc
325 .ctop.top.clist add
$canv2
326 set canv3 .ctop.top.clist.canv3
327 canvas
$canv3 -height $geometry(canvh
) -width $geometry(canv3
) \
328 -bg white
-bd 0 -yscrollincr $linespc
329 .ctop.top.clist add
$canv3
330 bind .ctop.top.clist
<Configure
> {resizeclistpanes
%W
%w
}
332 set sha1entry .ctop.top.bar.sha1
333 set entries
$sha1entry
334 set sha1but .ctop.top.bar.sha1label
335 button
$sha1but -text "SHA1 ID: " -state disabled
-relief flat \
336 -command gotocommit
-width 8
337 $sha1but conf
-disabledforeground [$sha1but cget
-foreground]
338 pack .ctop.top.bar.sha1label
-side left
339 entry
$sha1entry -width 40 -font $textfont -textvariable sha1string
340 trace add variable sha1string
write sha1change
341 pack
$sha1entry -side left
-pady 2
342 button .ctop.top.bar.findbut
-text "Find" -command dofind
343 pack .ctop.top.bar.findbut
-side left
345 set fstring .ctop.top.bar.findstring
346 lappend entries
$fstring
347 entry
$fstring -width 30 -font $textfont -textvariable findstring
348 pack
$fstring -side left
-expand 1 -fill x
350 set findtypemenu
[tk_optionMenu .ctop.top.bar.findtype \
351 findtype Exact IgnCase Regexp
]
352 set findloc
"All fields"
353 tk_optionMenu .ctop.top.bar.findloc findloc
"All fields" Headline \
354 Comments Author Committer Files Pickaxe
355 pack .ctop.top.bar.findloc
-side right
356 pack .ctop.top.bar.findtype
-side right
357 # for making sure type==Exact whenever loc==Pickaxe
358 trace add variable findloc
write findlocchange
360 panedwindow .ctop.cdet
-orient horizontal
362 frame .ctop.cdet.left
363 set ctext .ctop.cdet.left.ctext
364 text
$ctext -bg white
-state disabled
-font $textfont \
365 -width $geometry(ctextw
) -height $geometry(ctexth
) \
366 -yscrollcommand ".ctop.cdet.left.sb set"
367 scrollbar .ctop.cdet.left.sb
-command "$ctext yview"
368 pack .ctop.cdet.left.sb
-side right
-fill y
369 pack
$ctext -side left
-fill both
-expand 1
370 .ctop.cdet add .ctop.cdet.left
372 $ctext tag conf filesep
-font [concat
$textfont bold
] -back "#aaaaaa"
374 $ctext tag conf hunksep
-back blue
-fore white
375 $ctext tag conf d0
-back "#ff8080"
376 $ctext tag conf d1
-back green
378 $ctext tag conf hunksep
-fore blue
379 $ctext tag conf d0
-fore red
380 $ctext tag conf d1
-fore "#00a000"
381 $ctext tag conf m0
-fore red
382 $ctext tag conf m1
-fore blue
383 $ctext tag conf m2
-fore green
384 $ctext tag conf m3
-fore purple
385 $ctext tag conf
m4 -fore brown
386 $ctext tag conf mmax
-fore darkgrey
388 $ctext tag conf mresult
-font [concat
$textfont bold
]
389 $ctext tag conf msep
-font [concat
$textfont bold
]
390 $ctext tag conf found
-back yellow
393 frame .ctop.cdet.right
394 set cflist .ctop.cdet.right.cfiles
395 listbox
$cflist -bg white
-selectmode extended
-width $geometry(cflistw
) \
396 -yscrollcommand ".ctop.cdet.right.sb set"
397 scrollbar .ctop.cdet.right.sb
-command "$cflist yview"
398 pack .ctop.cdet.right.sb
-side right
-fill y
399 pack
$cflist -side left
-fill both
-expand 1
400 .ctop.cdet add .ctop.cdet.right
401 bind .ctop.cdet
<Configure
> {resizecdetpanes
%W
%w
}
403 pack .ctop
-side top
-fill both
-expand 1
405 bindall
<1> {selcanvline
%W
%x
%y
}
406 #bindall <B1-Motion> {selcanvline %W %x %y}
407 bindall
<ButtonRelease-4
> "allcanvs yview scroll -5 units"
408 bindall
<ButtonRelease-5
> "allcanvs yview scroll 5 units"
409 bindall
<2> "allcanvs scan mark 0 %y"
410 bindall
<B2-Motion
> "allcanvs scan dragto 0 %y"
411 bind .
<Key-Up
> "selnextline -1"
412 bind .
<Key-Down
> "selnextline 1"
413 bind .
<Key-Prior
> "allcanvs yview scroll -1 pages"
414 bind .
<Key-Next
> "allcanvs yview scroll 1 pages"
415 bindkey
<Key-Delete
> "$ctext yview scroll -1 pages"
416 bindkey
<Key-BackSpace
> "$ctext yview scroll -1 pages"
417 bindkey
<Key-space
> "$ctext yview scroll 1 pages"
418 bindkey p
"selnextline -1"
419 bindkey n
"selnextline 1"
420 bindkey b
"$ctext yview scroll -1 pages"
421 bindkey d
"$ctext yview scroll 18 units"
422 bindkey u
"$ctext yview scroll -18 units"
423 bindkey
/ {findnext
1}
424 bindkey
<Key-Return
> {findnext
0}
427 bind .
<Control-q
> doquit
428 bind .
<Control-f
> dofind
429 bind .
<Control-g
> {findnext
0}
430 bind .
<Control-r
> findprev
431 bind .
<Control-equal
> {incrfont
1}
432 bind .
<Control-KP_Add
> {incrfont
1}
433 bind .
<Control-minus
> {incrfont
-1}
434 bind .
<Control-KP_Subtract
> {incrfont
-1}
435 bind $cflist <<ListboxSelect>> listboxsel
436 bind . <Destroy> {savestuff %W}
437 bind . <Button-1> "click %W"
438 bind $fstring <Key-Return> dofind
439 bind $sha1entry <Key-Return> gotocommit
440 bind $sha1entry <<PasteSelection>> clearsha1
442 set maincursor [. cget -cursor]
443 set textcursor [$ctext cget -cursor]
445 set rowctxmenu .rowctxmenu
446 menu $rowctxmenu -tearoff 0
447 $rowctxmenu add command -label "Diff this -> selected" \
448 -command {diffvssel 0}
449 $rowctxmenu add command -label "Diff selected -> this" \
450 -command {diffvssel 1}
451 $rowctxmenu add command -label "Make patch" -command mkpatch
452 $rowctxmenu add command -label "Create tag" -command mktag
453 $rowctxmenu add command -label "Write commit to file" -command writecommit
456 # when we make a key binding for the toplevel, make sure
457 # it doesn't get triggered when that key is pressed in the
458 # find string entry widget.
459 proc bindkey {ev script} {
462 set escript [bind Entry $ev]
463 if {$escript == {}} {
464 set escript [bind Entry <Key>]
467 bind $e $ev "$escript; break"
471 # set the focus back to the toplevel for any click outside
482 global canv canv2 canv3 ctext cflist mainfont textfont
483 global stuffsaved findmergefiles gaudydiff maxgraphpct
485 if {$stuffsaved} return
486 if {![winfo viewable .]} return
488 set f [open "~/.gitk-new" w]
489 puts $f [list set mainfont $mainfont]
490 puts $f [list set textfont $textfont]
491 puts $f [list set findmergefiles $findmergefiles]
492 puts $f [list set gaudydiff $gaudydiff]
493 puts $f [list set maxgraphpct $maxgraphpct]
494 puts $f "set geometry(width) [winfo width .ctop]"
495 puts $f "set geometry(height) [winfo height .ctop]"
496 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
497 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
498 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
499 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
500 set wid [expr {([winfo width $ctext] - 8) \
501 / [font measure $textfont "0"]}]
502 puts $f "set geometry(ctextw) $wid"
503 set wid [expr {([winfo width $cflist] - 11) \
504 / [font measure [$cflist cget -font] "0"]}]
505 puts $f "set geometry(cflistw) $wid"
507 file rename -force "~/.gitk-new" "~/.gitk"
512 proc resizeclistpanes {win w} {
514 if [info exists oldwidth($win)] {
515 set s0 [$win sash coord 0]
516 set s1 [$win sash coord 1]
518 set sash0 [expr {int($w/2 - 2)}]
519 set sash1 [expr {int($w*5/6 - 2)}]
521 set factor [expr {1.0 * $w / $oldwidth($win)}]
522 set sash0 [expr {int($factor * [lindex $s0 0])}]
523 set sash1 [expr {int($factor * [lindex $s1 0])}]
527 if {$sash1 < $sash0 + 20} {
528 set sash1 [expr $sash0 + 20]
530 if {$sash1 > $w - 10} {
531 set sash1 [expr $w - 10]
532 if {$sash0 > $sash1 - 20} {
533 set sash0 [expr $sash1 - 20]
537 $win sash place 0 $sash0 [lindex $s0 1]
538 $win sash place 1 $sash1 [lindex $s1 1]
540 set oldwidth($win) $w
543 proc resizecdetpanes {win w} {
545 if [info exists oldwidth($win)] {
546 set s0 [$win sash coord 0]
548 set sash0 [expr {int($w*3/4 - 2)}]
550 set factor [expr {1.0 * $w / $oldwidth($win)}]
551 set sash0 [expr {int($factor * [lindex $s0 0])}]
555 if {$sash0 > $w - 15} {
556 set sash0 [expr $w - 15]
559 $win sash place 0 $sash0 [lindex $s0 1]
561 set oldwidth($win) $w
565 global canv canv2 canv3
571 proc bindall {event action} {
572 global canv canv2 canv3
573 bind $canv $event $action
574 bind $canv2 $event $action
575 bind $canv3 $event $action
580 if {[winfo exists $w]} {
585 wm title $w "About gitk"
589 Copyright © 2005 Paul Mackerras
591 Use and redistribute under the terms of the GNU General Public License} \
592 -justify center -aspect 400
593 pack $w.m -side top -fill x -padx 20 -pady 20
594 button $w.ok -text Close -command "destroy $w"
595 pack $w.ok -side bottom
598 proc assigncolor {id} {
599 global commitinfo colormap commcolors colors nextcolor
600 global parents nparents children nchildren
601 global cornercrossings crossings
603 if [info exists colormap($id)] return
604 set ncolors [llength $colors]
605 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
606 set child [lindex $children($id) 0]
607 if {[info exists colormap($child)]
608 && $nparents($child) == 1} {
609 set colormap($id) $colormap($child)
614 if {[info exists cornercrossings($id)]} {
615 foreach x $cornercrossings($id) {
616 if {[info exists colormap($x)]
617 && [lsearch -exact $badcolors $colormap($x)] < 0} {
618 lappend badcolors $colormap($x)
621 if {[llength $badcolors] >= $ncolors} {
625 set origbad $badcolors
626 if {[llength $badcolors] < $ncolors - 1} {
627 if {[info exists crossings($id)]} {
628 foreach x $crossings($id) {
629 if {[info exists colormap($x)]
630 && [lsearch -exact $badcolors $colormap($x)] < 0} {
631 lappend badcolors $colormap($x)
634 if {[llength $badcolors] >= $ncolors} {
635 set badcolors $origbad
638 set origbad $badcolors
640 if {[llength $badcolors] < $ncolors - 1} {
641 foreach child $children($id) {
642 if {[info exists colormap($child)]
643 && [lsearch -exact $badcolors $colormap($child)] < 0} {
644 lappend badcolors $colormap($child)
646 if {[info exists parents($child)]} {
647 foreach p $parents($child) {
648 if {[info exists colormap($p)]
649 && [lsearch -exact $badcolors $colormap($p)] < 0} {
650 lappend badcolors $colormap($p)
655 if {[llength $badcolors] >= $ncolors} {
656 set badcolors $origbad
659 for {set i 0} {$i <= $ncolors} {incr i} {
660 set c [lindex $colors $nextcolor]
661 if {[incr nextcolor] >= $ncolors} {
664 if {[lsearch -exact $badcolors $c]} break
670 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
671 global mainline sidelines
672 global nchildren ncleft
679 set lthickness [expr {int($linespc / 9) + 1}]
680 catch {unset mainline}
681 catch {unset sidelines}
682 foreach id [array names nchildren] {
683 set ncleft($id) $nchildren($id)
687 proc bindline {t id} {
690 $canv bind $t <Enter> "lineenter %x %y $id"
691 $canv bind $t <Motion> "linemotion %x %y $id"
692 $canv bind $t <Leave> "lineleave $id"
693 $canv bind $t <Button-1> "lineclick %x %y $id"
696 proc drawcommitline {level} {
697 global parents children nparents nchildren todo
698 global canv canv2 canv3 mainfont namefont canvy linespc
699 global lineid linehtag linentag linedtag commitinfo
700 global colormap numcommits currentparents dupparents
701 global oldlevel oldnlines oldtodo
702 global idtags idline idheads
703 global lineno lthickness mainline sidelines
704 global commitlisted rowtextx idpos
708 set id [lindex $todo $level]
709 set lineid($lineno) $id
710 set idline($id) $lineno
711 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
712 if {![info exists commitinfo($id)]} {
714 if {![info exists commitinfo($id)]} {
715 set commitinfo($id) {"No commit information available"}
720 set currentparents {}
722 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
723 foreach p $parents($id) {
724 if {[lsearch -exact $currentparents $p] < 0} {
725 lappend currentparents $p
727 # remember that this parent was listed twice
728 lappend dupparents $p
732 set x [xcoord $level $level $lineno]
734 set canvy [expr $canvy + $linespc]
735 allcanvs conf -scrollregion \
736 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
737 if {[info exists mainline($id)]} {
738 lappend mainline($id) $x $y1
739 set t [$canv create line $mainline($id) \
740 -width $lthickness -fill $colormap($id)]
744 if {[info exists sidelines($id)]} {
745 foreach ls $sidelines($id) {
746 set coords [lindex $ls 0]
747 set thick [lindex $ls 1]
748 set t [$canv create line $coords -fill $colormap($id) \
749 -width [expr {$thick * $lthickness}]]
754 set orad [expr {$linespc / 3}]
755 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
756 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
757 -fill $ofill -outline black -width 1]
759 $canv bind $t <1> {selcanvline {} %x %y}
760 set xt [xcoord [llength $todo] $level $lineno]
761 if {[llength $currentparents] > 2} {
762 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
764 set rowtextx($lineno) $xt
765 set idpos($id) [list $x $xt $y1]
766 if {[info exists idtags($id)] || [info exists idheads($id)]} {
767 set xt [drawtags $id $x $xt $y1]
769 set headline [lindex $commitinfo($id) 0]
770 set name [lindex $commitinfo($id) 1]
771 set date [lindex $commitinfo($id) 2]
772 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
773 -text $headline -font $mainfont ]
774 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
775 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
776 -text $name -font $namefont]
777 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
778 -text $date -font $mainfont]
781 proc drawtags {id x xt y1} {
782 global idtags idheads
783 global linespc lthickness
788 if {[info exists idtags($id)]} {
789 set marks $idtags($id)
790 set ntags [llength $marks]
792 if {[info exists idheads($id)]} {
793 set marks [concat $marks $idheads($id)]
799 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
800 set yt [expr $y1 - 0.5 * $linespc]
801 set yb [expr $yt + $linespc - 1]
805 set wid [font measure $mainfont $tag]
808 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
810 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
811 -width $lthickness -fill black -tags tag.$id]
813 foreach tag $marks x $xvals wid $wvals {
814 set xl [expr $x + $delta]
815 set xr [expr $x + $delta + $wid + $lthickness]
816 if {[incr ntags -1] >= 0} {
818 $canv create polygon $x [expr $yt + $delta] $xl $yt\
819 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
820 -width 1 -outline black -fill yellow -tags tag.$id
823 set xl [expr $xl - $delta/2]
824 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
825 -width 1 -outline black -fill green -tags tag.$id
827 $canv create text $xl $y1 -anchor w -text $tag \
828 -font $mainfont -tags tag.$id
833 proc updatetodo {level noshortcut} {
834 global currentparents ncleft todo
835 global mainline oldlevel oldtodo oldnlines
836 global canvy linespc mainline
837 global commitinfo lineno xspc1
841 set oldnlines [llength $todo]
842 if {!$noshortcut && [llength $currentparents] == 1} {
843 set p [lindex $currentparents 0]
844 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
846 set x [xcoord $level $level $lineno]
847 set y [expr $canvy - $linespc]
848 set mainline($p) [list $x $y]
849 set todo [lreplace $todo $level $level $p]
850 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
855 set todo [lreplace $todo $level $level]
857 foreach p $currentparents {
859 set k [lsearch -exact $todo $p]
861 set todo [linsert $todo $i $p]
868 proc notecrossings {id lo hi corner} {
869 global oldtodo crossings cornercrossings
871 for {set i $lo} {[incr i] < $hi} {} {
872 set p [lindex $oldtodo $i]
873 if {$p == {}} continue
875 if {![info exists cornercrossings($id)]
876 || [lsearch -exact $cornercrossings($id) $p] < 0} {
877 lappend cornercrossings($id) $p
879 if {![info exists cornercrossings($p)]
880 || [lsearch -exact $cornercrossings($p) $id] < 0} {
881 lappend cornercrossings($p) $id
884 if {![info exists crossings($id)]
885 || [lsearch -exact $crossings($id) $p] < 0} {
886 lappend crossings($id) $p
888 if {![info exists crossings($p)]
889 || [lsearch -exact $crossings($p) $id] < 0} {
890 lappend crossings($p) $id
896 proc xcoord {i level ln} {
897 global canvx0 xspc1 xspc2
899 set x [expr {$canvx0 + $i * $xspc1($ln)}]
900 if {$i > 0 && $i == $level} {
901 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
902 } elseif {$i > $level} {
903 set x [expr {$x + $xspc2 - $xspc1($ln)}]
908 proc drawslants {level} {
909 global canv mainline sidelines canvx0 canvy xspc1 xspc2 lthickness
910 global oldlevel oldtodo todo currentparents dupparents
911 global lthickness linespc canvy colormap lineno geometry
914 # decide on the line spacing for the next line
915 set lj [expr {$lineno + 1}]
916 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
917 set n [llength $todo]
918 if {$n <= 1 || $canvx0 + $n * $xspc2 <= $maxw} {
919 set xspc1($lj) $xspc2
921 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($n - 1)}]
922 if {$xspc1($lj) < $lthickness} {
923 set xspc1($lj) $lthickness
927 set y1 [expr $canvy - $linespc]
930 foreach id $oldtodo {
932 if {$id == {}} continue
933 set xi [xcoord $i $oldlevel $lineno]
934 if {$i == $oldlevel} {
935 foreach p $currentparents {
936 set j [lsearch -exact $todo $p]
937 set coords [list $xi $y1]
938 set xj [xcoord $j $level $lj]
939 if {$xj < $xi - $linespc} {
940 lappend coords [expr {$xj + $linespc}] $y1
941 notecrossings $p $j $i [expr {$j + 1}]
942 } elseif {$xj > $xi + $linespc} {
943 lappend coords [expr {$xj - $linespc}] $y1
944 notecrossings $p $i $j [expr {$j - 1}]
946 if {[lsearch -exact $dupparents $p] >= 0} {
947 # draw a double-width line to indicate the doubled parent
948 lappend coords $xj $y2
949 lappend sidelines($p) [list $coords 2]
950 if {![info exists mainline($p)]} {
951 set mainline($p) [list $xj $y2]
954 # normal case, no parent duplicated
956 set dx [expr {abs($xi - $xj)}]
957 if {0 && $dx < $linespc} {
958 set yb [expr {$y1 + $dx}]
960 if {![info exists mainline($p)]} {
962 lappend coords $xj $yb
964 set mainline($p) $coords
966 lappend coords $xj $yb
968 lappend coords $xj $y2
970 lappend sidelines($p) [list $coords 1]
976 if {[lindex $todo $i] != $id} {
977 set j [lsearch -exact $todo $id]
979 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
980 || ($oldlevel <= $i && $i <= $level)
981 || ($level <= $i && $i <= $oldlevel)} {
982 set xj [xcoord $j $level $lj]
983 set dx [expr {abs($xi - $xj)}]
985 if {0 && $dx < $linespc} {
986 set yb [expr {$y1 + $dx}]
988 lappend mainline($id) $xi $y1 $xj $yb
994 proc decidenext {{noread 0}} {
995 global parents children nchildren ncleft todo
996 global canv canv2 canv3 mainfont namefont canvy linespc
997 global datemode cdate
999 global currentparents oldlevel oldnlines oldtodo
1000 global lineno lthickness
1002 # remove the null entry if present
1003 set nullentry [lsearch -exact $todo {}]
1004 if {$nullentry >= 0} {
1005 set todo [lreplace $todo $nullentry $nullentry]
1008 # choose which one to do next time around
1009 set todol [llength $todo]
1012 for {set k $todol} {[incr k -1] >= 0} {} {
1013 set p [lindex $todo $k]
1014 if {$ncleft($p) == 0} {
1016 if {![info exists commitinfo($p)]} {
1022 if {$latest == {} || $cdate($p) > $latest} {
1024 set latest $cdate($p)
1034 puts "ERROR: none of the pending commits can be done yet:"
1036 puts " $p ($ncleft($p))"
1042 # If we are reducing, put in a null entry
1043 if {$todol < $oldnlines} {
1044 if {$nullentry >= 0} {
1047 && [lindex $oldtodo $i] == [lindex $todo $i]} {
1057 set todo [linsert $todo $i {}]
1066 proc drawcommit {id} {
1067 global phase todo nchildren datemode nextupdate
1070 if {$phase != "incrdraw"} {
1073 set startcommits $id
1076 updatetodo 0 $datemode
1078 if {$nchildren($id) == 0} {
1080 lappend startcommits $id
1082 set level [decidenext 1]
1083 if {$level == {} || $id != [lindex $todo $level]} {
1088 drawcommitline $level
1089 if {[updatetodo $level $datemode]} {
1090 set level [decidenext 1]
1091 if {$level == {}} break
1093 set id [lindex $todo $level]
1094 if {![info exists commitlisted($id)]} {
1097 if {[clock clicks -milliseconds] >= $nextupdate} {
1105 proc finishcommits {} {
1108 global canv mainfont ctext maincursor textcursor
1110 if {$phase != "incrdraw"} {
1112 $canv create text 3 3 -anchor nw -text "No commits selected" \
1113 -font $mainfont -tags textitems
1116 set level [decidenext]
1118 drawrest $level [llength $startcommits]
1120 . config -cursor $maincursor
1121 $ctext config -cursor $textcursor
1125 global nextupdate startmsecs startcommits todo
1127 if {$startcommits == {}} return
1128 set startmsecs [clock clicks -milliseconds]
1129 set nextupdate [expr $startmsecs + 100]
1131 set todo [lindex $startcommits 0]
1135 proc drawrest {level startix} {
1136 global phase stopped redisplaying selectedline
1137 global datemode currentparents todo
1139 global nextupdate startmsecs startcommits idline
1143 set startid [lindex $startcommits $startix]
1145 if {$startid != {}} {
1146 set startline $idline($startid)
1150 drawcommitline $level
1151 set hard [updatetodo $level $datemode]
1152 if {$numcommits == $startline} {
1153 lappend todo $startid
1156 set startid [lindex $startcommits $startix]
1158 if {$startid != {}} {
1159 set startline $idline($startid)
1163 set level [decidenext]
1164 if {$level < 0} break
1167 if {[clock clicks -milliseconds] >= $nextupdate} {
1174 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1175 #puts "overall $drawmsecs ms for $numcommits commits"
1176 if {$redisplaying} {
1177 if {$stopped == 0 && [info exists selectedline]} {
1178 selectline $selectedline
1180 if {$stopped == 1} {
1182 after idle drawgraph
1189 proc findmatches {f} {
1190 global findtype foundstring foundstrlen
1191 if {$findtype == "Regexp"} {
1192 set matches [regexp -indices -all -inline $foundstring $f]
1194 if {$findtype == "IgnCase"} {
1195 set str [string tolower $f]
1201 while {[set j [string first $foundstring $str $i]] >= 0} {
1202 lappend matches [list $j [expr $j+$foundstrlen-1]]
1203 set i [expr $j + $foundstrlen]
1210 global findtype findloc findstring markedmatches commitinfo
1211 global numcommits lineid linehtag linentag linedtag
1212 global mainfont namefont canv canv2 canv3 selectedline
1213 global matchinglines foundstring foundstrlen
1218 set matchinglines {}
1219 if {$findloc == "Pickaxe"} {
1223 if {$findtype == "IgnCase"} {
1224 set foundstring [string tolower $findstring]
1226 set foundstring $findstring
1228 set foundstrlen [string length $findstring]
1229 if {$foundstrlen == 0} return
1230 if {$findloc == "Files"} {
1234 if {![info exists selectedline]} {
1237 set oldsel $selectedline
1240 set fldtypes {Headline Author Date Committer CDate Comment}
1241 for {set l 0} {$l < $numcommits} {incr l} {
1243 set info $commitinfo($id)
1245 foreach f $info ty $fldtypes {
1246 if {$findloc != "All fields" && $findloc != $ty} {
1249 set matches [findmatches $f]
1250 if {$matches == {}} continue
1252 if {$ty == "Headline"} {
1253 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1254 } elseif {$ty == "Author"} {
1255 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1256 } elseif {$ty == "Date"} {
1257 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1261 lappend matchinglines $l
1262 if {!$didsel && $l > $oldsel} {
1268 if {$matchinglines == {}} {
1270 } elseif {!$didsel} {
1271 findselectline [lindex $matchinglines 0]
1275 proc findselectline {l} {
1276 global findloc commentend ctext
1278 if {$findloc == "All fields" || $findloc == "Comments"} {
1279 # highlight the matches in the comments
1280 set f [$ctext get 1.0 $commentend]
1281 set matches [findmatches $f]
1282 foreach match $matches {
1283 set start [lindex $match 0]
1284 set end [expr [lindex $match 1] + 1]
1285 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1290 proc findnext {restart} {
1291 global matchinglines selectedline
1292 if {![info exists matchinglines]} {
1298 if {![info exists selectedline]} return
1299 foreach l $matchinglines {
1300 if {$l > $selectedline} {
1309 global matchinglines selectedline
1310 if {![info exists matchinglines]} {
1314 if {![info exists selectedline]} return
1316 foreach l $matchinglines {
1317 if {$l >= $selectedline} break
1321 findselectline $prev
1327 proc findlocchange {name ix op} {
1328 global findloc findtype findtypemenu
1329 if {$findloc == "Pickaxe"} {
1335 $findtypemenu entryconf 1 -state $state
1336 $findtypemenu entryconf 2 -state $state
1339 proc stopfindproc {{done 0}} {
1340 global findprocpid findprocfile findids
1341 global ctext findoldcursor phase maincursor textcursor
1342 global findinprogress
1344 catch {unset findids}
1345 if {[info exists findprocpid]} {
1347 catch {exec kill $findprocpid}
1349 catch {close $findprocfile}
1352 if {[info exists findinprogress]} {
1353 unset findinprogress
1354 if {$phase != "incrdraw"} {
1355 . config -cursor $maincursor
1356 $ctext config -cursor $textcursor
1361 proc findpatches {} {
1362 global findstring selectedline numcommits
1363 global findprocpid findprocfile
1364 global finddidsel ctext lineid findinprogress
1365 global findinsertpos
1367 if {$numcommits == 0} return
1369 # make a list of all the ids to search, starting at the one
1370 # after the selected line (if any)
1371 if {[info exists selectedline]} {
1377 for {set i 0} {$i < $numcommits} {incr i} {
1378 if {[incr l] >= $numcommits} {
1381 append inputids $lineid($l) "\n"
1385 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1388 error_popup "Error starting search process: $err"
1392 set findinsertpos end
1394 set findprocpid [pid $f]
1395 fconfigure $f -blocking 0
1396 fileevent $f readable readfindproc
1398 . config -cursor watch
1399 $ctext config -cursor watch
1400 set findinprogress 1
1403 proc readfindproc {} {
1404 global findprocfile finddidsel
1405 global idline matchinglines findinsertpos
1407 set n [gets $findprocfile line]
1409 if {[eof $findprocfile]} {
1417 if {![regexp {^[0-9a-f]{40}} $line id]} {
1418 error_popup "Can't parse git-diff-tree output: $line"
1422 if {![info exists idline($id)]} {
1423 puts stderr "spurious id: $id"
1430 proc insertmatch {l id} {
1431 global matchinglines findinsertpos finddidsel
1433 if {$findinsertpos == "end"} {
1434 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1435 set matchinglines [linsert $matchinglines 0 $l]
1438 lappend matchinglines $l
1441 set matchinglines [linsert $matchinglines $findinsertpos $l]
1452 global selectedline numcommits lineid ctext
1453 global ffileline finddidsel parents nparents
1454 global findinprogress findstartline findinsertpos
1455 global treediffs fdiffids fdiffsneeded fdiffpos
1456 global findmergefiles
1458 if {$numcommits == 0} return
1460 if {[info exists selectedline]} {
1461 set l [expr {$selectedline + 1}]
1466 set findstartline $l
1471 if {$findmergefiles || $nparents($id) == 1} {
1472 foreach p $parents($id) {
1473 if {![info exists treediffs([list $id $p])]} {
1474 append diffsneeded "$id $p\n"
1475 lappend fdiffsneeded [list $id $p]
1479 if {[incr l] >= $numcommits} {
1482 if {$l == $findstartline} break
1485 # start off a git-diff-tree process if needed
1486 if {$diffsneeded ne {}} {
1488 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1490 error_popup "Error starting search process: $err"
1493 catch {unset fdiffids}
1495 fconfigure $df -blocking 0
1496 fileevent $df readable [list readfilediffs $df]
1500 set findinsertpos end
1502 set p [lindex $parents($id) 0]
1503 . config -cursor watch
1504 $ctext config -cursor watch
1505 set findinprogress 1
1506 findcont [list $id $p]
1510 proc readfilediffs {df} {
1511 global findids fdiffids fdiffs
1513 set n [gets $df line]
1517 if {[catch {close $df} err]} {
1520 error_popup "Error in git-diff-tree: $err"
1521 } elseif {[info exists findids]} {
1525 error_popup "Couldn't find diffs for {$ids}"
1530 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1531 # start of a new string of diffs
1533 set fdiffids [list $id $p]
1535 } elseif {[string match ":*" $line]} {
1536 lappend fdiffs [lindex $line 5]
1540 proc donefilediff {} {
1541 global fdiffids fdiffs treediffs findids
1542 global fdiffsneeded fdiffpos
1544 if {[info exists fdiffids]} {
1545 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1546 && $fdiffpos < [llength $fdiffsneeded]} {
1547 # git-diff-tree doesn't output anything for a commit
1548 # which doesn't change anything
1549 set nullids [lindex $fdiffsneeded $fdiffpos]
1550 set treediffs($nullids) {}
1551 if {[info exists findids] && $nullids eq $findids} {
1559 if {![info exists treediffs($fdiffids)]} {
1560 set treediffs($fdiffids) $fdiffs
1562 if {[info exists findids] && $fdiffids eq $findids} {
1569 proc findcont {ids} {
1570 global findids treediffs parents nparents
1571 global ffileline findstartline finddidsel
1572 global lineid numcommits matchinglines findinprogress
1573 global findmergefiles
1575 set id [lindex $ids 0]
1576 set p [lindex $ids 1]
1577 set pi [lsearch -exact $parents($id) $p]
1580 if {$findmergefiles || $nparents($id) == 1} {
1581 if {![info exists treediffs($ids)]} {
1587 foreach f $treediffs($ids) {
1588 set x [findmatches $f]
1596 set pi $nparents($id)
1599 set pi $nparents($id)
1601 if {[incr pi] >= $nparents($id)} {
1603 if {[incr l] >= $numcommits} {
1606 if {$l == $findstartline} break
1609 set p [lindex $parents($id) $pi]
1610 set ids [list $id $p]
1618 # mark a commit as matching by putting a yellow background
1619 # behind the headline
1620 proc markheadline {l id} {
1621 global canv mainfont linehtag commitinfo
1623 set bbox [$canv bbox $linehtag($l)]
1624 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1628 # mark the bits of a headline, author or date that match a find string
1629 proc markmatches {canv l str tag matches font} {
1630 set bbox [$canv bbox $tag]
1631 set x0 [lindex $bbox 0]
1632 set y0 [lindex $bbox 1]
1633 set y1 [lindex $bbox 3]
1634 foreach match $matches {
1635 set start [lindex $match 0]
1636 set end [lindex $match 1]
1637 if {$start > $end} continue
1638 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1639 set xlen [font measure $font [string range $str 0 [expr $end]]]
1640 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1641 -outline {} -tags matches -fill yellow]
1646 proc unmarkmatches {} {
1647 global matchinglines findids
1648 allcanvs delete matches
1649 catch {unset matchinglines}
1650 catch {unset findids}
1653 proc selcanvline {w x y} {
1654 global canv canvy0 ctext linespc selectedline
1655 global lineid linehtag linentag linedtag rowtextx
1656 set ymax [lindex [$canv cget -scrollregion] 3]
1657 if {$ymax == {}} return
1658 set yfrac [lindex [$canv yview] 0]
1659 set y [expr {$y + $yfrac * $ymax}]
1660 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1665 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1671 proc selectline {l} {
1672 global canv canv2 canv3 ctext commitinfo selectedline
1673 global lineid linehtag linentag linedtag
1674 global canvy0 linespc parents nparents
1675 global cflist currentid sha1entry
1676 global commentend idtags
1678 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1680 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1681 -tags secsel -fill [$canv cget -selectbackground]]
1683 $canv2 delete secsel
1684 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1685 -tags secsel -fill [$canv2 cget -selectbackground]]
1687 $canv3 delete secsel
1688 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1689 -tags secsel -fill [$canv3 cget -selectbackground]]
1691 set y [expr {$canvy0 + $l * $linespc}]
1692 set ymax [lindex [$canv cget -scrollregion] 3]
1693 set ytop [expr {$y - $linespc - 1}]
1694 set ybot [expr {$y + $linespc + 1}]
1695 set wnow [$canv yview]
1696 set wtop [expr [lindex $wnow 0] * $ymax]
1697 set wbot [expr [lindex $wnow 1] * $ymax]
1698 set wh [expr {$wbot - $wtop}]
1700 if {$ytop < $wtop} {
1701 if {$ybot < $wtop} {
1702 set newtop [expr {$y - $wh / 2.0}]
1705 if {$newtop > $wtop - $linespc} {
1706 set newtop [expr {$wtop - $linespc}]
1709 } elseif {$ybot > $wbot} {
1710 if {$ytop > $wbot} {
1711 set newtop [expr {$y - $wh / 2.0}]
1713 set newtop [expr {$ybot - $wh}]
1714 if {$newtop < $wtop + $linespc} {
1715 set newtop [expr {$wtop + $linespc}]
1719 if {$newtop != $wtop} {
1723 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1729 $sha1entry delete 0 end
1730 $sha1entry insert 0 $id
1731 $sha1entry selection from 0
1732 $sha1entry selection to end
1734 $ctext conf -state normal
1735 $ctext delete 0.0 end
1736 $ctext mark set fmark.0 0.0
1737 $ctext mark gravity fmark.0 left
1738 set info $commitinfo($id)
1739 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1740 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1741 if {[info exists idtags($id)]} {
1742 $ctext insert end "Tags:"
1743 foreach tag $idtags($id) {
1744 $ctext insert end " $tag"
1746 $ctext insert end "\n"
1748 $ctext insert end "\n"
1749 $ctext insert end [lindex $info 5]
1750 $ctext insert end "\n"
1751 $ctext tag delete Comments
1752 $ctext tag remove found 1.0 end
1753 $ctext conf -state disabled
1754 set commentend [$ctext index "end - 1c"]
1756 $cflist delete 0 end
1757 $cflist insert end "Comments"
1758 if {$nparents($id) == 1} {
1759 startdiff [concat $id $parents($id)]
1760 } elseif {$nparents($id) > 1} {
1765 proc selnextline {dir} {
1767 if {![info exists selectedline]} return
1768 set l [expr $selectedline + $dir]
1773 proc mergediff {id} {
1774 global parents diffmergeid diffmergegca mergefilelist diffpindex
1778 set diffmergegca [findgca $parents($id)]
1779 if {[info exists mergefilelist($id)]} {
1780 if {$mergefilelist($id) ne {}} {
1788 proc findgca {ids} {
1795 set gca [exec git-merge-base $gca $id]
1804 proc contmergediff {ids} {
1805 global diffmergeid diffpindex parents nparents diffmergegca
1806 global treediffs mergefilelist diffids treepending
1808 # diff the child against each of the parents, and diff
1809 # each of the parents against the GCA.
1811 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1812 set ids [list [lindex $ids 1] $diffmergegca]
1814 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1815 set p [lindex $parents($diffmergeid) $diffpindex]
1816 set ids [list $diffmergeid $p]
1818 if {![info exists treediffs($ids)]} {
1820 if {![info exists treepending]} {
1827 # If a file in some parent is different from the child and also
1828 # different from the GCA, then it's interesting.
1829 # If we don't have a GCA, then a file is interesting if it is
1830 # different from the child in all the parents.
1831 if {$diffmergegca ne {}} {
1833 foreach p $parents($diffmergeid) {
1834 set gcadiffs $treediffs([list $p $diffmergegca])
1835 foreach f $treediffs([list $diffmergeid $p]) {
1836 if {[lsearch -exact $files $f] < 0
1837 && [lsearch -exact $gcadiffs $f] >= 0} {
1842 set files [lsort $files]
1844 set p [lindex $parents($diffmergeid) 0]
1845 set files $treediffs([list $diffmergeid $p])
1846 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1847 set p [lindex $parents($diffmergeid) $i]
1848 set df $treediffs([list $diffmergeid $p])
1851 if {[lsearch -exact $df $f] >= 0} {
1859 set mergefilelist($diffmergeid) $files
1865 proc showmergediff {} {
1866 global cflist diffmergeid mergefilelist parents
1867 global diffopts diffinhunk currentfile currenthunk filelines
1868 global diffblocked groupfilelast mergefds groupfilenum grouphunks
1870 set files $mergefilelist($diffmergeid)
1872 $cflist insert end $f
1874 set env(GIT_DIFF_OPTS) $diffopts
1876 catch {unset currentfile}
1877 catch {unset currenthunk}
1878 catch {unset filelines}
1879 catch {unset groupfilenum}
1880 catch {unset grouphunks}
1881 set groupfilelast -1
1882 foreach p $parents($diffmergeid) {
1883 set cmd [list | git-diff-tree -p $p $diffmergeid]
1884 set cmd [concat $cmd $mergefilelist($diffmergeid)]
1885 if {[catch {set f [open $cmd r]} err]} {
1886 error_popup "Error getting diffs: $err"
1893 set ids [list $diffmergeid $p]
1894 set mergefds($ids) $f
1895 set diffinhunk($ids) 0
1896 set diffblocked($ids) 0
1897 fconfigure $f -blocking 0
1898 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
1902 proc getmergediffline {f ids id} {
1903 global diffmergeid diffinhunk diffoldlines diffnewlines
1904 global currentfile currenthunk
1905 global diffoldstart diffnewstart diffoldlno diffnewlno
1906 global diffblocked mergefilelist
1907 global noldlines nnewlines difflcounts filelines
1909 set n [gets $f line]
1911 if {![eof $f]} return
1914 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
1921 if {$diffinhunk($ids) != 0} {
1922 set fi $currentfile($ids)
1923 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
1924 # continuing an existing hunk
1925 set line [string range $line 1 end]
1926 set p [lindex $ids 1]
1927 if {$match eq "-" || $match eq " "} {
1928 set filelines($p,$fi,$diffoldlno($ids)) $line
1929 incr diffoldlno($ids)
1931 if {$match eq "+" || $match eq " "} {
1932 set filelines($id,$fi,$diffnewlno($ids)) $line
1933 incr diffnewlno($ids)
1935 if {$match eq " "} {
1936 if {$diffinhunk($ids) == 2} {
1937 lappend difflcounts($ids) \
1938 [list $noldlines($ids) $nnewlines($ids)]
1939 set noldlines($ids) 0
1940 set diffinhunk($ids) 1
1942 incr noldlines($ids)
1943 } elseif {$match eq "-" || $match eq "+"} {
1944 if {$diffinhunk($ids) == 1} {
1945 lappend difflcounts($ids) [list $noldlines($ids)]
1946 set noldlines($ids) 0
1947 set nnewlines($ids) 0
1948 set diffinhunk($ids) 2
1950 if {$match eq "-"} {
1951 incr noldlines($ids)
1953 incr nnewlines($ids)
1956 # and if it's \ No newline at end of line, then what?
1960 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
1961 lappend difflcounts($ids) [list $noldlines($ids)]
1962 } elseif {$diffinhunk($ids) == 2
1963 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
1964 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
1966 set currenthunk($ids) [list $currentfile($ids) \
1967 $diffoldstart($ids) $diffnewstart($ids) \
1968 $diffoldlno($ids) $diffnewlno($ids) \
1970 set diffinhunk($ids) 0
1971 # -1 = need to block, 0 = unblocked, 1 = is blocked
1972 set diffblocked($ids) -1
1974 if {$diffblocked($ids) == -1} {
1975 fileevent $f readable {}
1976 set diffblocked($ids) 1
1982 if {!$diffblocked($ids)} {
1984 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
1985 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
1988 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
1989 # start of a new file
1990 set currentfile($ids) \
1991 [lsearch -exact $mergefilelist($diffmergeid) $fname]
1992 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1993 $line match f1l f1c f2l f2c rest]} {
1994 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
1995 # start of a new hunk
1996 if {$f1l == 0 && $f1c == 0} {
1999 if {$f2l == 0 && $f2c == 0} {
2002 set diffinhunk($ids) 1
2003 set diffoldstart($ids) $f1l
2004 set diffnewstart($ids) $f2l
2005 set diffoldlno($ids) $f1l
2006 set diffnewlno($ids) $f2l
2007 set difflcounts($ids) {}
2008 set noldlines($ids) 0
2009 set nnewlines($ids) 0
2014 proc processhunks {} {
2015 global diffmergeid parents nparents currenthunk
2016 global mergefilelist diffblocked mergefds
2017 global grouphunks grouplinestart grouplineend groupfilenum
2019 set nfiles [llength $mergefilelist($diffmergeid)]
2023 # look for the earliest hunk
2024 foreach p $parents($diffmergeid) {
2025 set ids [list $diffmergeid $p]
2026 if {![info exists currenthunk($ids)]} return
2027 set i [lindex $currenthunk($ids) 0]
2028 set l [lindex $currenthunk($ids) 2]
2029 if {$i < $fi || ($i == $fi && $l < $lno)} {
2036 if {$fi < $nfiles} {
2037 set ids [list $diffmergeid $pi]
2038 set hunk $currenthunk($ids)
2039 unset currenthunk($ids)
2040 if {$diffblocked($ids) > 0} {
2041 fileevent $mergefds($ids) readable \
2042 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2044 set diffblocked($ids) 0
2046 if {[info exists groupfilenum] && $groupfilenum == $fi
2047 && $lno <= $grouplineend} {
2048 # add this hunk to the pending group
2049 lappend grouphunks($pi) $hunk
2050 set endln [lindex $hunk 4]
2051 if {$endln > $grouplineend} {
2052 set grouplineend $endln
2058 # succeeding stuff doesn't belong in this group, so
2059 # process the group now
2060 if {[info exists groupfilenum]} {
2066 if {$fi >= $nfiles} break
2069 set groupfilenum $fi
2070 set grouphunks($pi) [list $hunk]
2071 set grouplinestart $lno
2072 set grouplineend [lindex $hunk 4]
2076 proc processgroup {} {
2077 global groupfilelast groupfilenum difffilestart
2078 global mergefilelist diffmergeid ctext filelines
2079 global parents diffmergeid diffoffset
2080 global grouphunks grouplinestart grouplineend nparents
2083 $ctext conf -state normal
2086 if {$groupfilelast != $f} {
2087 $ctext insert end "\n"
2088 set here [$ctext index "end - 1c"]
2089 set difffilestart($f) $here
2090 set mark fmark.[expr {$f + 1}]
2091 $ctext mark set $mark $here
2092 $ctext mark gravity $mark left
2093 set header [lindex $mergefilelist($id) $f]
2094 set l [expr {(78 - [string length $header]) / 2}]
2095 set pad [string range "----------------------------------------" 1 $l]
2096 $ctext insert end "$pad $header $pad\n" filesep
2097 set groupfilelast $f
2098 foreach p $parents($id) {
2099 set diffoffset($p) 0
2103 $ctext insert end "@@" msep
2104 set nlines [expr {$grouplineend - $grouplinestart}]
2107 foreach p $parents($id) {
2108 set startline [expr {$grouplinestart + $diffoffset($p)}]
2110 set nl $grouplinestart
2111 if {[info exists grouphunks($p)]} {
2112 foreach h $grouphunks($p) {
2115 for {} {$nl < $l} {incr nl} {
2116 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2120 foreach chunk [lindex $h 5] {
2121 if {[llength $chunk] == 2} {
2122 set olc [lindex $chunk 0]
2123 set nlc [lindex $chunk 1]
2124 set nnl [expr {$nl + $nlc}]
2125 lappend events [list $nl $nnl $pnum $olc $nlc]
2129 incr ol [lindex $chunk 0]
2130 incr nl [lindex $chunk 0]
2135 if {$nl < $grouplineend} {
2136 for {} {$nl < $grouplineend} {incr nl} {
2137 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2141 set nlines [expr {$ol - $startline}]
2142 $ctext insert end " -$startline,$nlines" msep
2146 set nlines [expr {$grouplineend - $grouplinestart}]
2147 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2149 set events [lsort -integer -index 0 $events]
2150 set nevents [llength $events]
2151 set nmerge $nparents($diffmergeid)
2152 set l $grouplinestart
2153 for {set i 0} {$i < $nevents} {set i $j} {
2154 set nl [lindex $events $i 0]
2156 $ctext insert end " $filelines($id,$f,$l)\n"
2159 set e [lindex $events $i]
2160 set enl [lindex $e 1]
2164 set pnum [lindex $e 2]
2165 set olc [lindex $e 3]
2166 set nlc [lindex $e 4]
2167 if {![info exists delta($pnum)]} {
2168 set delta($pnum) [expr {$olc - $nlc}]
2169 lappend active $pnum
2171 incr delta($pnum) [expr {$olc - $nlc}]
2173 if {[incr j] >= $nevents} break
2174 set e [lindex $events $j]
2175 if {[lindex $e 0] >= $enl} break
2176 if {[lindex $e 1] > $enl} {
2177 set enl [lindex $e 1]
2180 set nlc [expr {$enl - $l}]
2183 if {[llength $active] == $nmerge - 1} {
2184 # no diff for one of the parents, i.e. it's identical
2185 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2186 if {![info exists delta($pnum)]} {
2187 if {$pnum < $mergemax} {
2195 } elseif {[llength $active] == $nmerge} {
2196 # all parents are different, see if one is very similar
2198 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2199 set sim [similarity $pnum $l $nlc $f \
2200 [lrange $events $i [expr {$j-1}]]]
2201 if {$sim > $bestsim} {
2207 lappend ncol m$bestpn
2211 foreach p $parents($id) {
2213 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2214 set olc [expr {$nlc + $delta($pnum)}]
2215 set ol [expr {$l + $diffoffset($p)}]
2216 incr diffoffset($p) $delta($pnum)
2218 for {} {$olc > 0} {incr olc -1} {
2219 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2223 set endl [expr {$l + $nlc}]
2225 # show this pretty much as a normal diff
2226 set p [lindex $parents($id) $bestpn]
2227 set ol [expr {$l + $diffoffset($p)}]
2228 incr diffoffset($p) $delta($bestpn)
2229 unset delta($bestpn)
2230 for {set k $i} {$k < $j} {incr k} {
2231 set e [lindex $events $k]
2232 if {[lindex $e 2] != $bestpn} continue
2233 set nl [lindex $e 0]
2234 set ol [expr {$ol + $nl - $l}]
2235 for {} {$l < $nl} {incr l} {
2236 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2239 for {} {$c > 0} {incr c -1} {
2240 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2243 set nl [lindex $e 1]
2244 for {} {$l < $nl} {incr l} {
2245 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2249 for {} {$l < $endl} {incr l} {
2250 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2253 while {$l < $grouplineend} {
2254 $ctext insert end " $filelines($id,$f,$l)\n"
2257 $ctext conf -state disabled
2260 proc similarity {pnum l nlc f events} {
2261 global diffmergeid parents diffoffset filelines
2264 set p [lindex $parents($id) $pnum]
2265 set ol [expr {$l + $diffoffset($p)}]
2266 set endl [expr {$l + $nlc}]
2270 if {[lindex $e 2] != $pnum} continue
2271 set nl [lindex $e 0]
2272 set ol [expr {$ol + $nl - $l}]
2273 for {} {$l < $nl} {incr l} {
2274 incr same [string length $filelines($id,$f,$l)]
2277 set oc [lindex $e 3]
2278 for {} {$oc > 0} {incr oc -1} {
2279 incr diff [string length $filelines($p,$f,$ol)]
2283 set nl [lindex $e 1]
2284 for {} {$l < $nl} {incr l} {
2285 incr diff [string length $filelines($id,$f,$l)]
2289 for {} {$l < $endl} {incr l} {
2290 incr same [string length $filelines($id,$f,$l)]
2296 return [expr {200 * $same / (2 * $same + $diff)}]
2299 proc startdiff {ids} {
2300 global treediffs diffids treepending diffmergeid
2303 catch {unset diffmergeid}
2304 if {![info exists treediffs($ids)]} {
2305 if {![info exists treepending]} {
2313 proc addtocflist {ids} {
2314 global treediffs cflist
2315 foreach f $treediffs($ids) {
2316 $cflist insert end $f
2321 proc gettreediffs {ids} {
2322 global treediff parents treepending
2323 set treepending $ids
2325 set id [lindex $ids 0]
2326 set p [lindex $ids 1]
2327 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2328 fconfigure $gdtf -blocking 0
2329 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2332 proc gettreediffline {gdtf ids} {
2333 global treediff treediffs treepending diffids diffmergeid
2335 set n [gets $gdtf line]
2337 if {![eof $gdtf]} return
2339 set treediffs($ids) $treediff
2341 if {$ids != $diffids} {
2342 gettreediffs $diffids
2344 if {[info exists diffmergeid]} {
2352 set file [lindex $line 5]
2353 lappend treediff $file
2356 proc getblobdiffs {ids} {
2357 global diffopts blobdifffd diffids env curdifftag curtagstart
2358 global difffilestart nextupdate diffinhdr treediffs
2360 set id [lindex $ids 0]
2361 set p [lindex $ids 1]
2362 set env(GIT_DIFF_OPTS) $diffopts
2363 set cmd [list | git-diff-tree -r -p -C $p $id]
2364 if {[catch {set bdf [open $cmd r]} err]} {
2365 puts "error getting diffs: $err"
2369 fconfigure $bdf -blocking 0
2370 set blobdifffd($ids) $bdf
2371 set curdifftag Comments
2373 catch {unset difffilestart}
2374 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2375 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2378 proc getblobdiffline {bdf ids} {
2379 global diffids blobdifffd ctext curdifftag curtagstart
2380 global diffnexthead diffnextnote difffilestart
2381 global nextupdate diffinhdr treediffs
2384 set n [gets $bdf line]
2388 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2389 $ctext tag add $curdifftag $curtagstart end
2394 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2397 $ctext conf -state normal
2398 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2399 # start of a new file
2400 $ctext insert end "\n"
2401 $ctext tag add $curdifftag $curtagstart end
2402 set curtagstart [$ctext index "end - 1c"]
2404 set here [$ctext index "end - 1c"]
2405 set i [lsearch -exact $treediffs($diffids) $fname]
2407 set difffilestart($i) $here
2409 $ctext mark set fmark.$i $here
2410 $ctext mark gravity fmark.$i left
2412 if {$newname != $fname} {
2413 set i [lsearch -exact $treediffs($diffids) $newname]
2415 set difffilestart($i) $here
2417 $ctext mark set fmark.$i $here
2418 $ctext mark gravity fmark.$i left
2421 set curdifftag "f:$fname"
2422 $ctext tag delete $curdifftag
2423 set l [expr {(78 - [string length $header]) / 2}]
2424 set pad [string range "----------------------------------------" 1 $l]
2425 $ctext insert end "$pad $header $pad\n" filesep
2427 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2429 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2430 $line match f1l f1c f2l f2c rest]} {
2432 $ctext insert end "\t" hunksep
2433 $ctext insert end " $f1l " d0 " $f2l " d1
2434 $ctext insert end " $rest \n" hunksep
2436 $ctext insert end "$line\n" hunksep
2440 set x [string range $line 0 0]
2441 if {$x == "-" || $x == "+"} {
2442 set tag [expr {$x == "+"}]
2444 set line [string range $line 1 end]
2446 $ctext insert end "$line\n" d$tag
2447 } elseif {$x == " "} {
2449 set line [string range $line 1 end]
2451 $ctext insert end "$line\n"
2452 } elseif {$diffinhdr || $x == "\\"} {
2453 # e.g. "\ No newline at end of file"
2454 $ctext insert end "$line\n" filesep
2456 # Something else we don't recognize
2457 if {$curdifftag != "Comments"} {
2458 $ctext insert end "\n"
2459 $ctext tag add $curdifftag $curtagstart end
2460 set curtagstart [$ctext index "end - 1c"]
2461 set curdifftag Comments
2463 $ctext insert end "$line\n" filesep
2466 $ctext conf -state disabled
2467 if {[clock clicks -milliseconds] >= $nextupdate} {
2469 fileevent $bdf readable {}
2471 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2476 global difffilestart ctext
2477 set here [$ctext index @0,0]
2478 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2479 if {[$ctext compare $difffilestart($i) > $here]} {
2480 if {![info exists pos]
2481 || [$ctext compare $difffilestart($i) < $pos]} {
2482 set pos $difffilestart($i)
2486 if {[info exists pos]} {
2491 proc listboxsel {} {
2492 global ctext cflist currentid
2493 if {![info exists currentid]} return
2494 set sel [lsort [$cflist curselection]]
2495 if {$sel eq {}} return
2496 set first [lindex $sel 0]
2497 catch {$ctext yview fmark.$first}
2501 global linespc charspc canvx0 canvy0 mainfont
2504 set linespc [font metrics $mainfont -linespace]
2505 set charspc [font measure $mainfont "m"]
2506 set canvy0 [expr 3 + 0.5 * $linespc]
2507 set canvx0 [expr 3 + 0.5 * $linespc]
2508 set xspc1(0) $linespc
2513 global selectedline stopped redisplaying phase
2514 if {$stopped > 1} return
2515 if {$phase == "getcommits"} return
2517 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2524 proc incrfont {inc} {
2525 global mainfont namefont textfont selectedline ctext canv phase
2526 global stopped entries
2528 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2529 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2530 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2532 $ctext conf -font $textfont
2533 $ctext tag conf filesep -font [concat $textfont bold]
2534 foreach e $entries {
2535 $e conf -font $mainfont
2537 if {$phase == "getcommits"} {
2538 $canv itemconf textitems -font $mainfont
2544 global sha1entry sha1string
2545 if {[string length $sha1string] == 40} {
2546 $sha1entry delete 0 end
2550 proc sha1change {n1 n2 op} {
2551 global sha1string currentid sha1but
2552 if {$sha1string == {}
2553 || ([info exists currentid] && $sha1string == $currentid)} {
2558 if {[$sha1but cget -state] == $state} return
2559 if {$state == "normal"} {
2560 $sha1but conf -state normal -relief raised -text "Goto: "
2562 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2566 proc gotocommit {} {
2567 global sha1string currentid idline tagids
2568 global lineid numcommits
2570 if {$sha1string == {}
2571 || ([info exists currentid] && $sha1string == $currentid)} return
2572 if {[info exists tagids($sha1string)]} {
2573 set id $tagids($sha1string)
2575 set id [string tolower $sha1string]
2576 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2578 for {set l 0} {$l < $numcommits} {incr l} {
2579 if {[string match $id* $lineid($l)]} {
2580 lappend matches $lineid($l)
2583 if {$matches ne {}} {
2584 if {[llength $matches] > 1} {
2585 error_popup "Short SHA1 id $id is ambiguous"
2588 set id [lindex $matches 0]
2592 if {[info exists idline($id)]} {
2593 selectline $idline($id)
2596 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2601 error_popup "$type $sha1string is not known"
2604 proc lineenter {x y id} {
2605 global hoverx hovery hoverid hovertimer
2606 global commitinfo canv
2608 if {![info exists commitinfo($id)]} return
2612 if {[info exists hovertimer]} {
2613 after cancel $hovertimer
2615 set hovertimer [after 500 linehover]
2619 proc linemotion {x y id} {
2620 global hoverx hovery hoverid hovertimer
2622 if {[info exists hoverid] && $id == $hoverid} {
2625 if {[info exists hovertimer]} {
2626 after cancel $hovertimer
2628 set hovertimer [after 500 linehover]
2632 proc lineleave {id} {
2633 global hoverid hovertimer canv
2635 if {[info exists hoverid] && $id == $hoverid} {
2637 if {[info exists hovertimer]} {
2638 after cancel $hovertimer
2646 global hoverx hovery hoverid hovertimer
2647 global canv linespc lthickness
2648 global commitinfo mainfont
2650 set text [lindex $commitinfo($hoverid) 0]
2651 set ymax [lindex [$canv cget -scrollregion] 3]
2652 if {$ymax == {}} return
2653 set yfrac [lindex [$canv yview] 0]
2654 set x [expr {$hoverx + 2 * $linespc}]
2655 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2656 set x0 [expr {$x - 2 * $lthickness}]
2657 set y0 [expr {$y - 2 * $lthickness}]
2658 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2659 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2660 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2661 -fill \#ffff80 -outline black -width 1 -tags hover]
2663 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2667 proc lineclick {x y id} {
2668 global ctext commitinfo children cflist canv
2672 # fill the details pane with info about this line
2673 $ctext conf -state normal
2674 $ctext delete 0.0 end
2675 $ctext insert end "Parent:\n "
2676 catch {destroy $ctext.$id}
2677 button $ctext.$id -text "Go:" -command "selbyid $id" \
2679 $ctext window create end -window $ctext.$id -align center
2680 set info $commitinfo($id)
2681 $ctext insert end "\t[lindex $info 0]\n"
2682 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2683 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2684 $ctext insert end "\tID:\t$id\n"
2685 if {[info exists children($id)]} {
2686 $ctext insert end "\nChildren:"
2687 foreach child $children($id) {
2688 $ctext insert end "\n "
2689 catch {destroy $ctext.$child}
2690 button $ctext.$child -text "Go:" -command "selbyid $child" \
2692 $ctext window create end -window $ctext.$child -align center
2693 set info $commitinfo($child)
2694 $ctext insert end "\t[lindex $info 0]"
2697 $ctext conf -state disabled
2699 $cflist delete 0 end
2704 if {[info exists idline($id)]} {
2705 selectline $idline($id)
2711 if {![info exists startmstime]} {
2712 set startmstime [clock clicks -milliseconds]
2714 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2717 proc rowmenu {x y id} {
2718 global rowctxmenu idline selectedline rowmenuid
2720 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2725 $rowctxmenu entryconfigure 0 -state $state
2726 $rowctxmenu entryconfigure 1 -state $state
2727 $rowctxmenu entryconfigure 2 -state $state
2729 tk_popup $rowctxmenu $x $y
2732 proc diffvssel {dirn} {
2733 global rowmenuid selectedline lineid
2737 if {![info exists selectedline]} return
2739 set oldid $lineid($selectedline)
2740 set newid $rowmenuid
2742 set oldid $rowmenuid
2743 set newid $lineid($selectedline)
2745 $ctext conf -state normal
2746 $ctext delete 0.0 end
2747 $ctext mark set fmark.0 0.0
2748 $ctext mark gravity fmark.0 left
2749 $cflist delete 0 end
2750 $cflist insert end "Top"
2751 $ctext insert end "From $oldid\n "
2752 $ctext insert end [lindex $commitinfo($oldid) 0]
2753 $ctext insert end "\n\nTo $newid\n "
2754 $ctext insert end [lindex $commitinfo($newid) 0]
2755 $ctext insert end "\n"
2756 $ctext conf -state disabled
2757 $ctext tag delete Comments
2758 $ctext tag remove found 1.0 end
2759 startdiff [list $newid $oldid]
2763 global rowmenuid currentid commitinfo patchtop patchnum
2765 if {![info exists currentid]} return
2766 set oldid $currentid
2767 set oldhead [lindex $commitinfo($oldid) 0]
2768 set newid $rowmenuid
2769 set newhead [lindex $commitinfo($newid) 0]
2772 catch {destroy $top}
2774 label $top.title -text "Generate patch"
2775 grid $top.title - -pady 10
2776 label $top.from -text "From:"
2777 entry $top.fromsha1 -width 40 -relief flat
2778 $top.fromsha1 insert 0 $oldid
2779 $top.fromsha1 conf -state readonly
2780 grid $top.from $top.fromsha1 -sticky w
2781 entry $top.fromhead -width 60 -relief flat
2782 $top.fromhead insert 0 $oldhead
2783 $top.fromhead conf -state readonly
2784 grid x $top.fromhead -sticky w
2785 label $top.to -text "To:"
2786 entry $top.tosha1 -width 40 -relief flat
2787 $top.tosha1 insert 0 $newid
2788 $top.tosha1 conf -state readonly
2789 grid $top.to $top.tosha1 -sticky w
2790 entry $top.tohead -width 60 -relief flat
2791 $top.tohead insert 0 $newhead
2792 $top.tohead conf -state readonly
2793 grid x $top.tohead -sticky w
2794 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2795 grid $top.rev x -pady 10
2796 label $top.flab -text "Output file:"
2797 entry $top.fname -width 60
2798 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2800 grid $top.flab $top.fname -sticky w
2802 button $top.buts.gen -text "Generate" -command mkpatchgo
2803 button $top.buts.can -text "Cancel" -command mkpatchcan
2804 grid $top.buts.gen $top.buts.can
2805 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2806 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2807 grid $top.buts - -pady 10 -sticky ew
2811 proc mkpatchrev {} {
2814 set oldid [$patchtop.fromsha1 get]
2815 set oldhead [$patchtop.fromhead get]
2816 set newid [$patchtop.tosha1 get]
2817 set newhead [$patchtop.tohead get]
2818 foreach e [list fromsha1 fromhead tosha1 tohead] \
2819 v [list $newid $newhead $oldid $oldhead] {
2820 $patchtop.$e conf -state normal
2821 $patchtop.$e delete 0 end
2822 $patchtop.$e insert 0 $v
2823 $patchtop.$e conf -state readonly
2830 set oldid [$patchtop.fromsha1 get]
2831 set newid [$patchtop.tosha1 get]
2832 set fname [$patchtop.fname get]
2833 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2834 error_popup "Error creating patch: $err"
2836 catch {destroy $patchtop}
2840 proc mkpatchcan {} {
2843 catch {destroy $patchtop}
2848 global rowmenuid mktagtop commitinfo
2852 catch {destroy $top}
2854 label $top.title -text "Create tag"
2855 grid $top.title - -pady 10
2856 label $top.id -text "ID:"
2857 entry $top.sha1 -width 40 -relief flat
2858 $top.sha1 insert 0 $rowmenuid
2859 $top.sha1 conf -state readonly
2860 grid $top.id $top.sha1 -sticky w
2861 entry $top.head -width 60 -relief flat
2862 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2863 $top.head conf -state readonly
2864 grid x $top.head -sticky w
2865 label $top.tlab -text "Tag name:"
2866 entry $top.tag -width 60
2867 grid $top.tlab $top.tag -sticky w
2869 button $top.buts.gen -text "Create" -command mktaggo
2870 button $top.buts.can -text "Cancel" -command mktagcan
2871 grid $top.buts.gen $top.buts.can
2872 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2873 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2874 grid $top.buts - -pady 10 -sticky ew
2879 global mktagtop env tagids idtags
2880 global idpos idline linehtag canv selectedline
2882 set id [$mktagtop.sha1 get]
2883 set tag [$mktagtop.tag get]
2885 error_popup "No tag name specified"
2888 if {[info exists tagids($tag)]} {
2889 error_popup "Tag \"$tag\" already exists"
2894 set fname [file join $dir "refs/tags" $tag]
2895 set f [open $fname w]
2899 error_popup "Error creating tag: $err"
2903 set tagids($tag) $id
2904 lappend idtags($id) $tag
2905 $canv delete tag.$id
2906 set xt [eval drawtags $id $idpos($id)]
2907 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2908 if {[info exists selectedline] && $selectedline == $idline($id)} {
2909 selectline $selectedline
2916 catch {destroy $mktagtop}
2925 proc writecommit {} {
2926 global rowmenuid wrcomtop commitinfo wrcomcmd
2928 set top .writecommit
2930 catch {destroy $top}
2932 label $top.title -text "Write commit to file"
2933 grid $top.title - -pady 10
2934 label $top.id -text "ID:"
2935 entry $top.sha1 -width 40 -relief flat
2936 $top.sha1 insert 0 $rowmenuid
2937 $top.sha1 conf -state readonly
2938 grid $top.id $top.sha1 -sticky w
2939 entry $top.head -width 60 -relief flat
2940 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2941 $top.head conf -state readonly
2942 grid x $top.head -sticky w
2943 label $top.clab -text "Command:"
2944 entry $top.cmd -width 60 -textvariable wrcomcmd
2945 grid $top.clab $top.cmd -sticky w -pady 10
2946 label $top.flab -text "Output file:"
2947 entry $top.fname -width 60
2948 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2949 grid $top.flab $top.fname -sticky w
2951 button $top.buts.gen -text "Write" -command wrcomgo
2952 button $top.buts.can -text "Cancel" -command wrcomcan
2953 grid $top.buts.gen $top.buts.can
2954 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2955 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2956 grid $top.buts - -pady 10 -sticky ew
2963 set id [$wrcomtop.sha1 get]
2964 set cmd "echo $id | [$wrcomtop.cmd get]"
2965 set fname [$wrcomtop.fname get]
2966 if {[catch {exec sh -c $cmd >$fname &} err]} {
2967 error_popup "Error writing commit: $err"
2969 catch {destroy $wrcomtop}
2976 catch {destroy $wrcomtop}
2989 set diffopts "-U 5 -p"
2990 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2992 set mainfont {Helvetica 9}
2993 set textfont {Courier 9}
2994 set findmergefiles 0
2998 set colors {green red blue magenta darkgrey brown orange}
3000 catch {source ~/.gitk}
3002 set namefont $mainfont
3004 lappend namefont bold
3009 switch -regexp -- $arg {
3011 "^-b" { set boldnames 1 }
3012 "^-d" { set datemode 1 }
3014 lappend revtreeargs $arg
3026 getcommits $revtreeargs