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.
10 proc getcommits
{rargs
} {
11 global commits commfd phase canv mainfont env
12 global startmsecs nextupdate
13 global ctext maincursor textcursor leftover
15 # check that we can find a .git directory somewhere...
16 if {[info exists env
(GIT_DIR
)]} {
17 set gitdir
$env(GIT_DIR
)
21 if {![file isdirectory
$gitdir]} {
22 error_popup
"Cannot find the git directory \"$gitdir\"."
27 set startmsecs
[clock clicks
-milliseconds]
28 set nextupdate
[expr $startmsecs + 100]
30 set parse_args
[concat
--default HEAD
$rargs]
31 set parsed_args
[split [eval exec git-rev-parse
$parse_args] "\n"]
33 # if git-rev-parse failed for some reason...
37 set parsed_args
$rargs
40 set commfd
[open
"|git-rev-list --header --topo-order $parsed_args" r
]
42 puts stderr
"Error executing git-rev-list: $err"
46 fconfigure
$commfd -blocking 0 -translation binary
47 fileevent
$commfd readable
"getcommitlines $commfd"
49 $canv create text
3 3 -anchor nw
-text "Reading commits..." \
50 -font $mainfont -tags textitems
51 . config
-cursor watch
52 $ctext config
-cursor watch
55 proc getcommitlines
{commfd
} {
56 global commits parents cdate children nchildren
57 global commitlisted phase commitinfo nextupdate
58 global stopped redisplaying leftover
60 set stuff
[read $commfd]
62 if {![eof
$commfd]} return
63 # set it blocking so we wait for the process to terminate
64 fconfigure
$commfd -blocking 1
65 if {![catch
{close
$commfd} err
]} {
66 after idle finishcommits
69 if {[string range
$err 0 4] == "usage"} {
71 {Gitk
: error reading commits
: bad arguments to git-rev-list.
72 (Note
: arguments to gitk are passed to git-rev-list
73 to allow selection of commits to be displayed.
)}
75 set err
"Error reading commits: $err"
82 set i
[string first
"\0" $stuff $start]
84 append leftover
[string range
$stuff $start end
]
87 set cmit
[string range
$stuff $start [expr {$i - 1}]]
89 set cmit
"$leftover$cmit"
92 set start
[expr {$i + 1}]
93 if {![regexp
{^
([0-9a-f]{40})\n} $cmit match id
]} {
95 if {[string length
$shortcmit] > 80} {
96 set shortcmit
"[string range $shortcmit 0 80]..."
98 error_popup
"Can't parse git-rev-list output: {$shortcmit}"
101 set cmit
[string range
$cmit 41 end
]
103 set commitlisted
($id) 1
104 parsecommit
$id $cmit 1
106 if {[clock clicks
-milliseconds] >= $nextupdate} {
109 while {$redisplaying} {
113 set phase
"getcommits"
114 foreach id
$commits {
117 if {[clock clicks
-milliseconds] >= $nextupdate} {
127 global commfd nextupdate
130 fileevent
$commfd readable
{}
132 fileevent
$commfd readable
"getcommitlines $commfd"
135 proc readcommit
{id
} {
136 if [catch
{set contents
[exec git-cat-file commit
$id]}] return
137 parsecommit
$id $contents 0
140 proc parsecommit
{id contents listed
} {
141 global commitinfo children nchildren parents nparents cdate ncleft
150 if {![info exists nchildren
($id)]} {
157 foreach line
[split $contents "\n"] {
162 set tag
[lindex
$line 0]
163 if {$tag == "parent"} {
164 set p
[lindex
$line 1]
165 if {![info exists nchildren
($p)]} {
170 lappend parents
($id) $p
172 # sometimes we get a commit that lists a parent twice...
173 if {$listed && [lsearch
-exact $children($p) $id] < 0} {
174 lappend children
($p) $id
178 } elseif
{$tag == "author"} {
179 set x
[expr {[llength
$line] - 2}]
180 set audate
[lindex
$line $x]
181 set auname
[lrange
$line 1 [expr {$x - 1}]]
182 } elseif
{$tag == "committer"} {
183 set x
[expr {[llength
$line] - 2}]
184 set comdate
[lindex
$line $x]
185 set comname
[lrange
$line 1 [expr {$x - 1}]]
189 if {$comment == {}} {
190 set headline
[string trim
$line]
195 # git-rev-list indents the comment by 4 spaces;
196 # if we got this via git-cat-file, add the indentation
203 set audate
[clock format
$audate -format "%Y-%m-%d %H:%M:%S"]
205 if {$comdate != {}} {
206 set cdate
($id) $comdate
207 set comdate
[clock format
$comdate -format "%Y-%m-%d %H:%M:%S"]
209 set commitinfo
($id) [list
$headline $auname $audate \
210 $comname $comdate $comment]
214 global tagids idtags headids idheads
215 set tags
[glob
-nocomplain -types f .git
/refs
/tags
/*]
220 if {[regexp
{^
[0-9a-f]{40}} $line id
]} {
221 set direct
[file tail $f]
222 set tagids
($direct) $id
223 lappend idtags
($id) $direct
224 set contents
[split [exec git-cat-file tag
$id] "\n"]
228 foreach l
$contents {
230 switch
-- [lindex
$l 0] {
231 "object" {set obj
[lindex
$l 1]}
232 "type" {set type [lindex
$l 1]}
233 "tag" {set tag
[string range
$l 4 end
]}
236 if {$obj != {} && $type == "commit" && $tag != {}} {
237 set tagids
($tag) $obj
238 lappend idtags
($obj) $tag
244 set heads
[glob
-nocomplain -types f .git
/refs
/heads
/*]
248 set line
[read $fd 40]
249 if {[regexp
{^
[0-9a-f]{40}} $line id
]} {
250 set head [file tail $f]
251 set headids
($head) $line
252 lappend idheads
($line) $head
259 proc error_popup msg
{
263 message
$w.m
-text $msg -justify center
-aspect 400
264 pack
$w.m
-side top
-fill x
-padx 20 -pady 20
265 button
$w.ok
-text OK
-command "destroy $w"
266 pack
$w.ok
-side bottom
-fill x
267 bind $w <Visibility
> "grab $w; focus $w"
272 global canv canv2 canv3 linespc charspc ctext cflist textfont
273 global findtype findtypemenu findloc findstring fstring geometry
274 global entries sha1entry sha1string sha1but
275 global maincursor textcursor
276 global rowctxmenu gaudydiff
279 .bar add cascade
-label "File" -menu .bar.
file
281 .bar.
file add
command -label "Quit" -command doquit
283 .bar add cascade
-label "Help" -menu .bar.
help
284 .bar.
help add
command -label "About gitk" -command about
285 . configure
-menu .bar
287 if {![info exists geometry
(canv1
)]} {
288 set geometry
(canv1
) [expr 45 * $charspc]
289 set geometry
(canv2
) [expr 30 * $charspc]
290 set geometry
(canv3
) [expr 15 * $charspc]
291 set geometry
(canvh
) [expr 25 * $linespc + 4]
292 set geometry
(ctextw
) 80
293 set geometry
(ctexth
) 30
294 set geometry
(cflistw
) 30
296 panedwindow .ctop
-orient vertical
297 if {[info exists geometry
(width
)]} {
298 .ctop conf
-width $geometry(width
) -height $geometry(height
)
299 set texth
[expr {$geometry(height
) - $geometry(canvh
) - 56}]
300 set geometry
(ctexth
) [expr {($texth - 8) /
301 [font metrics
$textfont -linespace]}]
305 pack .ctop.top.bar
-side bottom
-fill x
306 set cscroll .ctop.top.csb
307 scrollbar
$cscroll -command {allcanvs yview
} -highlightthickness 0
308 pack
$cscroll -side right
-fill y
309 panedwindow .ctop.top.clist
-orient horizontal
-sashpad 0 -handlesize 4
310 pack .ctop.top.clist
-side top
-fill both
-expand 1
312 set canv .ctop.top.clist.canv
313 canvas
$canv -height $geometry(canvh
) -width $geometry(canv1
) \
315 -yscrollincr $linespc -yscrollcommand "$cscroll set"
316 .ctop.top.clist add
$canv
317 set canv2 .ctop.top.clist.canv2
318 canvas
$canv2 -height $geometry(canvh
) -width $geometry(canv2
) \
319 -bg white
-bd 0 -yscrollincr $linespc
320 .ctop.top.clist add
$canv2
321 set canv3 .ctop.top.clist.canv3
322 canvas
$canv3 -height $geometry(canvh
) -width $geometry(canv3
) \
323 -bg white
-bd 0 -yscrollincr $linespc
324 .ctop.top.clist add
$canv3
325 bind .ctop.top.clist
<Configure
> {resizeclistpanes
%W
%w
}
327 set sha1entry .ctop.top.bar.sha1
328 set entries
$sha1entry
329 set sha1but .ctop.top.bar.sha1label
330 button
$sha1but -text "SHA1 ID: " -state disabled
-relief flat \
331 -command gotocommit
-width 8
332 $sha1but conf
-disabledforeground [$sha1but cget
-foreground]
333 pack .ctop.top.bar.sha1label
-side left
334 entry
$sha1entry -width 40 -font $textfont -textvariable sha1string
335 trace add variable sha1string
write sha1change
336 pack
$sha1entry -side left
-pady 2
337 button .ctop.top.bar.findbut
-text "Find" -command dofind
338 pack .ctop.top.bar.findbut
-side left
340 set fstring .ctop.top.bar.findstring
341 lappend entries
$fstring
342 entry
$fstring -width 30 -font $textfont -textvariable findstring
343 pack
$fstring -side left
-expand 1 -fill x
345 set findtypemenu
[tk_optionMenu .ctop.top.bar.findtype \
346 findtype Exact IgnCase Regexp
]
347 set findloc
"All fields"
348 tk_optionMenu .ctop.top.bar.findloc findloc
"All fields" Headline \
349 Comments Author Committer Files Pickaxe
350 pack .ctop.top.bar.findloc
-side right
351 pack .ctop.top.bar.findtype
-side right
352 # for making sure type==Exact whenever loc==Pickaxe
353 trace add variable findloc
write findlocchange
355 panedwindow .ctop.cdet
-orient horizontal
357 frame .ctop.cdet.left
358 set ctext .ctop.cdet.left.ctext
359 text
$ctext -bg white
-state disabled
-font $textfont \
360 -width $geometry(ctextw
) -height $geometry(ctexth
) \
361 -yscrollcommand ".ctop.cdet.left.sb set"
362 scrollbar .ctop.cdet.left.sb
-command "$ctext yview"
363 pack .ctop.cdet.left.sb
-side right
-fill y
364 pack
$ctext -side left
-fill both
-expand 1
365 .ctop.cdet add .ctop.cdet.left
367 $ctext tag conf filesep
-font [concat
$textfont bold
] -back "#aaaaaa"
369 $ctext tag conf hunksep
-back blue
-fore white
370 $ctext tag conf d0
-back "#ff8080"
371 $ctext tag conf d1
-back green
373 $ctext tag conf hunksep
-fore blue
374 $ctext tag conf d0
-fore red
375 $ctext tag conf d1
-fore "#00a000"
376 $ctext tag conf found
-back yellow
379 frame .ctop.cdet.right
380 set cflist .ctop.cdet.right.cfiles
381 listbox
$cflist -bg white
-selectmode extended
-width $geometry(cflistw
) \
382 -yscrollcommand ".ctop.cdet.right.sb set"
383 scrollbar .ctop.cdet.right.sb
-command "$cflist yview"
384 pack .ctop.cdet.right.sb
-side right
-fill y
385 pack
$cflist -side left
-fill both
-expand 1
386 .ctop.cdet add .ctop.cdet.right
387 bind .ctop.cdet
<Configure
> {resizecdetpanes
%W
%w
}
389 pack .ctop
-side top
-fill both
-expand 1
391 bindall
<1> {selcanvline
%W
%x
%y
}
392 #bindall <B1-Motion> {selcanvline %W %x %y}
393 bindall
<ButtonRelease-4
> "allcanvs yview scroll -5 units"
394 bindall
<ButtonRelease-5
> "allcanvs yview scroll 5 units"
395 bindall
<2> "allcanvs scan mark 0 %y"
396 bindall
<B2-Motion
> "allcanvs scan dragto 0 %y"
397 bind .
<Key-Up
> "selnextline -1"
398 bind .
<Key-Down
> "selnextline 1"
399 bind .
<Key-Prior
> "allcanvs yview scroll -1 pages"
400 bind .
<Key-Next
> "allcanvs yview scroll 1 pages"
401 bindkey
<Key-Delete
> "$ctext yview scroll -1 pages"
402 bindkey
<Key-BackSpace
> "$ctext yview scroll -1 pages"
403 bindkey
<Key-space
> "$ctext yview scroll 1 pages"
404 bindkey p
"selnextline -1"
405 bindkey n
"selnextline 1"
406 bindkey b
"$ctext yview scroll -1 pages"
407 bindkey d
"$ctext yview scroll 18 units"
408 bindkey u
"$ctext yview scroll -18 units"
409 bindkey
/ {findnext
1}
410 bindkey
<Key-Return
> {findnext
0}
413 bind .
<Control-q
> doquit
414 bind .
<Control-f
> dofind
415 bind .
<Control-g
> {findnext
0}
416 bind .
<Control-r
> findprev
417 bind .
<Control-equal
> {incrfont
1}
418 bind .
<Control-KP_Add
> {incrfont
1}
419 bind .
<Control-minus
> {incrfont
-1}
420 bind .
<Control-KP_Subtract
> {incrfont
-1}
421 bind $cflist <<ListboxSelect>> listboxsel
422 bind . <Destroy> {savestuff %W}
423 bind . <Button-1> "click %W"
424 bind $fstring <Key-Return> dofind
425 bind $sha1entry <Key-Return> gotocommit
426 bind $sha1entry <<PasteSelection>> clearsha1
428 set maincursor [. cget -cursor]
429 set textcursor [$ctext cget -cursor]
431 set rowctxmenu .rowctxmenu
432 menu $rowctxmenu -tearoff 0
433 $rowctxmenu add command -label "Diff this -> selected" \
434 -command {diffvssel 0}
435 $rowctxmenu add command -label "Diff selected -> this" \
436 -command {diffvssel 1}
437 $rowctxmenu add command -label "Make patch" -command mkpatch
438 $rowctxmenu add command -label "Create tag" -command mktag
439 $rowctxmenu add command -label "Write commit to file" -command writecommit
442 # when we make a key binding for the toplevel, make sure
443 # it doesn't get triggered when that key is pressed in the
444 # find string entry widget.
445 proc bindkey {ev script} {
448 set escript [bind Entry $ev]
449 if {$escript == {}} {
450 set escript [bind Entry <Key>]
453 bind $e $ev "$escript; break"
457 # set the focus back to the toplevel for any click outside
468 global canv canv2 canv3 ctext cflist mainfont textfont
470 if {$stuffsaved} return
471 if {![winfo viewable .]} return
473 set f [open "~/.gitk-new" w]
474 puts $f [list set mainfont $mainfont]
475 puts $f [list set textfont $textfont]
476 puts $f [list set findmergefiles $findmergefiles]
477 puts $f [list set gaudydiff $gaudydiff]
478 puts $f "set geometry(width) [winfo width .ctop]"
479 puts $f "set geometry(height) [winfo height .ctop]"
480 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
481 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
482 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
483 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
484 set wid [expr {([winfo width $ctext] - 8) \
485 / [font measure $textfont "0"]}]
486 puts $f "set geometry(ctextw) $wid"
487 set wid [expr {([winfo width $cflist] - 11) \
488 / [font measure [$cflist cget -font] "0"]}]
489 puts $f "set geometry(cflistw) $wid"
491 file rename -force "~/.gitk-new" "~/.gitk"
496 proc resizeclistpanes {win w} {
498 if [info exists oldwidth($win)] {
499 set s0 [$win sash coord 0]
500 set s1 [$win sash coord 1]
502 set sash0 [expr {int($w/2 - 2)}]
503 set sash1 [expr {int($w*5/6 - 2)}]
505 set factor [expr {1.0 * $w / $oldwidth($win)}]
506 set sash0 [expr {int($factor * [lindex $s0 0])}]
507 set sash1 [expr {int($factor * [lindex $s1 0])}]
511 if {$sash1 < $sash0 + 20} {
512 set sash1 [expr $sash0 + 20]
514 if {$sash1 > $w - 10} {
515 set sash1 [expr $w - 10]
516 if {$sash0 > $sash1 - 20} {
517 set sash0 [expr $sash1 - 20]
521 $win sash place 0 $sash0 [lindex $s0 1]
522 $win sash place 1 $sash1 [lindex $s1 1]
524 set oldwidth($win) $w
527 proc resizecdetpanes {win w} {
529 if [info exists oldwidth($win)] {
530 set s0 [$win sash coord 0]
532 set sash0 [expr {int($w*3/4 - 2)}]
534 set factor [expr {1.0 * $w / $oldwidth($win)}]
535 set sash0 [expr {int($factor * [lindex $s0 0])}]
539 if {$sash0 > $w - 15} {
540 set sash0 [expr $w - 15]
543 $win sash place 0 $sash0 [lindex $s0 1]
545 set oldwidth($win) $w
549 global canv canv2 canv3
555 proc bindall {event action} {
556 global canv canv2 canv3
557 bind $canv $event $action
558 bind $canv2 $event $action
559 bind $canv3 $event $action
564 if {[winfo exists $w]} {
569 wm title $w "About gitk"
573 Copyright © 2005 Paul Mackerras
575 Use and redistribute under the terms of the GNU General Public License} \
576 -justify center -aspect 400
577 pack $w.m -side top -fill x -padx 20 -pady 20
578 button $w.ok -text Close -command "destroy $w"
579 pack $w.ok -side bottom
582 proc assigncolor {id} {
583 global commitinfo colormap commcolors colors nextcolor
584 global parents nparents children nchildren
585 global cornercrossings crossings
587 if [info exists colormap($id)] return
588 set ncolors [llength $colors]
589 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
590 set child [lindex $children($id) 0]
591 if {[info exists colormap($child)]
592 && $nparents($child) == 1} {
593 set colormap($id) $colormap($child)
598 if {[info exists cornercrossings($id)]} {
599 foreach x $cornercrossings($id) {
600 if {[info exists colormap($x)]
601 && [lsearch -exact $badcolors $colormap($x)] < 0} {
602 lappend badcolors $colormap($x)
605 if {[llength $badcolors] >= $ncolors} {
609 set origbad $badcolors
610 if {[llength $badcolors] < $ncolors - 1} {
611 if {[info exists crossings($id)]} {
612 foreach x $crossings($id) {
613 if {[info exists colormap($x)]
614 && [lsearch -exact $badcolors $colormap($x)] < 0} {
615 lappend badcolors $colormap($x)
618 if {[llength $badcolors] >= $ncolors} {
619 set badcolors $origbad
622 set origbad $badcolors
624 if {[llength $badcolors] < $ncolors - 1} {
625 foreach child $children($id) {
626 if {[info exists colormap($child)]
627 && [lsearch -exact $badcolors $colormap($child)] < 0} {
628 lappend badcolors $colormap($child)
630 if {[info exists parents($child)]} {
631 foreach p $parents($child) {
632 if {[info exists colormap($p)]
633 && [lsearch -exact $badcolors $colormap($p)] < 0} {
634 lappend badcolors $colormap($p)
639 if {[llength $badcolors] >= $ncolors} {
640 set badcolors $origbad
643 for {set i 0} {$i <= $ncolors} {incr i} {
644 set c [lindex $colors $nextcolor]
645 if {[incr nextcolor] >= $ncolors} {
648 if {[lsearch -exact $badcolors $c]} break
654 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
655 global mainline sidelines
656 global nchildren ncleft
663 set lthickness [expr {int($linespc / 9) + 1}]
664 catch {unset mainline}
665 catch {unset sidelines}
666 foreach id [array names nchildren] {
667 set ncleft($id) $nchildren($id)
671 proc bindline {t id} {
674 $canv bind $t <Enter> "lineenter %x %y $id"
675 $canv bind $t <Motion> "linemotion %x %y $id"
676 $canv bind $t <Leave> "lineleave $id"
677 $canv bind $t <Button-1> "lineclick %x %y $id"
680 proc drawcommitline {level} {
681 global parents children nparents nchildren todo
682 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
683 global lineid linehtag linentag linedtag commitinfo
684 global colormap numcommits currentparents dupparents
685 global oldlevel oldnlines oldtodo
686 global idtags idline idheads
687 global lineno lthickness mainline sidelines
688 global commitlisted rowtextx idpos
692 set id [lindex $todo $level]
693 set lineid($lineno) $id
694 set idline($id) $lineno
695 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
696 if {![info exists commitinfo($id)]} {
698 if {![info exists commitinfo($id)]} {
699 set commitinfo($id) {"No commit information available"}
704 set currentparents {}
706 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
707 foreach p $parents($id) {
708 if {[lsearch -exact $currentparents $p] < 0} {
709 lappend currentparents $p
711 # remember that this parent was listed twice
712 lappend dupparents $p
716 set x [expr $canvx0 + $level * $linespc]
718 set canvy [expr $canvy + $linespc]
719 allcanvs conf -scrollregion \
720 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
721 if {[info exists mainline($id)]} {
722 lappend mainline($id) $x $y1
723 set t [$canv create line $mainline($id) \
724 -width $lthickness -fill $colormap($id)]
728 if {[info exists sidelines($id)]} {
729 foreach ls $sidelines($id) {
730 set coords [lindex $ls 0]
731 set thick [lindex $ls 1]
732 set t [$canv create line $coords -fill $colormap($id) \
733 -width [expr {$thick * $lthickness}]]
738 set orad [expr {$linespc / 3}]
739 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
740 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
741 -fill $ofill -outline black -width 1]
743 $canv bind $t <1> {selcanvline {} %x %y}
744 set xt [expr $canvx0 + [llength $todo] * $linespc]
745 if {[llength $currentparents] > 2} {
746 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
748 set rowtextx($lineno) $xt
749 set idpos($id) [list $x $xt $y1]
750 if {[info exists idtags($id)] || [info exists idheads($id)]} {
751 set xt [drawtags $id $x $xt $y1]
753 set headline [lindex $commitinfo($id) 0]
754 set name [lindex $commitinfo($id) 1]
755 set date [lindex $commitinfo($id) 2]
756 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
757 -text $headline -font $mainfont ]
758 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
759 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
760 -text $name -font $namefont]
761 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
762 -text $date -font $mainfont]
765 proc drawtags {id x xt y1} {
766 global idtags idheads
767 global linespc lthickness
772 if {[info exists idtags($id)]} {
773 set marks $idtags($id)
774 set ntags [llength $marks]
776 if {[info exists idheads($id)]} {
777 set marks [concat $marks $idheads($id)]
783 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
784 set yt [expr $y1 - 0.5 * $linespc]
785 set yb [expr $yt + $linespc - 1]
789 set wid [font measure $mainfont $tag]
792 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
794 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
795 -width $lthickness -fill black -tags tag.$id]
797 foreach tag $marks x $xvals wid $wvals {
798 set xl [expr $x + $delta]
799 set xr [expr $x + $delta + $wid + $lthickness]
800 if {[incr ntags -1] >= 0} {
802 $canv create polygon $x [expr $yt + $delta] $xl $yt\
803 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
804 -width 1 -outline black -fill yellow -tags tag.$id
807 set xl [expr $xl - $delta/2]
808 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
809 -width 1 -outline black -fill green -tags tag.$id
811 $canv create text $xl $y1 -anchor w -text $tag \
812 -font $mainfont -tags tag.$id
817 proc updatetodo {level noshortcut} {
818 global currentparents ncleft todo
819 global mainline oldlevel oldtodo oldnlines
820 global canvx0 canvy linespc mainline
825 set oldnlines [llength $todo]
826 if {!$noshortcut && [llength $currentparents] == 1} {
827 set p [lindex $currentparents 0]
828 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
830 set x [expr $canvx0 + $level * $linespc]
831 set y [expr $canvy - $linespc]
832 set mainline($p) [list $x $y]
833 set todo [lreplace $todo $level $level $p]
838 set todo [lreplace $todo $level $level]
840 foreach p $currentparents {
842 set k [lsearch -exact $todo $p]
844 set todo [linsert $todo $i $p]
851 proc notecrossings {id lo hi corner} {
852 global oldtodo crossings cornercrossings
854 for {set i $lo} {[incr i] < $hi} {} {
855 set p [lindex $oldtodo $i]
856 if {$p == {}} continue
858 if {![info exists cornercrossings($id)]
859 || [lsearch -exact $cornercrossings($id) $p] < 0} {
860 lappend cornercrossings($id) $p
862 if {![info exists cornercrossings($p)]
863 || [lsearch -exact $cornercrossings($p) $id] < 0} {
864 lappend cornercrossings($p) $id
867 if {![info exists crossings($id)]
868 || [lsearch -exact $crossings($id) $p] < 0} {
869 lappend crossings($id) $p
871 if {![info exists crossings($p)]
872 || [lsearch -exact $crossings($p) $id] < 0} {
873 lappend crossings($p) $id
880 global canv mainline sidelines canvx0 canvy linespc
881 global oldlevel oldtodo todo currentparents dupparents
882 global lthickness linespc canvy colormap
884 set y1 [expr $canvy - $linespc]
887 foreach id $oldtodo {
889 if {$id == {}} continue
890 set xi [expr {$canvx0 + $i * $linespc}]
891 if {$i == $oldlevel} {
892 foreach p $currentparents {
893 set j [lsearch -exact $todo $p]
894 set coords [list $xi $y1]
895 set xj [expr {$canvx0 + $j * $linespc}]
897 lappend coords [expr $xj + $linespc] $y1
898 notecrossings $p $j $i [expr {$j + 1}]
899 } elseif {$j > $i + 1} {
900 lappend coords [expr $xj - $linespc] $y1
901 notecrossings $p $i $j [expr {$j - 1}]
903 if {[lsearch -exact $dupparents $p] >= 0} {
904 # draw a double-width line to indicate the doubled parent
905 lappend coords $xj $y2
906 lappend sidelines($p) [list $coords 2]
907 if {![info exists mainline($p)]} {
908 set mainline($p) [list $xj $y2]
911 # normal case, no parent duplicated
912 if {![info exists mainline($p)]} {
914 lappend coords $xj $y2
916 set mainline($p) $coords
918 lappend coords $xj $y2
919 lappend sidelines($p) [list $coords 1]
923 } elseif {[lindex $todo $i] != $id} {
924 set j [lsearch -exact $todo $id]
925 set xj [expr {$canvx0 + $j * $linespc}]
926 lappend mainline($id) $xi $y1 $xj $y2
931 proc decidenext {{noread 0}} {
932 global parents children nchildren ncleft todo
933 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
934 global datemode cdate
936 global currentparents oldlevel oldnlines oldtodo
937 global lineno lthickness
939 # remove the null entry if present
940 set nullentry [lsearch -exact $todo {}]
941 if {$nullentry >= 0} {
942 set todo [lreplace $todo $nullentry $nullentry]
945 # choose which one to do next time around
946 set todol [llength $todo]
949 for {set k $todol} {[incr k -1] >= 0} {} {
950 set p [lindex $todo $k]
951 if {$ncleft($p) == 0} {
953 if {![info exists commitinfo($p)]} {
959 if {$latest == {} || $cdate($p) > $latest} {
961 set latest $cdate($p)
971 puts "ERROR: none of the pending commits can be done yet:"
973 puts " $p ($ncleft($p))"
979 # If we are reducing, put in a null entry
980 if {$todol < $oldnlines} {
981 if {$nullentry >= 0} {
984 && [lindex $oldtodo $i] == [lindex $todo $i]} {
994 set todo [linsert $todo $i {}]
1003 proc drawcommit {id} {
1004 global phase todo nchildren datemode nextupdate
1007 if {$phase != "incrdraw"} {
1010 set startcommits $id
1013 updatetodo 0 $datemode
1015 if {$nchildren($id) == 0} {
1017 lappend startcommits $id
1019 set level [decidenext 1]
1020 if {$level == {} || $id != [lindex $todo $level]} {
1025 drawcommitline $level
1026 if {[updatetodo $level $datemode]} {
1027 set level [decidenext 1]
1028 if {$level == {}} break
1030 set id [lindex $todo $level]
1031 if {![info exists commitlisted($id)]} {
1034 if {[clock clicks -milliseconds] >= $nextupdate} {
1042 proc finishcommits {} {
1045 global canv mainfont ctext maincursor textcursor
1047 if {$phase != "incrdraw"} {
1049 $canv create text 3 3 -anchor nw -text "No commits selected" \
1050 -font $mainfont -tags textitems
1054 set level [decidenext]
1055 drawrest $level [llength $startcommits]
1057 . config -cursor $maincursor
1058 $ctext config -cursor $textcursor
1062 global nextupdate startmsecs startcommits todo
1064 if {$startcommits == {}} return
1065 set startmsecs [clock clicks -milliseconds]
1066 set nextupdate [expr $startmsecs + 100]
1068 set todo [lindex $startcommits 0]
1072 proc drawrest {level startix} {
1073 global phase stopped redisplaying selectedline
1074 global datemode currentparents todo
1076 global nextupdate startmsecs startcommits idline
1080 set startid [lindex $startcommits $startix]
1082 if {$startid != {}} {
1083 set startline $idline($startid)
1087 drawcommitline $level
1088 set hard [updatetodo $level $datemode]
1089 if {$numcommits == $startline} {
1090 lappend todo $startid
1093 set startid [lindex $startcommits $startix]
1095 if {$startid != {}} {
1096 set startline $idline($startid)
1100 set level [decidenext]
1101 if {$level < 0} break
1104 if {[clock clicks -milliseconds] >= $nextupdate} {
1111 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1112 #puts "overall $drawmsecs ms for $numcommits commits"
1113 if {$redisplaying} {
1114 if {$stopped == 0 && [info exists selectedline]} {
1115 selectline $selectedline
1117 if {$stopped == 1} {
1119 after idle drawgraph
1126 proc findmatches {f} {
1127 global findtype foundstring foundstrlen
1128 if {$findtype == "Regexp"} {
1129 set matches [regexp -indices -all -inline $foundstring $f]
1131 if {$findtype == "IgnCase"} {
1132 set str [string tolower $f]
1138 while {[set j [string first $foundstring $str $i]] >= 0} {
1139 lappend matches [list $j [expr $j+$foundstrlen-1]]
1140 set i [expr $j + $foundstrlen]
1147 global findtype findloc findstring markedmatches commitinfo
1148 global numcommits lineid linehtag linentag linedtag
1149 global mainfont namefont canv canv2 canv3 selectedline
1150 global matchinglines foundstring foundstrlen
1155 set matchinglines {}
1156 if {$findloc == "Pickaxe"} {
1160 if {$findtype == "IgnCase"} {
1161 set foundstring [string tolower $findstring]
1163 set foundstring $findstring
1165 set foundstrlen [string length $findstring]
1166 if {$foundstrlen == 0} return
1167 if {$findloc == "Files"} {
1171 if {![info exists selectedline]} {
1174 set oldsel $selectedline
1177 set fldtypes {Headline Author Date Committer CDate Comment}
1178 for {set l 0} {$l < $numcommits} {incr l} {
1180 set info $commitinfo($id)
1182 foreach f $info ty $fldtypes {
1183 if {$findloc != "All fields" && $findloc != $ty} {
1186 set matches [findmatches $f]
1187 if {$matches == {}} continue
1189 if {$ty == "Headline"} {
1190 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1191 } elseif {$ty == "Author"} {
1192 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1193 } elseif {$ty == "Date"} {
1194 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1198 lappend matchinglines $l
1199 if {!$didsel && $l > $oldsel} {
1205 if {$matchinglines == {}} {
1207 } elseif {!$didsel} {
1208 findselectline [lindex $matchinglines 0]
1212 proc findselectline {l} {
1213 global findloc commentend ctext
1215 if {$findloc == "All fields" || $findloc == "Comments"} {
1216 # highlight the matches in the comments
1217 set f [$ctext get 1.0 $commentend]
1218 set matches [findmatches $f]
1219 foreach match $matches {
1220 set start [lindex $match 0]
1221 set end [expr [lindex $match 1] + 1]
1222 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1227 proc findnext {restart} {
1228 global matchinglines selectedline
1229 if {![info exists matchinglines]} {
1235 if {![info exists selectedline]} return
1236 foreach l $matchinglines {
1237 if {$l > $selectedline} {
1246 global matchinglines selectedline
1247 if {![info exists matchinglines]} {
1251 if {![info exists selectedline]} return
1253 foreach l $matchinglines {
1254 if {$l >= $selectedline} break
1258 findselectline $prev
1264 proc findlocchange {name ix op} {
1265 global findloc findtype findtypemenu
1266 if {$findloc == "Pickaxe"} {
1272 $findtypemenu entryconf 1 -state $state
1273 $findtypemenu entryconf 2 -state $state
1276 proc stopfindproc {{done 0}} {
1277 global findprocpid findprocfile findids
1278 global ctext findoldcursor phase maincursor textcursor
1279 global findinprogress
1281 catch {unset findids}
1282 if {[info exists findprocpid]} {
1284 catch {exec kill $findprocpid}
1286 catch {close $findprocfile}
1289 if {[info exists findinprogress]} {
1290 unset findinprogress
1291 if {$phase != "incrdraw"} {
1292 . config -cursor $maincursor
1293 $ctext config -cursor $textcursor
1298 proc findpatches {} {
1299 global findstring selectedline numcommits
1300 global findprocpid findprocfile
1301 global finddidsel ctext lineid findinprogress
1302 global findinsertpos
1304 if {$numcommits == 0} return
1306 # make a list of all the ids to search, starting at the one
1307 # after the selected line (if any)
1308 if {[info exists selectedline]} {
1314 for {set i 0} {$i < $numcommits} {incr i} {
1315 if {[incr l] >= $numcommits} {
1318 append inputids $lineid($l) "\n"
1322 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1325 error_popup "Error starting search process: $err"
1329 set findinsertpos end
1331 set findprocpid [pid $f]
1332 fconfigure $f -blocking 0
1333 fileevent $f readable readfindproc
1335 . config -cursor watch
1336 $ctext config -cursor watch
1337 set findinprogress 1
1340 proc readfindproc {} {
1341 global findprocfile finddidsel
1342 global idline matchinglines findinsertpos
1344 set n [gets $findprocfile line]
1346 if {[eof $findprocfile]} {
1354 if {![regexp {^[0-9a-f]{40}} $line id]} {
1355 error_popup "Can't parse git-diff-tree output: $line"
1359 if {![info exists idline($id)]} {
1360 puts stderr "spurious id: $id"
1367 proc insertmatch {l id} {
1368 global matchinglines findinsertpos finddidsel
1370 if {$findinsertpos == "end"} {
1371 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1372 set matchinglines [linsert $matchinglines 0 $l]
1375 lappend matchinglines $l
1378 set matchinglines [linsert $matchinglines $findinsertpos $l]
1389 global selectedline numcommits lineid ctext
1390 global ffileline finddidsel parents nparents
1391 global findinprogress findstartline findinsertpos
1392 global treediffs fdiffids fdiffsneeded fdiffpos
1393 global findmergefiles
1395 if {$numcommits == 0} return
1397 if {[info exists selectedline]} {
1398 set l [expr {$selectedline + 1}]
1403 set findstartline $l
1408 if {$findmergefiles || $nparents($id) == 1} {
1409 foreach p $parents($id) {
1410 if {![info exists treediffs([list $id $p])]} {
1411 append diffsneeded "$id $p\n"
1412 lappend fdiffsneeded [list $id $p]
1416 if {[incr l] >= $numcommits} {
1419 if {$l == $findstartline} break
1422 # start off a git-diff-tree process if needed
1423 if {$diffsneeded ne {}} {
1425 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1427 error_popup "Error starting search process: $err"
1430 catch {unset fdiffids}
1432 fconfigure $df -blocking 0
1433 fileevent $df readable [list readfilediffs $df]
1437 set findinsertpos end
1439 set p [lindex $parents($id) 0]
1440 . config -cursor watch
1441 $ctext config -cursor watch
1442 set findinprogress 1
1443 findcont [list $id $p]
1447 proc readfilediffs {df} {
1448 global findids fdiffids fdiffs
1450 set n [gets $df line]
1454 if {[catch {close $df} err]} {
1457 error_popup "Error in git-diff-tree: $err"
1458 } elseif {[info exists findids]} {
1462 error_popup "Couldn't find diffs for {$ids}"
1467 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1468 # start of a new string of diffs
1470 set fdiffids [list $id $p]
1472 } elseif {[string match ":*" $line]} {
1473 lappend fdiffs [lindex $line 5]
1477 proc donefilediff {} {
1478 global fdiffids fdiffs treediffs findids
1479 global fdiffsneeded fdiffpos
1481 if {[info exists fdiffids]} {
1482 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1483 && $fdiffpos < [llength $fdiffsneeded]} {
1484 # git-diff-tree doesn't output anything for a commit
1485 # which doesn't change anything
1486 set nullids [lindex $fdiffsneeded $fdiffpos]
1487 set treediffs($nullids) {}
1488 if {[info exists findids] && $nullids eq $findids} {
1496 if {![info exists treediffs($fdiffids)]} {
1497 set treediffs($fdiffids) $fdiffs
1499 if {[info exists findids] && $fdiffids eq $findids} {
1506 proc findcont {ids} {
1507 global findids treediffs parents nparents treepending
1508 global ffileline findstartline finddidsel
1509 global lineid numcommits matchinglines findinprogress
1510 global findmergefiles
1512 set id [lindex $ids 0]
1513 set p [lindex $ids 1]
1514 set pi [lsearch -exact $parents($id) $p]
1517 if {$findmergefiles || $nparents($id) == 1} {
1518 if {![info exists treediffs($ids)]} {
1524 foreach f $treediffs($ids) {
1525 set x [findmatches $f]
1533 set pi $nparents($id)
1536 set pi $nparents($id)
1538 if {[incr pi] >= $nparents($id)} {
1540 if {[incr l] >= $numcommits} {
1543 if {$l == $findstartline} break
1546 set p [lindex $parents($id) $pi]
1547 set ids [list $id $p]
1555 # mark a commit as matching by putting a yellow background
1556 # behind the headline
1557 proc markheadline {l id} {
1558 global canv mainfont linehtag commitinfo
1560 set bbox [$canv bbox $linehtag($l)]
1561 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1565 # mark the bits of a headline, author or date that match a find string
1566 proc markmatches {canv l str tag matches font} {
1567 set bbox [$canv bbox $tag]
1568 set x0 [lindex $bbox 0]
1569 set y0 [lindex $bbox 1]
1570 set y1 [lindex $bbox 3]
1571 foreach match $matches {
1572 set start [lindex $match 0]
1573 set end [lindex $match 1]
1574 if {$start > $end} continue
1575 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1576 set xlen [font measure $font [string range $str 0 [expr $end]]]
1577 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1578 -outline {} -tags matches -fill yellow]
1583 proc unmarkmatches {} {
1584 global matchinglines findids
1585 allcanvs delete matches
1586 catch {unset matchinglines}
1587 catch {unset findids}
1590 proc selcanvline {w x y} {
1591 global canv canvy0 ctext linespc selectedline
1592 global lineid linehtag linentag linedtag rowtextx
1593 set ymax [lindex [$canv cget -scrollregion] 3]
1594 if {$ymax == {}} return
1595 set yfrac [lindex [$canv yview] 0]
1596 set y [expr {$y + $yfrac * $ymax}]
1597 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1602 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1608 proc selectline {l} {
1609 global canv canv2 canv3 ctext commitinfo selectedline
1610 global lineid linehtag linentag linedtag
1611 global canvy0 linespc parents nparents
1612 global cflist currentid sha1entry
1613 global commentend idtags
1615 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1617 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1618 -tags secsel -fill [$canv cget -selectbackground]]
1620 $canv2 delete secsel
1621 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1622 -tags secsel -fill [$canv2 cget -selectbackground]]
1624 $canv3 delete secsel
1625 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1626 -tags secsel -fill [$canv3 cget -selectbackground]]
1628 set y [expr {$canvy0 + $l * $linespc}]
1629 set ymax [lindex [$canv cget -scrollregion] 3]
1630 set ytop [expr {$y - $linespc - 1}]
1631 set ybot [expr {$y + $linespc + 1}]
1632 set wnow [$canv yview]
1633 set wtop [expr [lindex $wnow 0] * $ymax]
1634 set wbot [expr [lindex $wnow 1] * $ymax]
1635 set wh [expr {$wbot - $wtop}]
1637 if {$ytop < $wtop} {
1638 if {$ybot < $wtop} {
1639 set newtop [expr {$y - $wh / 2.0}]
1642 if {$newtop > $wtop - $linespc} {
1643 set newtop [expr {$wtop - $linespc}]
1646 } elseif {$ybot > $wbot} {
1647 if {$ytop > $wbot} {
1648 set newtop [expr {$y - $wh / 2.0}]
1650 set newtop [expr {$ybot - $wh}]
1651 if {$newtop < $wtop + $linespc} {
1652 set newtop [expr {$wtop + $linespc}]
1656 if {$newtop != $wtop} {
1660 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1666 $sha1entry delete 0 end
1667 $sha1entry insert 0 $id
1668 $sha1entry selection from 0
1669 $sha1entry selection to end
1671 $ctext conf -state normal
1672 $ctext delete 0.0 end
1673 $ctext mark set fmark.0 0.0
1674 $ctext mark gravity fmark.0 left
1675 set info $commitinfo($id)
1676 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1677 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1678 if {[info exists idtags($id)]} {
1679 $ctext insert end "Tags:"
1680 foreach tag $idtags($id) {
1681 $ctext insert end " $tag"
1683 $ctext insert end "\n"
1685 $ctext insert end "\n"
1686 $ctext insert end [lindex $info 5]
1687 $ctext insert end "\n"
1688 $ctext tag delete Comments
1689 $ctext tag remove found 1.0 end
1690 $ctext conf -state disabled
1691 set commentend [$ctext index "end - 1c"]
1693 $cflist delete 0 end
1694 $cflist insert end "Comments"
1695 startdiff $id $parents($id)
1698 proc startdiff {id vs} {
1699 global diffpending diffpindex
1700 global diffindex difffilestart
1701 global curdifftag curtagstart
1706 catch {unset difffilestart}
1707 set curdifftag Comments
1709 contdiff [list $id [lindex $vs 0]]
1712 proc contdiff {ids} {
1713 global treediffs diffids treepending
1716 if {![info exists treediffs($ids)]} {
1717 if {![info exists treepending]} {
1725 proc selnextline {dir} {
1727 if {![info exists selectedline]} return
1728 set l [expr $selectedline + $dir]
1733 proc addtocflist {ids} {
1734 global treediffs cflist diffpindex
1736 set colors {black blue green red cyan magenta}
1737 set color [lindex $colors [expr {$diffpindex % [llength $colors]}]]
1738 foreach f $treediffs($ids) {
1739 $cflist insert end $f
1740 $cflist itemconf end -foreground $color
1745 proc gettreediffs {ids} {
1746 global treediffs parents treepending
1747 set treepending $ids
1748 set treediffs($ids) {}
1749 set id [lindex $ids 0]
1750 set p [lindex $ids 1]
1751 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1752 fconfigure $gdtf -blocking 0
1753 fileevent $gdtf readable "gettreediffline $gdtf {$ids}"
1756 proc gettreediffline {gdtf ids} {
1757 global treediffs treepending diffids
1758 set n [gets $gdtf line]
1760 if {![eof $gdtf]} return
1763 if {[info exists diffids]} {
1764 if {$ids != $diffids} {
1765 gettreediffs $diffids
1772 set file [lindex $line 5]
1773 lappend treediffs($ids) $file
1776 proc getblobdiffs {ids} {
1777 global diffopts blobdifffd diffids env
1778 global nextupdate diffinhdr
1780 set id [lindex $ids 0]
1781 set p [lindex $ids 1]
1782 set env(GIT_DIFF_OPTS) $diffopts
1783 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1784 puts "error getting diffs: $err"
1788 fconfigure $bdf -blocking 0
1789 set blobdifffd($ids) $bdf
1790 fileevent $bdf readable [list getblobdiffline $bdf $ids]
1791 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1794 proc getblobdiffline {bdf ids} {
1795 global diffids blobdifffd ctext curdifftag curtagstart
1796 global diffnexthead diffnextnote diffindex difffilestart
1797 global nextupdate diffpending diffpindex diffinhdr
1800 set n [gets $bdf line]
1804 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1805 $ctext tag add $curdifftag $curtagstart end
1806 if {[incr diffpindex] < [llength $diffpending]} {
1807 set id [lindex $ids 0]
1808 set p [lindex $diffpending $diffpindex]
1809 contdiff [list $id $p]
1815 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1818 $ctext conf -state normal
1819 if {[regexp {^diff --git a/(.*) b/} $line match fname]} {
1820 # start of a new file
1821 $ctext insert end "\n"
1822 $ctext tag add $curdifftag $curtagstart end
1823 set curtagstart [$ctext index "end - 1c"]
1825 set here [$ctext index "end - 1c"]
1826 set difffilestart($diffindex) $here
1828 # start mark names at fmark.1 for first file
1829 $ctext mark set fmark.$diffindex $here
1830 $ctext mark gravity fmark.$diffindex left
1831 set curdifftag "f:$fname"
1832 $ctext tag delete $curdifftag
1833 set l [expr {(78 - [string length $header]) / 2}]
1834 set pad [string range "----------------------------------------" 1 $l]
1835 $ctext insert end "$pad $header $pad\n" filesep
1837 } elseif {[regexp {^(---|\+\+\+)} $line]} {
1839 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1840 $line match f1l f1c f2l f2c rest]} {
1842 $ctext insert end "\t" hunksep
1843 $ctext insert end " $f1l " d0 " $f2l " d1
1844 $ctext insert end " $rest \n" hunksep
1846 $ctext insert end "$line\n" hunksep
1850 set x [string range $line 0 0]
1851 if {$x == "-" || $x == "+"} {
1852 set tag [expr {$x == "+"}]
1854 set line [string range $line 1 end]
1856 $ctext insert end "$line\n" d$tag
1857 } elseif {$x == " "} {
1859 set line [string range $line 1 end]
1861 $ctext insert end "$line\n"
1862 } elseif {$diffinhdr || $x == "\\"} {
1863 # e.g. "\ No newline at end of file"
1864 $ctext insert end "$line\n" filesep
1866 # Something else we don't recognize
1867 if {$curdifftag != "Comments"} {
1868 $ctext insert end "\n"
1869 $ctext tag add $curdifftag $curtagstart end
1870 set curtagstart [$ctext index "end - 1c"]
1871 set curdifftag Comments
1873 $ctext insert end "$line\n" filesep
1876 $ctext conf -state disabled
1877 if {[clock clicks -milliseconds] >= $nextupdate} {
1879 fileevent $bdf readable {}
1881 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1886 global difffilestart ctext
1887 set here [$ctext index @0,0]
1888 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1889 if {[$ctext compare $difffilestart($i) > $here]} {
1890 $ctext yview $difffilestart($i)
1896 proc listboxsel {} {
1897 global ctext cflist currentid treediffs
1898 if {![info exists currentid]} return
1899 set sel [lsort [$cflist curselection]]
1900 if {$sel eq {}} return
1901 set first [lindex $sel 0]
1902 catch {$ctext yview fmark.$first}
1906 global linespc charspc canvx0 canvy0 mainfont
1907 set linespc [font metrics $mainfont -linespace]
1908 set charspc [font measure $mainfont "m"]
1909 set canvy0 [expr 3 + 0.5 * $linespc]
1910 set canvx0 [expr 3 + 0.5 * $linespc]
1914 global selectedline stopped redisplaying phase
1915 if {$stopped > 1} return
1916 if {$phase == "getcommits"} return
1918 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1925 proc incrfont {inc} {
1926 global mainfont namefont textfont selectedline ctext canv phase
1927 global stopped entries
1929 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1930 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1931 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1933 $ctext conf -font $textfont
1934 $ctext tag conf filesep -font [concat $textfont bold]
1935 foreach e $entries {
1936 $e conf -font $mainfont
1938 if {$phase == "getcommits"} {
1939 $canv itemconf textitems -font $mainfont
1945 global sha1entry sha1string
1946 if {[string length $sha1string] == 40} {
1947 $sha1entry delete 0 end
1951 proc sha1change {n1 n2 op} {
1952 global sha1string currentid sha1but
1953 if {$sha1string == {}
1954 || ([info exists currentid] && $sha1string == $currentid)} {
1959 if {[$sha1but cget -state] == $state} return
1960 if {$state == "normal"} {
1961 $sha1but conf -state normal -relief raised -text "Goto: "
1963 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1967 proc gotocommit {} {
1968 global sha1string currentid idline tagids
1969 global lineid numcommits
1971 if {$sha1string == {}
1972 || ([info exists currentid] && $sha1string == $currentid)} return
1973 if {[info exists tagids($sha1string)]} {
1974 set id $tagids($sha1string)
1976 set id [string tolower $sha1string]
1977 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
1979 for {set l 0} {$l < $numcommits} {incr l} {
1980 if {[string match $id* $lineid($l)]} {
1981 lappend matches $lineid($l)
1984 if {$matches ne {}} {
1985 if {[llength $matches] > 1} {
1986 error_popup "Short SHA1 id $id is ambiguous"
1989 set id [lindex $matches 0]
1993 if {[info exists idline($id)]} {
1994 selectline $idline($id)
1997 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2002 error_popup "$type $sha1string is not known"
2005 proc lineenter {x y id} {
2006 global hoverx hovery hoverid hovertimer
2007 global commitinfo canv
2009 if {![info exists commitinfo($id)]} return
2013 if {[info exists hovertimer]} {
2014 after cancel $hovertimer
2016 set hovertimer [after 500 linehover]
2020 proc linemotion {x y id} {
2021 global hoverx hovery hoverid hovertimer
2023 if {[info exists hoverid] && $id == $hoverid} {
2026 if {[info exists hovertimer]} {
2027 after cancel $hovertimer
2029 set hovertimer [after 500 linehover]
2033 proc lineleave {id} {
2034 global hoverid hovertimer canv
2036 if {[info exists hoverid] && $id == $hoverid} {
2038 if {[info exists hovertimer]} {
2039 after cancel $hovertimer
2047 global hoverx hovery hoverid hovertimer
2048 global canv linespc lthickness
2049 global commitinfo mainfont
2051 set text [lindex $commitinfo($hoverid) 0]
2052 set ymax [lindex [$canv cget -scrollregion] 3]
2053 if {$ymax == {}} return
2054 set yfrac [lindex [$canv yview] 0]
2055 set x [expr {$hoverx + 2 * $linespc}]
2056 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2057 set x0 [expr {$x - 2 * $lthickness}]
2058 set y0 [expr {$y - 2 * $lthickness}]
2059 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2060 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2061 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2062 -fill \#ffff80 -outline black -width 1 -tags hover]
2064 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2068 proc lineclick {x y id} {
2069 global ctext commitinfo children cflist canv
2073 # fill the details pane with info about this line
2074 $ctext conf -state normal
2075 $ctext delete 0.0 end
2076 $ctext insert end "Parent:\n "
2077 catch {destroy $ctext.$id}
2078 button $ctext.$id -text "Go:" -command "selbyid $id" \
2080 $ctext window create end -window $ctext.$id -align center
2081 set info $commitinfo($id)
2082 $ctext insert end "\t[lindex $info 0]\n"
2083 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2084 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2085 $ctext insert end "\tID:\t$id\n"
2086 if {[info exists children($id)]} {
2087 $ctext insert end "\nChildren:"
2088 foreach child $children($id) {
2089 $ctext insert end "\n "
2090 catch {destroy $ctext.$child}
2091 button $ctext.$child -text "Go:" -command "selbyid $child" \
2093 $ctext window create end -window $ctext.$child -align center
2094 set info $commitinfo($child)
2095 $ctext insert end "\t[lindex $info 0]"
2098 $ctext conf -state disabled
2100 $cflist delete 0 end
2105 if {[info exists idline($id)]} {
2106 selectline $idline($id)
2112 if {![info exists startmstime]} {
2113 set startmstime [clock clicks -milliseconds]
2115 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2118 proc rowmenu {x y id} {
2119 global rowctxmenu idline selectedline rowmenuid
2121 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2126 $rowctxmenu entryconfigure 0 -state $state
2127 $rowctxmenu entryconfigure 1 -state $state
2128 $rowctxmenu entryconfigure 2 -state $state
2130 tk_popup $rowctxmenu $x $y
2133 proc diffvssel {dirn} {
2134 global rowmenuid selectedline lineid
2138 if {![info exists selectedline]} return
2140 set oldid $lineid($selectedline)
2141 set newid $rowmenuid
2143 set oldid $rowmenuid
2144 set newid $lineid($selectedline)
2146 $ctext conf -state normal
2147 $ctext delete 0.0 end
2148 $ctext mark set fmark.0 0.0
2149 $ctext mark gravity fmark.0 left
2150 $cflist delete 0 end
2151 $cflist insert end "Top"
2152 $ctext insert end "From $oldid\n "
2153 $ctext insert end [lindex $commitinfo($oldid) 0]
2154 $ctext insert end "\n\nTo $newid\n "
2155 $ctext insert end [lindex $commitinfo($newid) 0]
2156 $ctext insert end "\n"
2157 $ctext conf -state disabled
2158 $ctext tag delete Comments
2159 $ctext tag remove found 1.0 end
2160 startdiff [list $newid $oldid]
2164 global rowmenuid currentid commitinfo patchtop patchnum
2166 if {![info exists currentid]} return
2167 set oldid $currentid
2168 set oldhead [lindex $commitinfo($oldid) 0]
2169 set newid $rowmenuid
2170 set newhead [lindex $commitinfo($newid) 0]
2173 catch {destroy $top}
2175 label $top.title -text "Generate patch"
2176 grid $top.title - -pady 10
2177 label $top.from -text "From:"
2178 entry $top.fromsha1 -width 40 -relief flat
2179 $top.fromsha1 insert 0 $oldid
2180 $top.fromsha1 conf -state readonly
2181 grid $top.from $top.fromsha1 -sticky w
2182 entry $top.fromhead -width 60 -relief flat
2183 $top.fromhead insert 0 $oldhead
2184 $top.fromhead conf -state readonly
2185 grid x $top.fromhead -sticky w
2186 label $top.to -text "To:"
2187 entry $top.tosha1 -width 40 -relief flat
2188 $top.tosha1 insert 0 $newid
2189 $top.tosha1 conf -state readonly
2190 grid $top.to $top.tosha1 -sticky w
2191 entry $top.tohead -width 60 -relief flat
2192 $top.tohead insert 0 $newhead
2193 $top.tohead conf -state readonly
2194 grid x $top.tohead -sticky w
2195 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2196 grid $top.rev x -pady 10
2197 label $top.flab -text "Output file:"
2198 entry $top.fname -width 60
2199 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2201 grid $top.flab $top.fname -sticky w
2203 button $top.buts.gen -text "Generate" -command mkpatchgo
2204 button $top.buts.can -text "Cancel" -command mkpatchcan
2205 grid $top.buts.gen $top.buts.can
2206 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2207 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2208 grid $top.buts - -pady 10 -sticky ew
2212 proc mkpatchrev {} {
2215 set oldid [$patchtop.fromsha1 get]
2216 set oldhead [$patchtop.fromhead get]
2217 set newid [$patchtop.tosha1 get]
2218 set newhead [$patchtop.tohead get]
2219 foreach e [list fromsha1 fromhead tosha1 tohead] \
2220 v [list $newid $newhead $oldid $oldhead] {
2221 $patchtop.$e conf -state normal
2222 $patchtop.$e delete 0 end
2223 $patchtop.$e insert 0 $v
2224 $patchtop.$e conf -state readonly
2231 set oldid [$patchtop.fromsha1 get]
2232 set newid [$patchtop.tosha1 get]
2233 set fname [$patchtop.fname get]
2234 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2235 error_popup "Error creating patch: $err"
2237 catch {destroy $patchtop}
2241 proc mkpatchcan {} {
2244 catch {destroy $patchtop}
2249 global rowmenuid mktagtop commitinfo
2253 catch {destroy $top}
2255 label $top.title -text "Create tag"
2256 grid $top.title - -pady 10
2257 label $top.id -text "ID:"
2258 entry $top.sha1 -width 40 -relief flat
2259 $top.sha1 insert 0 $rowmenuid
2260 $top.sha1 conf -state readonly
2261 grid $top.id $top.sha1 -sticky w
2262 entry $top.head -width 60 -relief flat
2263 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2264 $top.head conf -state readonly
2265 grid x $top.head -sticky w
2266 label $top.tlab -text "Tag name:"
2267 entry $top.tag -width 60
2268 grid $top.tlab $top.tag -sticky w
2270 button $top.buts.gen -text "Create" -command mktaggo
2271 button $top.buts.can -text "Cancel" -command mktagcan
2272 grid $top.buts.gen $top.buts.can
2273 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2274 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2275 grid $top.buts - -pady 10 -sticky ew
2280 global mktagtop env tagids idtags
2281 global idpos idline linehtag canv selectedline
2283 set id [$mktagtop.sha1 get]
2284 set tag [$mktagtop.tag get]
2286 error_popup "No tag name specified"
2289 if {[info exists tagids($tag)]} {
2290 error_popup "Tag \"$tag\" already exists"
2295 if {[info exists env(GIT_DIR)]} {
2296 set dir $env(GIT_DIR)
2298 set fname [file join $dir "refs/tags" $tag]
2299 set f [open $fname w]
2303 error_popup "Error creating tag: $err"
2307 set tagids($tag) $id
2308 lappend idtags($id) $tag
2309 $canv delete tag.$id
2310 set xt [eval drawtags $id $idpos($id)]
2311 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2312 if {[info exists selectedline] && $selectedline == $idline($id)} {
2313 selectline $selectedline
2320 catch {destroy $mktagtop}
2329 proc writecommit {} {
2330 global rowmenuid wrcomtop commitinfo wrcomcmd
2332 set top .writecommit
2334 catch {destroy $top}
2336 label $top.title -text "Write commit to file"
2337 grid $top.title - -pady 10
2338 label $top.id -text "ID:"
2339 entry $top.sha1 -width 40 -relief flat
2340 $top.sha1 insert 0 $rowmenuid
2341 $top.sha1 conf -state readonly
2342 grid $top.id $top.sha1 -sticky w
2343 entry $top.head -width 60 -relief flat
2344 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2345 $top.head conf -state readonly
2346 grid x $top.head -sticky w
2347 label $top.clab -text "Command:"
2348 entry $top.cmd -width 60 -textvariable wrcomcmd
2349 grid $top.clab $top.cmd -sticky w -pady 10
2350 label $top.flab -text "Output file:"
2351 entry $top.fname -width 60
2352 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2353 grid $top.flab $top.fname -sticky w
2355 button $top.buts.gen -text "Write" -command wrcomgo
2356 button $top.buts.can -text "Cancel" -command wrcomcan
2357 grid $top.buts.gen $top.buts.can
2358 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2359 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2360 grid $top.buts - -pady 10 -sticky ew
2367 set id [$wrcomtop.sha1 get]
2368 set cmd "echo $id | [$wrcomtop.cmd get]"
2369 set fname [$wrcomtop.fname get]
2370 if {[catch {exec sh -c $cmd >$fname &} err]} {
2371 error_popup "Error writing commit: $err"
2373 catch {destroy $wrcomtop}
2380 catch {destroy $wrcomtop}
2393 set diffopts "-U 5 -p"
2394 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2396 set mainfont {Helvetica 9}
2397 set textfont {Courier 9}
2398 set findmergefiles 0
2401 set colors {green red blue magenta darkgrey brown orange}
2403 catch {source ~/.gitk}
2405 set namefont $mainfont
2407 lappend namefont bold
2412 switch -regexp -- $arg {
2414 "^-b" { set boldnames 1 }
2415 "^-d" { set datemode 1 }
2417 lappend revtreeargs $arg
2429 getcommits $revtreeargs