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
343 image create bitmap bm-left
-data {
344 #define left_width 16
345 #define left_height 16
346 static unsigned char left_bits
[] = {
347 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
348 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
349 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
351 image create bitmap bm-right
-data {
352 #define right_width 16
353 #define right_height 16
354 static unsigned char right_bits
[] = {
355 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
356 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
357 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
359 button .ctop.top.bar.leftbut
-image bm-left
-command goback \
360 -state disabled
-width 26
361 pack .ctop.top.bar.leftbut
-side left
-fill y
362 button .ctop.top.bar.rightbut
-image bm-right
-command goforw \
363 -state disabled
-width 26
364 pack .ctop.top.bar.rightbut
-side left
-fill y
366 button .ctop.top.bar.findbut
-text "Find" -command dofind
367 pack .ctop.top.bar.findbut
-side left
369 set fstring .ctop.top.bar.findstring
370 lappend entries
$fstring
371 entry
$fstring -width 30 -font $textfont -textvariable findstring
372 pack
$fstring -side left
-expand 1 -fill x
374 set findtypemenu
[tk_optionMenu .ctop.top.bar.findtype \
375 findtype Exact IgnCase Regexp
]
376 set findloc
"All fields"
377 tk_optionMenu .ctop.top.bar.findloc findloc
"All fields" Headline \
378 Comments Author Committer Files Pickaxe
379 pack .ctop.top.bar.findloc
-side right
380 pack .ctop.top.bar.findtype
-side right
381 # for making sure type==Exact whenever loc==Pickaxe
382 trace add variable findloc
write findlocchange
384 panedwindow .ctop.cdet
-orient horizontal
386 frame .ctop.cdet.left
387 set ctext .ctop.cdet.left.ctext
388 text
$ctext -bg white
-state disabled
-font $textfont \
389 -width $geometry(ctextw
) -height $geometry(ctexth
) \
390 -yscrollcommand ".ctop.cdet.left.sb set"
391 scrollbar .ctop.cdet.left.sb
-command "$ctext yview"
392 pack .ctop.cdet.left.sb
-side right
-fill y
393 pack
$ctext -side left
-fill both
-expand 1
394 .ctop.cdet add .ctop.cdet.left
396 $ctext tag conf filesep
-font [concat
$textfont bold
] -back "#aaaaaa"
398 $ctext tag conf hunksep
-back blue
-fore white
399 $ctext tag conf d0
-back "#ff8080"
400 $ctext tag conf d1
-back green
402 $ctext tag conf hunksep
-fore blue
403 $ctext tag conf d0
-fore red
404 $ctext tag conf d1
-fore "#00a000"
405 $ctext tag conf m0
-fore red
406 $ctext tag conf m1
-fore blue
407 $ctext tag conf m2
-fore green
408 $ctext tag conf m3
-fore purple
409 $ctext tag conf
m4 -fore brown
410 $ctext tag conf mmax
-fore darkgrey
412 $ctext tag conf mresult
-font [concat
$textfont bold
]
413 $ctext tag conf msep
-font [concat
$textfont bold
]
414 $ctext tag conf found
-back yellow
417 frame .ctop.cdet.right
418 set cflist .ctop.cdet.right.cfiles
419 listbox
$cflist -bg white
-selectmode extended
-width $geometry(cflistw
) \
420 -yscrollcommand ".ctop.cdet.right.sb set"
421 scrollbar .ctop.cdet.right.sb
-command "$cflist yview"
422 pack .ctop.cdet.right.sb
-side right
-fill y
423 pack
$cflist -side left
-fill both
-expand 1
424 .ctop.cdet add .ctop.cdet.right
425 bind .ctop.cdet
<Configure
> {resizecdetpanes
%W
%w
}
427 pack .ctop
-side top
-fill both
-expand 1
429 bindall
<1> {selcanvline
%W
%x
%y
}
430 #bindall <B1-Motion> {selcanvline %W %x %y}
431 bindall
<ButtonRelease-4
> "allcanvs yview scroll -5 units"
432 bindall
<ButtonRelease-5
> "allcanvs yview scroll 5 units"
433 bindall
<2> "allcanvs scan mark 0 %y"
434 bindall
<B2-Motion
> "allcanvs scan dragto 0 %y"
435 bind .
<Key-Up
> "selnextline -1"
436 bind .
<Key-Down
> "selnextline 1"
437 bind .
<Key-Prior
> "allcanvs yview scroll -1 pages"
438 bind .
<Key-Next
> "allcanvs yview scroll 1 pages"
439 bindkey
<Key-Delete
> "$ctext yview scroll -1 pages"
440 bindkey
<Key-BackSpace
> "$ctext yview scroll -1 pages"
441 bindkey
<Key-space
> "$ctext yview scroll 1 pages"
442 bindkey p
"selnextline -1"
443 bindkey n
"selnextline 1"
444 bindkey b
"$ctext yview scroll -1 pages"
445 bindkey d
"$ctext yview scroll 18 units"
446 bindkey u
"$ctext yview scroll -18 units"
447 bindkey
/ {findnext
1}
448 bindkey
<Key-Return
> {findnext
0}
451 bind .
<Control-q
> doquit
452 bind .
<Control-f
> dofind
453 bind .
<Control-g
> {findnext
0}
454 bind .
<Control-r
> findprev
455 bind .
<Control-equal
> {incrfont
1}
456 bind .
<Control-KP_Add
> {incrfont
1}
457 bind .
<Control-minus
> {incrfont
-1}
458 bind .
<Control-KP_Subtract
> {incrfont
-1}
459 bind $cflist <<ListboxSelect>> listboxsel
460 bind . <Destroy> {savestuff %W}
461 bind . <Button-1> "click %W"
462 bind $fstring <Key-Return> dofind
463 bind $sha1entry <Key-Return> gotocommit
464 bind $sha1entry <<PasteSelection>> clearsha1
466 set maincursor [. cget -cursor]
467 set textcursor [$ctext cget -cursor]
469 set rowctxmenu .rowctxmenu
470 menu $rowctxmenu -tearoff 0
471 $rowctxmenu add command -label "Diff this -> selected" \
472 -command {diffvssel 0}
473 $rowctxmenu add command -label "Diff selected -> this" \
474 -command {diffvssel 1}
475 $rowctxmenu add command -label "Make patch" -command mkpatch
476 $rowctxmenu add command -label "Create tag" -command mktag
477 $rowctxmenu add command -label "Write commit to file" -command writecommit
480 # when we make a key binding for the toplevel, make sure
481 # it doesn't get triggered when that key is pressed in the
482 # find string entry widget.
483 proc bindkey {ev script} {
486 set escript [bind Entry $ev]
487 if {$escript == {}} {
488 set escript [bind Entry <Key>]
491 bind $e $ev "$escript; break"
495 # set the focus back to the toplevel for any click outside
506 global canv canv2 canv3 ctext cflist mainfont textfont
507 global stuffsaved findmergefiles gaudydiff maxgraphpct
509 if {$stuffsaved} return
510 if {![winfo viewable .]} return
512 set f [open "~/.gitk-new" w]
513 puts $f [list set mainfont $mainfont]
514 puts $f [list set textfont $textfont]
515 puts $f [list set findmergefiles $findmergefiles]
516 puts $f [list set gaudydiff $gaudydiff]
517 puts $f [list set maxgraphpct $maxgraphpct]
518 puts $f "set geometry(width) [winfo width .ctop]"
519 puts $f "set geometry(height) [winfo height .ctop]"
520 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
521 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
522 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
523 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
524 set wid [expr {([winfo width $ctext] - 8) \
525 / [font measure $textfont "0"]}]
526 puts $f "set geometry(ctextw) $wid"
527 set wid [expr {([winfo width $cflist] - 11) \
528 / [font measure [$cflist cget -font] "0"]}]
529 puts $f "set geometry(cflistw) $wid"
531 file rename -force "~/.gitk-new" "~/.gitk"
536 proc resizeclistpanes {win w} {
538 if [info exists oldwidth($win)] {
539 set s0 [$win sash coord 0]
540 set s1 [$win sash coord 1]
542 set sash0 [expr {int($w/2 - 2)}]
543 set sash1 [expr {int($w*5/6 - 2)}]
545 set factor [expr {1.0 * $w / $oldwidth($win)}]
546 set sash0 [expr {int($factor * [lindex $s0 0])}]
547 set sash1 [expr {int($factor * [lindex $s1 0])}]
551 if {$sash1 < $sash0 + 20} {
552 set sash1 [expr $sash0 + 20]
554 if {$sash1 > $w - 10} {
555 set sash1 [expr $w - 10]
556 if {$sash0 > $sash1 - 20} {
557 set sash0 [expr $sash1 - 20]
561 $win sash place 0 $sash0 [lindex $s0 1]
562 $win sash place 1 $sash1 [lindex $s1 1]
564 set oldwidth($win) $w
567 proc resizecdetpanes {win w} {
569 if [info exists oldwidth($win)] {
570 set s0 [$win sash coord 0]
572 set sash0 [expr {int($w*3/4 - 2)}]
574 set factor [expr {1.0 * $w / $oldwidth($win)}]
575 set sash0 [expr {int($factor * [lindex $s0 0])}]
579 if {$sash0 > $w - 15} {
580 set sash0 [expr $w - 15]
583 $win sash place 0 $sash0 [lindex $s0 1]
585 set oldwidth($win) $w
589 global canv canv2 canv3
595 proc bindall {event action} {
596 global canv canv2 canv3
597 bind $canv $event $action
598 bind $canv2 $event $action
599 bind $canv3 $event $action
604 if {[winfo exists $w]} {
609 wm title $w "About gitk"
613 Copyright © 2005 Paul Mackerras
615 Use and redistribute under the terms of the GNU General Public License} \
616 -justify center -aspect 400
617 pack $w.m -side top -fill x -padx 20 -pady 20
618 button $w.ok -text Close -command "destroy $w"
619 pack $w.ok -side bottom
622 proc assigncolor {id} {
623 global commitinfo colormap commcolors colors nextcolor
624 global parents nparents children nchildren
625 global cornercrossings crossings
627 if [info exists colormap($id)] return
628 set ncolors [llength $colors]
629 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
630 set child [lindex $children($id) 0]
631 if {[info exists colormap($child)]
632 && $nparents($child) == 1} {
633 set colormap($id) $colormap($child)
638 if {[info exists cornercrossings($id)]} {
639 foreach x $cornercrossings($id) {
640 if {[info exists colormap($x)]
641 && [lsearch -exact $badcolors $colormap($x)] < 0} {
642 lappend badcolors $colormap($x)
645 if {[llength $badcolors] >= $ncolors} {
649 set origbad $badcolors
650 if {[llength $badcolors] < $ncolors - 1} {
651 if {[info exists crossings($id)]} {
652 foreach x $crossings($id) {
653 if {[info exists colormap($x)]
654 && [lsearch -exact $badcolors $colormap($x)] < 0} {
655 lappend badcolors $colormap($x)
658 if {[llength $badcolors] >= $ncolors} {
659 set badcolors $origbad
662 set origbad $badcolors
664 if {[llength $badcolors] < $ncolors - 1} {
665 foreach child $children($id) {
666 if {[info exists colormap($child)]
667 && [lsearch -exact $badcolors $colormap($child)] < 0} {
668 lappend badcolors $colormap($child)
670 if {[info exists parents($child)]} {
671 foreach p $parents($child) {
672 if {[info exists colormap($p)]
673 && [lsearch -exact $badcolors $colormap($p)] < 0} {
674 lappend badcolors $colormap($p)
679 if {[llength $badcolors] >= $ncolors} {
680 set badcolors $origbad
683 for {set i 0} {$i <= $ncolors} {incr i} {
684 set c [lindex $colors $nextcolor]
685 if {[incr nextcolor] >= $ncolors} {
688 if {[lsearch -exact $badcolors $c]} break
694 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
695 global mainline sidelines
696 global nchildren ncleft
703 set lthickness [expr {int($linespc / 9) + 1}]
704 catch {unset mainline}
705 catch {unset sidelines}
706 foreach id [array names nchildren] {
707 set ncleft($id) $nchildren($id)
711 proc bindline {t id} {
714 $canv bind $t <Enter> "lineenter %x %y $id"
715 $canv bind $t <Motion> "linemotion %x %y $id"
716 $canv bind $t <Leave> "lineleave $id"
717 $canv bind $t <Button-1> "lineclick %x %y $id"
720 proc drawcommitline {level} {
721 global parents children nparents nchildren todo
722 global canv canv2 canv3 mainfont namefont canvy linespc
723 global lineid linehtag linentag linedtag commitinfo
724 global colormap numcommits currentparents dupparents
725 global oldlevel oldnlines oldtodo
726 global idtags idline idheads
727 global lineno lthickness mainline sidelines
728 global commitlisted rowtextx idpos
732 set id [lindex $todo $level]
733 set lineid($lineno) $id
734 set idline($id) $lineno
735 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
736 if {![info exists commitinfo($id)]} {
738 if {![info exists commitinfo($id)]} {
739 set commitinfo($id) {"No commit information available"}
744 set currentparents {}
746 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
747 foreach p $parents($id) {
748 if {[lsearch -exact $currentparents $p] < 0} {
749 lappend currentparents $p
751 # remember that this parent was listed twice
752 lappend dupparents $p
756 set x [xcoord $level $level $lineno]
758 set canvy [expr $canvy + $linespc]
759 allcanvs conf -scrollregion \
760 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
761 if {[info exists mainline($id)]} {
762 lappend mainline($id) $x $y1
763 set t [$canv create line $mainline($id) \
764 -width $lthickness -fill $colormap($id)]
768 if {[info exists sidelines($id)]} {
769 foreach ls $sidelines($id) {
770 set coords [lindex $ls 0]
771 set thick [lindex $ls 1]
772 set t [$canv create line $coords -fill $colormap($id) \
773 -width [expr {$thick * $lthickness}]]
778 set orad [expr {$linespc / 3}]
779 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
780 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
781 -fill $ofill -outline black -width 1]
783 $canv bind $t <1> {selcanvline {} %x %y}
784 set xt [xcoord [llength $todo] $level $lineno]
785 if {[llength $currentparents] > 2} {
786 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
788 set rowtextx($lineno) $xt
789 set idpos($id) [list $x $xt $y1]
790 if {[info exists idtags($id)] || [info exists idheads($id)]} {
791 set xt [drawtags $id $x $xt $y1]
793 set headline [lindex $commitinfo($id) 0]
794 set name [lindex $commitinfo($id) 1]
795 set date [lindex $commitinfo($id) 2]
796 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
797 -text $headline -font $mainfont ]
798 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
799 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
800 -text $name -font $namefont]
801 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
802 -text $date -font $mainfont]
805 proc drawtags {id x xt y1} {
806 global idtags idheads
807 global linespc lthickness
812 if {[info exists idtags($id)]} {
813 set marks $idtags($id)
814 set ntags [llength $marks]
816 if {[info exists idheads($id)]} {
817 set marks [concat $marks $idheads($id)]
823 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
824 set yt [expr $y1 - 0.5 * $linespc]
825 set yb [expr $yt + $linespc - 1]
829 set wid [font measure $mainfont $tag]
832 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
834 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
835 -width $lthickness -fill black -tags tag.$id]
837 foreach tag $marks x $xvals wid $wvals {
838 set xl [expr $x + $delta]
839 set xr [expr $x + $delta + $wid + $lthickness]
840 if {[incr ntags -1] >= 0} {
842 $canv create polygon $x [expr $yt + $delta] $xl $yt\
843 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
844 -width 1 -outline black -fill yellow -tags tag.$id
847 set xl [expr $xl - $delta/2]
848 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
849 -width 1 -outline black -fill green -tags tag.$id
851 $canv create text $xl $y1 -anchor w -text $tag \
852 -font $mainfont -tags tag.$id
857 proc updatetodo {level noshortcut} {
858 global currentparents ncleft todo
859 global mainline oldlevel oldtodo oldnlines
860 global canvy linespc mainline
861 global commitinfo lineno xspc1
865 set oldnlines [llength $todo]
866 if {!$noshortcut && [llength $currentparents] == 1} {
867 set p [lindex $currentparents 0]
868 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
870 set x [xcoord $level $level $lineno]
871 set y [expr $canvy - $linespc]
872 set mainline($p) [list $x $y]
873 set todo [lreplace $todo $level $level $p]
874 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
879 set todo [lreplace $todo $level $level]
881 foreach p $currentparents {
883 set k [lsearch -exact $todo $p]
885 set todo [linsert $todo $i $p]
892 proc notecrossings {id lo hi corner} {
893 global oldtodo crossings cornercrossings
895 for {set i $lo} {[incr i] < $hi} {} {
896 set p [lindex $oldtodo $i]
897 if {$p == {}} continue
899 if {![info exists cornercrossings($id)]
900 || [lsearch -exact $cornercrossings($id) $p] < 0} {
901 lappend cornercrossings($id) $p
903 if {![info exists cornercrossings($p)]
904 || [lsearch -exact $cornercrossings($p) $id] < 0} {
905 lappend cornercrossings($p) $id
908 if {![info exists crossings($id)]
909 || [lsearch -exact $crossings($id) $p] < 0} {
910 lappend crossings($id) $p
912 if {![info exists crossings($p)]
913 || [lsearch -exact $crossings($p) $id] < 0} {
914 lappend crossings($p) $id
920 proc xcoord {i level ln} {
921 global canvx0 xspc1 xspc2
923 set x [expr {$canvx0 + $i * $xspc1($ln)}]
924 if {$i > 0 && $i == $level} {
925 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
926 } elseif {$i > $level} {
927 set x [expr {$x + $xspc2 - $xspc1($ln)}]
932 proc drawslants {level} {
933 global canv mainline sidelines canvx0 canvy xspc1 xspc2 lthickness
934 global oldlevel oldtodo todo currentparents dupparents
935 global lthickness linespc canvy colormap lineno geometry
938 # decide on the line spacing for the next line
939 set lj [expr {$lineno + 1}]
940 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
941 set n [llength $todo]
942 if {$n <= 1 || $canvx0 + $n * $xspc2 <= $maxw} {
943 set xspc1($lj) $xspc2
945 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($n - 1)}]
946 if {$xspc1($lj) < $lthickness} {
947 set xspc1($lj) $lthickness
951 set y1 [expr $canvy - $linespc]
954 foreach id $oldtodo {
956 if {$id == {}} continue
957 set xi [xcoord $i $oldlevel $lineno]
958 if {$i == $oldlevel} {
959 foreach p $currentparents {
960 set j [lsearch -exact $todo $p]
961 set coords [list $xi $y1]
962 set xj [xcoord $j $level $lj]
963 if {$xj < $xi - $linespc} {
964 lappend coords [expr {$xj + $linespc}] $y1
965 notecrossings $p $j $i [expr {$j + 1}]
966 } elseif {$xj > $xi + $linespc} {
967 lappend coords [expr {$xj - $linespc}] $y1
968 notecrossings $p $i $j [expr {$j - 1}]
970 if {[lsearch -exact $dupparents $p] >= 0} {
971 # draw a double-width line to indicate the doubled parent
972 lappend coords $xj $y2
973 lappend sidelines($p) [list $coords 2]
974 if {![info exists mainline($p)]} {
975 set mainline($p) [list $xj $y2]
978 # normal case, no parent duplicated
980 set dx [expr {abs($xi - $xj)}]
981 if {0 && $dx < $linespc} {
982 set yb [expr {$y1 + $dx}]
984 if {![info exists mainline($p)]} {
986 lappend coords $xj $yb
988 set mainline($p) $coords
990 lappend coords $xj $yb
992 lappend coords $xj $y2
994 lappend sidelines($p) [list $coords 1]
1000 if {[lindex $todo $i] != $id} {
1001 set j [lsearch -exact $todo $id]
1003 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1004 || ($oldlevel <= $i && $i <= $level)
1005 || ($level <= $i && $i <= $oldlevel)} {
1006 set xj [xcoord $j $level $lj]
1007 set dx [expr {abs($xi - $xj)}]
1009 if {0 && $dx < $linespc} {
1010 set yb [expr {$y1 + $dx}]
1012 lappend mainline($id) $xi $y1 $xj $yb
1018 proc decidenext {{noread 0}} {
1019 global parents children nchildren ncleft todo
1020 global canv canv2 canv3 mainfont namefont canvy linespc
1021 global datemode cdate
1023 global currentparents oldlevel oldnlines oldtodo
1024 global lineno lthickness
1026 # remove the null entry if present
1027 set nullentry [lsearch -exact $todo {}]
1028 if {$nullentry >= 0} {
1029 set todo [lreplace $todo $nullentry $nullentry]
1032 # choose which one to do next time around
1033 set todol [llength $todo]
1036 for {set k $todol} {[incr k -1] >= 0} {} {
1037 set p [lindex $todo $k]
1038 if {$ncleft($p) == 0} {
1040 if {![info exists commitinfo($p)]} {
1046 if {$latest == {} || $cdate($p) > $latest} {
1048 set latest $cdate($p)
1058 puts "ERROR: none of the pending commits can be done yet:"
1060 puts " $p ($ncleft($p))"
1066 # If we are reducing, put in a null entry
1067 if {$todol < $oldnlines} {
1068 if {$nullentry >= 0} {
1071 && [lindex $oldtodo $i] == [lindex $todo $i]} {
1081 set todo [linsert $todo $i {}]
1090 proc drawcommit {id} {
1091 global phase todo nchildren datemode nextupdate
1094 if {$phase != "incrdraw"} {
1097 set startcommits $id
1100 updatetodo 0 $datemode
1102 if {$nchildren($id) == 0} {
1104 lappend startcommits $id
1106 set level [decidenext 1]
1107 if {$level == {} || $id != [lindex $todo $level]} {
1112 drawcommitline $level
1113 if {[updatetodo $level $datemode]} {
1114 set level [decidenext 1]
1115 if {$level == {}} break
1117 set id [lindex $todo $level]
1118 if {![info exists commitlisted($id)]} {
1121 if {[clock clicks -milliseconds] >= $nextupdate} {
1129 proc finishcommits {} {
1132 global canv mainfont ctext maincursor textcursor
1134 if {$phase != "incrdraw"} {
1136 $canv create text 3 3 -anchor nw -text "No commits selected" \
1137 -font $mainfont -tags textitems
1140 set level [decidenext]
1142 drawrest $level [llength $startcommits]
1144 . config -cursor $maincursor
1145 $ctext config -cursor $textcursor
1149 global nextupdate startmsecs startcommits todo
1151 if {$startcommits == {}} return
1152 set startmsecs [clock clicks -milliseconds]
1153 set nextupdate [expr $startmsecs + 100]
1155 set todo [lindex $startcommits 0]
1159 proc drawrest {level startix} {
1160 global phase stopped redisplaying selectedline
1161 global datemode currentparents todo
1163 global nextupdate startmsecs startcommits idline
1167 set startid [lindex $startcommits $startix]
1169 if {$startid != {}} {
1170 set startline $idline($startid)
1174 drawcommitline $level
1175 set hard [updatetodo $level $datemode]
1176 if {$numcommits == $startline} {
1177 lappend todo $startid
1180 set startid [lindex $startcommits $startix]
1182 if {$startid != {}} {
1183 set startline $idline($startid)
1187 set level [decidenext]
1188 if {$level < 0} break
1191 if {[clock clicks -milliseconds] >= $nextupdate} {
1198 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1199 #puts "overall $drawmsecs ms for $numcommits commits"
1200 if {$redisplaying} {
1201 if {$stopped == 0 && [info exists selectedline]} {
1202 selectline $selectedline 0
1204 if {$stopped == 1} {
1206 after idle drawgraph
1213 proc findmatches {f} {
1214 global findtype foundstring foundstrlen
1215 if {$findtype == "Regexp"} {
1216 set matches [regexp -indices -all -inline $foundstring $f]
1218 if {$findtype == "IgnCase"} {
1219 set str [string tolower $f]
1225 while {[set j [string first $foundstring $str $i]] >= 0} {
1226 lappend matches [list $j [expr $j+$foundstrlen-1]]
1227 set i [expr $j + $foundstrlen]
1234 global findtype findloc findstring markedmatches commitinfo
1235 global numcommits lineid linehtag linentag linedtag
1236 global mainfont namefont canv canv2 canv3 selectedline
1237 global matchinglines foundstring foundstrlen
1242 set matchinglines {}
1243 if {$findloc == "Pickaxe"} {
1247 if {$findtype == "IgnCase"} {
1248 set foundstring [string tolower $findstring]
1250 set foundstring $findstring
1252 set foundstrlen [string length $findstring]
1253 if {$foundstrlen == 0} return
1254 if {$findloc == "Files"} {
1258 if {![info exists selectedline]} {
1261 set oldsel $selectedline
1264 set fldtypes {Headline Author Date Committer CDate Comment}
1265 for {set l 0} {$l < $numcommits} {incr l} {
1267 set info $commitinfo($id)
1269 foreach f $info ty $fldtypes {
1270 if {$findloc != "All fields" && $findloc != $ty} {
1273 set matches [findmatches $f]
1274 if {$matches == {}} continue
1276 if {$ty == "Headline"} {
1277 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1278 } elseif {$ty == "Author"} {
1279 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1280 } elseif {$ty == "Date"} {
1281 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1285 lappend matchinglines $l
1286 if {!$didsel && $l > $oldsel} {
1292 if {$matchinglines == {}} {
1294 } elseif {!$didsel} {
1295 findselectline [lindex $matchinglines 0]
1299 proc findselectline {l} {
1300 global findloc commentend ctext
1302 if {$findloc == "All fields" || $findloc == "Comments"} {
1303 # highlight the matches in the comments
1304 set f [$ctext get 1.0 $commentend]
1305 set matches [findmatches $f]
1306 foreach match $matches {
1307 set start [lindex $match 0]
1308 set end [expr [lindex $match 1] + 1]
1309 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1314 proc findnext {restart} {
1315 global matchinglines selectedline
1316 if {![info exists matchinglines]} {
1322 if {![info exists selectedline]} return
1323 foreach l $matchinglines {
1324 if {$l > $selectedline} {
1333 global matchinglines selectedline
1334 if {![info exists matchinglines]} {
1338 if {![info exists selectedline]} return
1340 foreach l $matchinglines {
1341 if {$l >= $selectedline} break
1345 findselectline $prev
1351 proc findlocchange {name ix op} {
1352 global findloc findtype findtypemenu
1353 if {$findloc == "Pickaxe"} {
1359 $findtypemenu entryconf 1 -state $state
1360 $findtypemenu entryconf 2 -state $state
1363 proc stopfindproc {{done 0}} {
1364 global findprocpid findprocfile findids
1365 global ctext findoldcursor phase maincursor textcursor
1366 global findinprogress
1368 catch {unset findids}
1369 if {[info exists findprocpid]} {
1371 catch {exec kill $findprocpid}
1373 catch {close $findprocfile}
1376 if {[info exists findinprogress]} {
1377 unset findinprogress
1378 if {$phase != "incrdraw"} {
1379 . config -cursor $maincursor
1380 $ctext config -cursor $textcursor
1385 proc findpatches {} {
1386 global findstring selectedline numcommits
1387 global findprocpid findprocfile
1388 global finddidsel ctext lineid findinprogress
1389 global findinsertpos
1391 if {$numcommits == 0} return
1393 # make a list of all the ids to search, starting at the one
1394 # after the selected line (if any)
1395 if {[info exists selectedline]} {
1401 for {set i 0} {$i < $numcommits} {incr i} {
1402 if {[incr l] >= $numcommits} {
1405 append inputids $lineid($l) "\n"
1409 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1412 error_popup "Error starting search process: $err"
1416 set findinsertpos end
1418 set findprocpid [pid $f]
1419 fconfigure $f -blocking 0
1420 fileevent $f readable readfindproc
1422 . config -cursor watch
1423 $ctext config -cursor watch
1424 set findinprogress 1
1427 proc readfindproc {} {
1428 global findprocfile finddidsel
1429 global idline matchinglines findinsertpos
1431 set n [gets $findprocfile line]
1433 if {[eof $findprocfile]} {
1441 if {![regexp {^[0-9a-f]{40}} $line id]} {
1442 error_popup "Can't parse git-diff-tree output: $line"
1446 if {![info exists idline($id)]} {
1447 puts stderr "spurious id: $id"
1454 proc insertmatch {l id} {
1455 global matchinglines findinsertpos finddidsel
1457 if {$findinsertpos == "end"} {
1458 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1459 set matchinglines [linsert $matchinglines 0 $l]
1462 lappend matchinglines $l
1465 set matchinglines [linsert $matchinglines $findinsertpos $l]
1476 global selectedline numcommits lineid ctext
1477 global ffileline finddidsel parents nparents
1478 global findinprogress findstartline findinsertpos
1479 global treediffs fdiffids fdiffsneeded fdiffpos
1480 global findmergefiles
1482 if {$numcommits == 0} return
1484 if {[info exists selectedline]} {
1485 set l [expr {$selectedline + 1}]
1490 set findstartline $l
1495 if {$findmergefiles || $nparents($id) == 1} {
1496 foreach p $parents($id) {
1497 if {![info exists treediffs([list $id $p])]} {
1498 append diffsneeded "$id $p\n"
1499 lappend fdiffsneeded [list $id $p]
1503 if {[incr l] >= $numcommits} {
1506 if {$l == $findstartline} break
1509 # start off a git-diff-tree process if needed
1510 if {$diffsneeded ne {}} {
1512 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1514 error_popup "Error starting search process: $err"
1517 catch {unset fdiffids}
1519 fconfigure $df -blocking 0
1520 fileevent $df readable [list readfilediffs $df]
1524 set findinsertpos end
1526 set p [lindex $parents($id) 0]
1527 . config -cursor watch
1528 $ctext config -cursor watch
1529 set findinprogress 1
1530 findcont [list $id $p]
1534 proc readfilediffs {df} {
1535 global findids fdiffids fdiffs
1537 set n [gets $df line]
1541 if {[catch {close $df} err]} {
1544 error_popup "Error in git-diff-tree: $err"
1545 } elseif {[info exists findids]} {
1549 error_popup "Couldn't find diffs for {$ids}"
1554 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1555 # start of a new string of diffs
1557 set fdiffids [list $id $p]
1559 } elseif {[string match ":*" $line]} {
1560 lappend fdiffs [lindex $line 5]
1564 proc donefilediff {} {
1565 global fdiffids fdiffs treediffs findids
1566 global fdiffsneeded fdiffpos
1568 if {[info exists fdiffids]} {
1569 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1570 && $fdiffpos < [llength $fdiffsneeded]} {
1571 # git-diff-tree doesn't output anything for a commit
1572 # which doesn't change anything
1573 set nullids [lindex $fdiffsneeded $fdiffpos]
1574 set treediffs($nullids) {}
1575 if {[info exists findids] && $nullids eq $findids} {
1583 if {![info exists treediffs($fdiffids)]} {
1584 set treediffs($fdiffids) $fdiffs
1586 if {[info exists findids] && $fdiffids eq $findids} {
1593 proc findcont {ids} {
1594 global findids treediffs parents nparents
1595 global ffileline findstartline finddidsel
1596 global lineid numcommits matchinglines findinprogress
1597 global findmergefiles
1599 set id [lindex $ids 0]
1600 set p [lindex $ids 1]
1601 set pi [lsearch -exact $parents($id) $p]
1604 if {$findmergefiles || $nparents($id) == 1} {
1605 if {![info exists treediffs($ids)]} {
1611 foreach f $treediffs($ids) {
1612 set x [findmatches $f]
1620 set pi $nparents($id)
1623 set pi $nparents($id)
1625 if {[incr pi] >= $nparents($id)} {
1627 if {[incr l] >= $numcommits} {
1630 if {$l == $findstartline} break
1633 set p [lindex $parents($id) $pi]
1634 set ids [list $id $p]
1642 # mark a commit as matching by putting a yellow background
1643 # behind the headline
1644 proc markheadline {l id} {
1645 global canv mainfont linehtag commitinfo
1647 set bbox [$canv bbox $linehtag($l)]
1648 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1652 # mark the bits of a headline, author or date that match a find string
1653 proc markmatches {canv l str tag matches font} {
1654 set bbox [$canv bbox $tag]
1655 set x0 [lindex $bbox 0]
1656 set y0 [lindex $bbox 1]
1657 set y1 [lindex $bbox 3]
1658 foreach match $matches {
1659 set start [lindex $match 0]
1660 set end [lindex $match 1]
1661 if {$start > $end} continue
1662 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1663 set xlen [font measure $font [string range $str 0 [expr $end]]]
1664 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1665 -outline {} -tags matches -fill yellow]
1670 proc unmarkmatches {} {
1671 global matchinglines findids
1672 allcanvs delete matches
1673 catch {unset matchinglines}
1674 catch {unset findids}
1677 proc selcanvline {w x y} {
1678 global canv canvy0 ctext linespc selectedline
1679 global lineid linehtag linentag linedtag rowtextx
1680 set ymax [lindex [$canv cget -scrollregion] 3]
1681 if {$ymax == {}} return
1682 set yfrac [lindex [$canv yview] 0]
1683 set y [expr {$y + $yfrac * $ymax}]
1684 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1689 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1695 proc selectline {l isnew} {
1696 global canv canv2 canv3 ctext commitinfo selectedline
1697 global lineid linehtag linentag linedtag
1698 global canvy0 linespc parents nparents
1699 global cflist currentid sha1entry
1700 global commentend idtags idline
1701 global history historyindex
1704 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1706 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1707 -tags secsel -fill [$canv cget -selectbackground]]
1709 $canv2 delete secsel
1710 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1711 -tags secsel -fill [$canv2 cget -selectbackground]]
1713 $canv3 delete secsel
1714 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1715 -tags secsel -fill [$canv3 cget -selectbackground]]
1717 set y [expr {$canvy0 + $l * $linespc}]
1718 set ymax [lindex [$canv cget -scrollregion] 3]
1719 set ytop [expr {$y - $linespc - 1}]
1720 set ybot [expr {$y + $linespc + 1}]
1721 set wnow [$canv yview]
1722 set wtop [expr [lindex $wnow 0] * $ymax]
1723 set wbot [expr [lindex $wnow 1] * $ymax]
1724 set wh [expr {$wbot - $wtop}]
1726 if {$ytop < $wtop} {
1727 if {$ybot < $wtop} {
1728 set newtop [expr {$y - $wh / 2.0}]
1731 if {$newtop > $wtop - $linespc} {
1732 set newtop [expr {$wtop - $linespc}]
1735 } elseif {$ybot > $wbot} {
1736 if {$ytop > $wbot} {
1737 set newtop [expr {$y - $wh / 2.0}]
1739 set newtop [expr {$ybot - $wh}]
1740 if {$newtop < $wtop + $linespc} {
1741 set newtop [expr {$wtop + $linespc}]
1745 if {$newtop != $wtop} {
1749 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1752 if {$isnew && (![info exists selectedline] || $selectedline != $l)} {
1753 if {$historyindex < [llength $history]} {
1754 set history [lreplace $history $historyindex end $l]
1759 if {$historyindex > 1} {
1760 .ctop.top.bar.leftbut conf -state normal
1762 .ctop.top.bar.leftbut conf -state disabled
1764 .ctop.top.bar.rightbut conf -state disabled
1771 $sha1entry delete 0 end
1772 $sha1entry insert 0 $id
1773 $sha1entry selection from 0
1774 $sha1entry selection to end
1776 $ctext conf -state normal
1777 $ctext delete 0.0 end
1778 $ctext mark set fmark.0 0.0
1779 $ctext mark gravity fmark.0 left
1780 set info $commitinfo($id)
1781 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1782 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1783 if {[info exists idtags($id)]} {
1784 $ctext insert end "Tags:"
1785 foreach tag $idtags($id) {
1786 $ctext insert end " $tag"
1788 $ctext insert end "\n"
1790 $ctext insert end "\n"
1791 set commentstart [$ctext index "end - 1c"]
1792 set comment [lindex $info 5]
1793 $ctext insert end $comment
1794 $ctext insert end "\n"
1796 # make anything that looks like a SHA1 ID be a clickable link
1797 set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
1802 set linkid [string range $comment $s $e]
1803 if {![info exists idline($linkid)]} continue
1805 $ctext tag conf link$i -foreground blue -underline 1
1806 $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
1807 $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
1810 $ctext tag delete Comments
1811 $ctext tag remove found 1.0 end
1812 $ctext conf -state disabled
1813 set commentend [$ctext index "end - 1c"]
1815 $cflist delete 0 end
1816 $cflist insert end "Comments"
1817 if {$nparents($id) == 1} {
1818 startdiff [concat $id $parents($id)]
1819 } elseif {$nparents($id) > 1} {
1824 proc selnextline {dir} {
1826 if {![info exists selectedline]} return
1827 set l [expr $selectedline + $dir]
1833 global history historyindex
1835 if {$historyindex > 1} {
1836 incr historyindex -1
1837 selectline [lindex $history [expr {$historyindex - 1}]] 0
1838 .ctop.top.bar.rightbut conf -state normal
1840 if {$historyindex <= 1} {
1841 .ctop.top.bar.leftbut conf -state disabled
1846 global history historyindex
1848 if {$historyindex < [llength $history]} {
1849 set l [lindex $history $historyindex]
1852 .ctop.top.bar.leftbut conf -state normal
1854 if {$historyindex >= [llength $history]} {
1855 .ctop.top.bar.rightbut conf -state disabled
1859 proc mergediff {id} {
1860 global parents diffmergeid diffmergegca mergefilelist diffpindex
1864 set diffmergegca [findgca $parents($id)]
1865 if {[info exists mergefilelist($id)]} {
1866 if {$mergefilelist($id) ne {}} {
1874 proc findgca {ids} {
1881 set gca [exec git-merge-base $gca $id]
1890 proc contmergediff {ids} {
1891 global diffmergeid diffpindex parents nparents diffmergegca
1892 global treediffs mergefilelist diffids treepending
1894 # diff the child against each of the parents, and diff
1895 # each of the parents against the GCA.
1897 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1898 set ids [list [lindex $ids 1] $diffmergegca]
1900 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1901 set p [lindex $parents($diffmergeid) $diffpindex]
1902 set ids [list $diffmergeid $p]
1904 if {![info exists treediffs($ids)]} {
1906 if {![info exists treepending]} {
1913 # If a file in some parent is different from the child and also
1914 # different from the GCA, then it's interesting.
1915 # If we don't have a GCA, then a file is interesting if it is
1916 # different from the child in all the parents.
1917 if {$diffmergegca ne {}} {
1919 foreach p $parents($diffmergeid) {
1920 set gcadiffs $treediffs([list $p $diffmergegca])
1921 foreach f $treediffs([list $diffmergeid $p]) {
1922 if {[lsearch -exact $files $f] < 0
1923 && [lsearch -exact $gcadiffs $f] >= 0} {
1928 set files [lsort $files]
1930 set p [lindex $parents($diffmergeid) 0]
1931 set files $treediffs([list $diffmergeid $p])
1932 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1933 set p [lindex $parents($diffmergeid) $i]
1934 set df $treediffs([list $diffmergeid $p])
1937 if {[lsearch -exact $df $f] >= 0} {
1945 set mergefilelist($diffmergeid) $files
1951 proc showmergediff {} {
1952 global cflist diffmergeid mergefilelist parents
1953 global diffopts diffinhunk currentfile currenthunk filelines
1954 global diffblocked groupfilelast mergefds groupfilenum grouphunks
1956 set files $mergefilelist($diffmergeid)
1958 $cflist insert end $f
1960 set env(GIT_DIFF_OPTS) $diffopts
1962 catch {unset currentfile}
1963 catch {unset currenthunk}
1964 catch {unset filelines}
1965 catch {unset groupfilenum}
1966 catch {unset grouphunks}
1967 set groupfilelast -1
1968 foreach p $parents($diffmergeid) {
1969 set cmd [list | git-diff-tree -p $p $diffmergeid]
1970 set cmd [concat $cmd $mergefilelist($diffmergeid)]
1971 if {[catch {set f [open $cmd r]} err]} {
1972 error_popup "Error getting diffs: $err"
1979 set ids [list $diffmergeid $p]
1980 set mergefds($ids) $f
1981 set diffinhunk($ids) 0
1982 set diffblocked($ids) 0
1983 fconfigure $f -blocking 0
1984 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
1988 proc getmergediffline {f ids id} {
1989 global diffmergeid diffinhunk diffoldlines diffnewlines
1990 global currentfile currenthunk
1991 global diffoldstart diffnewstart diffoldlno diffnewlno
1992 global diffblocked mergefilelist
1993 global noldlines nnewlines difflcounts filelines
1995 set n [gets $f line]
1997 if {![eof $f]} return
2000 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2007 if {$diffinhunk($ids) != 0} {
2008 set fi $currentfile($ids)
2009 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2010 # continuing an existing hunk
2011 set line [string range $line 1 end]
2012 set p [lindex $ids 1]
2013 if {$match eq "-" || $match eq " "} {
2014 set filelines($p,$fi,$diffoldlno($ids)) $line
2015 incr diffoldlno($ids)
2017 if {$match eq "+" || $match eq " "} {
2018 set filelines($id,$fi,$diffnewlno($ids)) $line
2019 incr diffnewlno($ids)
2021 if {$match eq " "} {
2022 if {$diffinhunk($ids) == 2} {
2023 lappend difflcounts($ids) \
2024 [list $noldlines($ids) $nnewlines($ids)]
2025 set noldlines($ids) 0
2026 set diffinhunk($ids) 1
2028 incr noldlines($ids)
2029 } elseif {$match eq "-" || $match eq "+"} {
2030 if {$diffinhunk($ids) == 1} {
2031 lappend difflcounts($ids) [list $noldlines($ids)]
2032 set noldlines($ids) 0
2033 set nnewlines($ids) 0
2034 set diffinhunk($ids) 2
2036 if {$match eq "-"} {
2037 incr noldlines($ids)
2039 incr nnewlines($ids)
2042 # and if it's \ No newline at end of line, then what?
2046 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2047 lappend difflcounts($ids) [list $noldlines($ids)]
2048 } elseif {$diffinhunk($ids) == 2
2049 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2050 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2052 set currenthunk($ids) [list $currentfile($ids) \
2053 $diffoldstart($ids) $diffnewstart($ids) \
2054 $diffoldlno($ids) $diffnewlno($ids) \
2056 set diffinhunk($ids) 0
2057 # -1 = need to block, 0 = unblocked, 1 = is blocked
2058 set diffblocked($ids) -1
2060 if {$diffblocked($ids) == -1} {
2061 fileevent $f readable {}
2062 set diffblocked($ids) 1
2068 if {!$diffblocked($ids)} {
2070 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2071 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2074 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2075 # start of a new file
2076 set currentfile($ids) \
2077 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2078 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2079 $line match f1l f1c f2l f2c rest]} {
2080 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2081 # start of a new hunk
2082 if {$f1l == 0 && $f1c == 0} {
2085 if {$f2l == 0 && $f2c == 0} {
2088 set diffinhunk($ids) 1
2089 set diffoldstart($ids) $f1l
2090 set diffnewstart($ids) $f2l
2091 set diffoldlno($ids) $f1l
2092 set diffnewlno($ids) $f2l
2093 set difflcounts($ids) {}
2094 set noldlines($ids) 0
2095 set nnewlines($ids) 0
2100 proc processhunks {} {
2101 global diffmergeid parents nparents currenthunk
2102 global mergefilelist diffblocked mergefds
2103 global grouphunks grouplinestart grouplineend groupfilenum
2105 set nfiles [llength $mergefilelist($diffmergeid)]
2109 # look for the earliest hunk
2110 foreach p $parents($diffmergeid) {
2111 set ids [list $diffmergeid $p]
2112 if {![info exists currenthunk($ids)]} return
2113 set i [lindex $currenthunk($ids) 0]
2114 set l [lindex $currenthunk($ids) 2]
2115 if {$i < $fi || ($i == $fi && $l < $lno)} {
2122 if {$fi < $nfiles} {
2123 set ids [list $diffmergeid $pi]
2124 set hunk $currenthunk($ids)
2125 unset currenthunk($ids)
2126 if {$diffblocked($ids) > 0} {
2127 fileevent $mergefds($ids) readable \
2128 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2130 set diffblocked($ids) 0
2132 if {[info exists groupfilenum] && $groupfilenum == $fi
2133 && $lno <= $grouplineend} {
2134 # add this hunk to the pending group
2135 lappend grouphunks($pi) $hunk
2136 set endln [lindex $hunk 4]
2137 if {$endln > $grouplineend} {
2138 set grouplineend $endln
2144 # succeeding stuff doesn't belong in this group, so
2145 # process the group now
2146 if {[info exists groupfilenum]} {
2152 if {$fi >= $nfiles} break
2155 set groupfilenum $fi
2156 set grouphunks($pi) [list $hunk]
2157 set grouplinestart $lno
2158 set grouplineend [lindex $hunk 4]
2162 proc processgroup {} {
2163 global groupfilelast groupfilenum difffilestart
2164 global mergefilelist diffmergeid ctext filelines
2165 global parents diffmergeid diffoffset
2166 global grouphunks grouplinestart grouplineend nparents
2169 $ctext conf -state normal
2172 if {$groupfilelast != $f} {
2173 $ctext insert end "\n"
2174 set here [$ctext index "end - 1c"]
2175 set difffilestart($f) $here
2176 set mark fmark.[expr {$f + 1}]
2177 $ctext mark set $mark $here
2178 $ctext mark gravity $mark left
2179 set header [lindex $mergefilelist($id) $f]
2180 set l [expr {(78 - [string length $header]) / 2}]
2181 set pad [string range "----------------------------------------" 1 $l]
2182 $ctext insert end "$pad $header $pad\n" filesep
2183 set groupfilelast $f
2184 foreach p $parents($id) {
2185 set diffoffset($p) 0
2189 $ctext insert end "@@" msep
2190 set nlines [expr {$grouplineend - $grouplinestart}]
2193 foreach p $parents($id) {
2194 set startline [expr {$grouplinestart + $diffoffset($p)}]
2196 set nl $grouplinestart
2197 if {[info exists grouphunks($p)]} {
2198 foreach h $grouphunks($p) {
2201 for {} {$nl < $l} {incr nl} {
2202 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2206 foreach chunk [lindex $h 5] {
2207 if {[llength $chunk] == 2} {
2208 set olc [lindex $chunk 0]
2209 set nlc [lindex $chunk 1]
2210 set nnl [expr {$nl + $nlc}]
2211 lappend events [list $nl $nnl $pnum $olc $nlc]
2215 incr ol [lindex $chunk 0]
2216 incr nl [lindex $chunk 0]
2221 if {$nl < $grouplineend} {
2222 for {} {$nl < $grouplineend} {incr nl} {
2223 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2227 set nlines [expr {$ol - $startline}]
2228 $ctext insert end " -$startline,$nlines" msep
2232 set nlines [expr {$grouplineend - $grouplinestart}]
2233 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2235 set events [lsort -integer -index 0 $events]
2236 set nevents [llength $events]
2237 set nmerge $nparents($diffmergeid)
2238 set l $grouplinestart
2239 for {set i 0} {$i < $nevents} {set i $j} {
2240 set nl [lindex $events $i 0]
2242 $ctext insert end " $filelines($id,$f,$l)\n"
2245 set e [lindex $events $i]
2246 set enl [lindex $e 1]
2250 set pnum [lindex $e 2]
2251 set olc [lindex $e 3]
2252 set nlc [lindex $e 4]
2253 if {![info exists delta($pnum)]} {
2254 set delta($pnum) [expr {$olc - $nlc}]
2255 lappend active $pnum
2257 incr delta($pnum) [expr {$olc - $nlc}]
2259 if {[incr j] >= $nevents} break
2260 set e [lindex $events $j]
2261 if {[lindex $e 0] >= $enl} break
2262 if {[lindex $e 1] > $enl} {
2263 set enl [lindex $e 1]
2266 set nlc [expr {$enl - $l}]
2269 if {[llength $active] == $nmerge - 1} {
2270 # no diff for one of the parents, i.e. it's identical
2271 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2272 if {![info exists delta($pnum)]} {
2273 if {$pnum < $mergemax} {
2281 } elseif {[llength $active] == $nmerge} {
2282 # all parents are different, see if one is very similar
2284 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2285 set sim [similarity $pnum $l $nlc $f \
2286 [lrange $events $i [expr {$j-1}]]]
2287 if {$sim > $bestsim} {
2293 lappend ncol m$bestpn
2297 foreach p $parents($id) {
2299 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2300 set olc [expr {$nlc + $delta($pnum)}]
2301 set ol [expr {$l + $diffoffset($p)}]
2302 incr diffoffset($p) $delta($pnum)
2304 for {} {$olc > 0} {incr olc -1} {
2305 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2309 set endl [expr {$l + $nlc}]
2311 # show this pretty much as a normal diff
2312 set p [lindex $parents($id) $bestpn]
2313 set ol [expr {$l + $diffoffset($p)}]
2314 incr diffoffset($p) $delta($bestpn)
2315 unset delta($bestpn)
2316 for {set k $i} {$k < $j} {incr k} {
2317 set e [lindex $events $k]
2318 if {[lindex $e 2] != $bestpn} continue
2319 set nl [lindex $e 0]
2320 set ol [expr {$ol + $nl - $l}]
2321 for {} {$l < $nl} {incr l} {
2322 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2325 for {} {$c > 0} {incr c -1} {
2326 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2329 set nl [lindex $e 1]
2330 for {} {$l < $nl} {incr l} {
2331 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2335 for {} {$l < $endl} {incr l} {
2336 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2339 while {$l < $grouplineend} {
2340 $ctext insert end " $filelines($id,$f,$l)\n"
2343 $ctext conf -state disabled
2346 proc similarity {pnum l nlc f events} {
2347 global diffmergeid parents diffoffset filelines
2350 set p [lindex $parents($id) $pnum]
2351 set ol [expr {$l + $diffoffset($p)}]
2352 set endl [expr {$l + $nlc}]
2356 if {[lindex $e 2] != $pnum} continue
2357 set nl [lindex $e 0]
2358 set ol [expr {$ol + $nl - $l}]
2359 for {} {$l < $nl} {incr l} {
2360 incr same [string length $filelines($id,$f,$l)]
2363 set oc [lindex $e 3]
2364 for {} {$oc > 0} {incr oc -1} {
2365 incr diff [string length $filelines($p,$f,$ol)]
2369 set nl [lindex $e 1]
2370 for {} {$l < $nl} {incr l} {
2371 incr diff [string length $filelines($id,$f,$l)]
2375 for {} {$l < $endl} {incr l} {
2376 incr same [string length $filelines($id,$f,$l)]
2382 return [expr {200 * $same / (2 * $same + $diff)}]
2385 proc startdiff {ids} {
2386 global treediffs diffids treepending diffmergeid
2389 catch {unset diffmergeid}
2390 if {![info exists treediffs($ids)]} {
2391 if {![info exists treepending]} {
2399 proc addtocflist {ids} {
2400 global treediffs cflist
2401 foreach f $treediffs($ids) {
2402 $cflist insert end $f
2407 proc gettreediffs {ids} {
2408 global treediff parents treepending
2409 set treepending $ids
2411 set id [lindex $ids 0]
2412 set p [lindex $ids 1]
2413 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2414 fconfigure $gdtf -blocking 0
2415 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2418 proc gettreediffline {gdtf ids} {
2419 global treediff treediffs treepending diffids diffmergeid
2421 set n [gets $gdtf line]
2423 if {![eof $gdtf]} return
2425 set treediffs($ids) $treediff
2427 if {$ids != $diffids} {
2428 gettreediffs $diffids
2430 if {[info exists diffmergeid]} {
2438 set file [lindex $line 5]
2439 lappend treediff $file
2442 proc getblobdiffs {ids} {
2443 global diffopts blobdifffd diffids env curdifftag curtagstart
2444 global difffilestart nextupdate diffinhdr treediffs
2446 set id [lindex $ids 0]
2447 set p [lindex $ids 1]
2448 set env(GIT_DIFF_OPTS) $diffopts
2449 set cmd [list | git-diff-tree -r -p -C $p $id]
2450 if {[catch {set bdf [open $cmd r]} err]} {
2451 puts "error getting diffs: $err"
2455 fconfigure $bdf -blocking 0
2456 set blobdifffd($ids) $bdf
2457 set curdifftag Comments
2459 catch {unset difffilestart}
2460 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2461 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2464 proc getblobdiffline {bdf ids} {
2465 global diffids blobdifffd ctext curdifftag curtagstart
2466 global diffnexthead diffnextnote difffilestart
2467 global nextupdate diffinhdr treediffs
2470 set n [gets $bdf line]
2474 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2475 $ctext tag add $curdifftag $curtagstart end
2480 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2483 $ctext conf -state normal
2484 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2485 # start of a new file
2486 $ctext insert end "\n"
2487 $ctext tag add $curdifftag $curtagstart end
2488 set curtagstart [$ctext index "end - 1c"]
2490 set here [$ctext index "end - 1c"]
2491 set i [lsearch -exact $treediffs($diffids) $fname]
2493 set difffilestart($i) $here
2495 $ctext mark set fmark.$i $here
2496 $ctext mark gravity fmark.$i left
2498 if {$newname != $fname} {
2499 set i [lsearch -exact $treediffs($diffids) $newname]
2501 set difffilestart($i) $here
2503 $ctext mark set fmark.$i $here
2504 $ctext mark gravity fmark.$i left
2507 set curdifftag "f:$fname"
2508 $ctext tag delete $curdifftag
2509 set l [expr {(78 - [string length $header]) / 2}]
2510 set pad [string range "----------------------------------------" 1 $l]
2511 $ctext insert end "$pad $header $pad\n" filesep
2513 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2515 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2516 $line match f1l f1c f2l f2c rest]} {
2518 $ctext insert end "\t" hunksep
2519 $ctext insert end " $f1l " d0 " $f2l " d1
2520 $ctext insert end " $rest \n" hunksep
2522 $ctext insert end "$line\n" hunksep
2526 set x [string range $line 0 0]
2527 if {$x == "-" || $x == "+"} {
2528 set tag [expr {$x == "+"}]
2530 set line [string range $line 1 end]
2532 $ctext insert end "$line\n" d$tag
2533 } elseif {$x == " "} {
2535 set line [string range $line 1 end]
2537 $ctext insert end "$line\n"
2538 } elseif {$diffinhdr || $x == "\\"} {
2539 # e.g. "\ No newline at end of file"
2540 $ctext insert end "$line\n" filesep
2542 # Something else we don't recognize
2543 if {$curdifftag != "Comments"} {
2544 $ctext insert end "\n"
2545 $ctext tag add $curdifftag $curtagstart end
2546 set curtagstart [$ctext index "end - 1c"]
2547 set curdifftag Comments
2549 $ctext insert end "$line\n" filesep
2552 $ctext conf -state disabled
2553 if {[clock clicks -milliseconds] >= $nextupdate} {
2555 fileevent $bdf readable {}
2557 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2562 global difffilestart ctext
2563 set here [$ctext index @0,0]
2564 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2565 if {[$ctext compare $difffilestart($i) > $here]} {
2566 if {![info exists pos]
2567 || [$ctext compare $difffilestart($i) < $pos]} {
2568 set pos $difffilestart($i)
2572 if {[info exists pos]} {
2577 proc listboxsel {} {
2578 global ctext cflist currentid
2579 if {![info exists currentid]} return
2580 set sel [lsort [$cflist curselection]]
2581 if {$sel eq {}} return
2582 set first [lindex $sel 0]
2583 catch {$ctext yview fmark.$first}
2587 global linespc charspc canvx0 canvy0 mainfont
2590 set linespc [font metrics $mainfont -linespace]
2591 set charspc [font measure $mainfont "m"]
2592 set canvy0 [expr 3 + 0.5 * $linespc]
2593 set canvx0 [expr 3 + 0.5 * $linespc]
2594 set xspc1(0) $linespc
2599 global selectedline stopped redisplaying phase
2600 if {$stopped > 1} return
2601 if {$phase == "getcommits"} return
2603 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2610 proc incrfont {inc} {
2611 global mainfont namefont textfont selectedline ctext canv phase
2612 global stopped entries
2614 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2615 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2616 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2618 $ctext conf -font $textfont
2619 $ctext tag conf filesep -font [concat $textfont bold]
2620 foreach e $entries {
2621 $e conf -font $mainfont
2623 if {$phase == "getcommits"} {
2624 $canv itemconf textitems -font $mainfont
2630 global sha1entry sha1string
2631 if {[string length $sha1string] == 40} {
2632 $sha1entry delete 0 end
2636 proc sha1change {n1 n2 op} {
2637 global sha1string currentid sha1but
2638 if {$sha1string == {}
2639 || ([info exists currentid] && $sha1string == $currentid)} {
2644 if {[$sha1but cget -state] == $state} return
2645 if {$state == "normal"} {
2646 $sha1but conf -state normal -relief raised -text "Goto: "
2648 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2652 proc gotocommit {} {
2653 global sha1string currentid idline tagids
2654 global lineid numcommits
2656 if {$sha1string == {}
2657 || ([info exists currentid] && $sha1string == $currentid)} return
2658 if {[info exists tagids($sha1string)]} {
2659 set id $tagids($sha1string)
2661 set id [string tolower $sha1string]
2662 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2664 for {set l 0} {$l < $numcommits} {incr l} {
2665 if {[string match $id* $lineid($l)]} {
2666 lappend matches $lineid($l)
2669 if {$matches ne {}} {
2670 if {[llength $matches] > 1} {
2671 error_popup "Short SHA1 id $id is ambiguous"
2674 set id [lindex $matches 0]
2678 if {[info exists idline($id)]} {
2679 selectline $idline($id) 1
2682 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2687 error_popup "$type $sha1string is not known"
2690 proc lineenter {x y id} {
2691 global hoverx hovery hoverid hovertimer
2692 global commitinfo canv
2694 if {![info exists commitinfo($id)]} return
2698 if {[info exists hovertimer]} {
2699 after cancel $hovertimer
2701 set hovertimer [after 500 linehover]
2705 proc linemotion {x y id} {
2706 global hoverx hovery hoverid hovertimer
2708 if {[info exists hoverid] && $id == $hoverid} {
2711 if {[info exists hovertimer]} {
2712 after cancel $hovertimer
2714 set hovertimer [after 500 linehover]
2718 proc lineleave {id} {
2719 global hoverid hovertimer canv
2721 if {[info exists hoverid] && $id == $hoverid} {
2723 if {[info exists hovertimer]} {
2724 after cancel $hovertimer
2732 global hoverx hovery hoverid hovertimer
2733 global canv linespc lthickness
2734 global commitinfo mainfont
2736 set text [lindex $commitinfo($hoverid) 0]
2737 set ymax [lindex [$canv cget -scrollregion] 3]
2738 if {$ymax == {}} return
2739 set yfrac [lindex [$canv yview] 0]
2740 set x [expr {$hoverx + 2 * $linespc}]
2741 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2742 set x0 [expr {$x - 2 * $lthickness}]
2743 set y0 [expr {$y - 2 * $lthickness}]
2744 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2745 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2746 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2747 -fill \#ffff80 -outline black -width 1 -tags hover]
2749 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2753 proc lineclick {x y id} {
2754 global ctext commitinfo children cflist canv
2758 # fill the details pane with info about this line
2759 $ctext conf -state normal
2760 $ctext delete 0.0 end
2761 $ctext insert end "Parent:\n "
2762 catch {destroy $ctext.$id}
2763 button $ctext.$id -text "Go:" -command "selbyid $id" \
2765 $ctext window create end -window $ctext.$id -align center
2766 set info $commitinfo($id)
2767 $ctext insert end "\t[lindex $info 0]\n"
2768 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2769 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2770 $ctext insert end "\tID:\t$id\n"
2771 if {[info exists children($id)]} {
2772 $ctext insert end "\nChildren:"
2773 foreach child $children($id) {
2774 $ctext insert end "\n "
2775 catch {destroy $ctext.$child}
2776 button $ctext.$child -text "Go:" -command "selbyid $child" \
2778 $ctext window create end -window $ctext.$child -align center
2779 set info $commitinfo($child)
2780 $ctext insert end "\t[lindex $info 0]"
2783 $ctext conf -state disabled
2785 $cflist delete 0 end
2790 if {[info exists idline($id)]} {
2791 selectline $idline($id) 1
2797 if {![info exists startmstime]} {
2798 set startmstime [clock clicks -milliseconds]
2800 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2803 proc rowmenu {x y id} {
2804 global rowctxmenu idline selectedline rowmenuid
2806 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2811 $rowctxmenu entryconfigure 0 -state $state
2812 $rowctxmenu entryconfigure 1 -state $state
2813 $rowctxmenu entryconfigure 2 -state $state
2815 tk_popup $rowctxmenu $x $y
2818 proc diffvssel {dirn} {
2819 global rowmenuid selectedline lineid
2823 if {![info exists selectedline]} return
2825 set oldid $lineid($selectedline)
2826 set newid $rowmenuid
2828 set oldid $rowmenuid
2829 set newid $lineid($selectedline)
2831 $ctext conf -state normal
2832 $ctext delete 0.0 end
2833 $ctext mark set fmark.0 0.0
2834 $ctext mark gravity fmark.0 left
2835 $cflist delete 0 end
2836 $cflist insert end "Top"
2837 $ctext insert end "From $oldid\n "
2838 $ctext insert end [lindex $commitinfo($oldid) 0]
2839 $ctext insert end "\n\nTo $newid\n "
2840 $ctext insert end [lindex $commitinfo($newid) 0]
2841 $ctext insert end "\n"
2842 $ctext conf -state disabled
2843 $ctext tag delete Comments
2844 $ctext tag remove found 1.0 end
2845 startdiff [list $newid $oldid]
2849 global rowmenuid currentid commitinfo patchtop patchnum
2851 if {![info exists currentid]} return
2852 set oldid $currentid
2853 set oldhead [lindex $commitinfo($oldid) 0]
2854 set newid $rowmenuid
2855 set newhead [lindex $commitinfo($newid) 0]
2858 catch {destroy $top}
2860 label $top.title -text "Generate patch"
2861 grid $top.title - -pady 10
2862 label $top.from -text "From:"
2863 entry $top.fromsha1 -width 40 -relief flat
2864 $top.fromsha1 insert 0 $oldid
2865 $top.fromsha1 conf -state readonly
2866 grid $top.from $top.fromsha1 -sticky w
2867 entry $top.fromhead -width 60 -relief flat
2868 $top.fromhead insert 0 $oldhead
2869 $top.fromhead conf -state readonly
2870 grid x $top.fromhead -sticky w
2871 label $top.to -text "To:"
2872 entry $top.tosha1 -width 40 -relief flat
2873 $top.tosha1 insert 0 $newid
2874 $top.tosha1 conf -state readonly
2875 grid $top.to $top.tosha1 -sticky w
2876 entry $top.tohead -width 60 -relief flat
2877 $top.tohead insert 0 $newhead
2878 $top.tohead conf -state readonly
2879 grid x $top.tohead -sticky w
2880 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2881 grid $top.rev x -pady 10
2882 label $top.flab -text "Output file:"
2883 entry $top.fname -width 60
2884 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2886 grid $top.flab $top.fname -sticky w
2888 button $top.buts.gen -text "Generate" -command mkpatchgo
2889 button $top.buts.can -text "Cancel" -command mkpatchcan
2890 grid $top.buts.gen $top.buts.can
2891 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2892 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2893 grid $top.buts - -pady 10 -sticky ew
2897 proc mkpatchrev {} {
2900 set oldid [$patchtop.fromsha1 get]
2901 set oldhead [$patchtop.fromhead get]
2902 set newid [$patchtop.tosha1 get]
2903 set newhead [$patchtop.tohead get]
2904 foreach e [list fromsha1 fromhead tosha1 tohead] \
2905 v [list $newid $newhead $oldid $oldhead] {
2906 $patchtop.$e conf -state normal
2907 $patchtop.$e delete 0 end
2908 $patchtop.$e insert 0 $v
2909 $patchtop.$e conf -state readonly
2916 set oldid [$patchtop.fromsha1 get]
2917 set newid [$patchtop.tosha1 get]
2918 set fname [$patchtop.fname get]
2919 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2920 error_popup "Error creating patch: $err"
2922 catch {destroy $patchtop}
2926 proc mkpatchcan {} {
2929 catch {destroy $patchtop}
2934 global rowmenuid mktagtop commitinfo
2938 catch {destroy $top}
2940 label $top.title -text "Create tag"
2941 grid $top.title - -pady 10
2942 label $top.id -text "ID:"
2943 entry $top.sha1 -width 40 -relief flat
2944 $top.sha1 insert 0 $rowmenuid
2945 $top.sha1 conf -state readonly
2946 grid $top.id $top.sha1 -sticky w
2947 entry $top.head -width 60 -relief flat
2948 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2949 $top.head conf -state readonly
2950 grid x $top.head -sticky w
2951 label $top.tlab -text "Tag name:"
2952 entry $top.tag -width 60
2953 grid $top.tlab $top.tag -sticky w
2955 button $top.buts.gen -text "Create" -command mktaggo
2956 button $top.buts.can -text "Cancel" -command mktagcan
2957 grid $top.buts.gen $top.buts.can
2958 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2959 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2960 grid $top.buts - -pady 10 -sticky ew
2965 global mktagtop env tagids idtags
2966 global idpos idline linehtag canv selectedline
2968 set id [$mktagtop.sha1 get]
2969 set tag [$mktagtop.tag get]
2971 error_popup "No tag name specified"
2974 if {[info exists tagids($tag)]} {
2975 error_popup "Tag \"$tag\" already exists"
2980 set fname [file join $dir "refs/tags" $tag]
2981 set f [open $fname w]
2985 error_popup "Error creating tag: $err"
2989 set tagids($tag) $id
2990 lappend idtags($id) $tag
2991 $canv delete tag.$id
2992 set xt [eval drawtags $id $idpos($id)]
2993 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2994 if {[info exists selectedline] && $selectedline == $idline($id)} {
2995 selectline $selectedline 0
3002 catch {destroy $mktagtop}
3011 proc writecommit {} {
3012 global rowmenuid wrcomtop commitinfo wrcomcmd
3014 set top .writecommit
3016 catch {destroy $top}
3018 label $top.title -text "Write commit to file"
3019 grid $top.title - -pady 10
3020 label $top.id -text "ID:"
3021 entry $top.sha1 -width 40 -relief flat
3022 $top.sha1 insert 0 $rowmenuid
3023 $top.sha1 conf -state readonly
3024 grid $top.id $top.sha1 -sticky w
3025 entry $top.head -width 60 -relief flat
3026 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3027 $top.head conf -state readonly
3028 grid x $top.head -sticky w
3029 label $top.clab -text "Command:"
3030 entry $top.cmd -width 60 -textvariable wrcomcmd
3031 grid $top.clab $top.cmd -sticky w -pady 10
3032 label $top.flab -text "Output file:"
3033 entry $top.fname -width 60
3034 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3035 grid $top.flab $top.fname -sticky w
3037 button $top.buts.gen -text "Write" -command wrcomgo
3038 button $top.buts.can -text "Cancel" -command wrcomcan
3039 grid $top.buts.gen $top.buts.can
3040 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3041 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3042 grid $top.buts - -pady 10 -sticky ew
3049 set id [$wrcomtop.sha1 get]
3050 set cmd "echo $id | [$wrcomtop.cmd get]"
3051 set fname [$wrcomtop.fname get]
3052 if {[catch {exec sh -c $cmd >$fname &} err]} {
3053 error_popup "Error writing commit: $err"
3055 catch {destroy $wrcomtop}
3062 catch {destroy $wrcomtop}
3075 set diffopts "-U 5 -p"
3076 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3078 set mainfont {Helvetica 9}
3079 set textfont {Courier 9}
3080 set findmergefiles 0
3084 set colors {green red blue magenta darkgrey brown orange}
3086 catch {source ~/.gitk}
3088 set namefont $mainfont
3090 lappend namefont bold
3095 switch -regexp -- $arg {
3097 "^-b" { set boldnames 1 }
3098 "^-d" { set datemode 1 }
3100 lappend revtreeargs $arg
3115 getcommits $revtreeargs