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
1806 $ctext tag conf link$i -foreground blue -underline 1
1807 $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
1808 $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
1811 $ctext tag delete Comments
1812 $ctext tag remove found 1.0 end
1813 $ctext conf -state disabled
1814 set commentend [$ctext index "end - 1c"]
1816 $cflist delete 0 end
1817 $cflist insert end "Comments"
1818 if {$nparents($id) == 1} {
1819 startdiff [concat $id $parents($id)]
1820 } elseif {$nparents($id) > 1} {
1825 proc selnextline {dir} {
1827 if {![info exists selectedline]} return
1828 set l [expr $selectedline + $dir]
1834 global history historyindex
1836 if {$historyindex > 1} {
1837 incr historyindex -1
1838 selectline [lindex $history [expr {$historyindex - 1}]] 0
1839 .ctop.top.bar.rightbut conf -state normal
1841 if {$historyindex <= 1} {
1842 .ctop.top.bar.leftbut conf -state disabled
1847 global history historyindex
1849 if {$historyindex < [llength $history]} {
1850 set l [lindex $history $historyindex]
1853 .ctop.top.bar.leftbut conf -state normal
1855 if {$historyindex >= [llength $history]} {
1856 .ctop.top.bar.rightbut conf -state disabled
1860 proc mergediff {id} {
1861 global parents diffmergeid diffmergegca mergefilelist diffpindex
1865 set diffmergegca [findgca $parents($id)]
1866 if {[info exists mergefilelist($id)]} {
1867 if {$mergefilelist($id) ne {}} {
1875 proc findgca {ids} {
1882 set gca [exec git-merge-base $gca $id]
1891 proc contmergediff {ids} {
1892 global diffmergeid diffpindex parents nparents diffmergegca
1893 global treediffs mergefilelist diffids treepending
1895 # diff the child against each of the parents, and diff
1896 # each of the parents against the GCA.
1898 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1899 set ids [list [lindex $ids 1] $diffmergegca]
1901 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1902 set p [lindex $parents($diffmergeid) $diffpindex]
1903 set ids [list $diffmergeid $p]
1905 if {![info exists treediffs($ids)]} {
1907 if {![info exists treepending]} {
1914 # If a file in some parent is different from the child and also
1915 # different from the GCA, then it's interesting.
1916 # If we don't have a GCA, then a file is interesting if it is
1917 # different from the child in all the parents.
1918 if {$diffmergegca ne {}} {
1920 foreach p $parents($diffmergeid) {
1921 set gcadiffs $treediffs([list $p $diffmergegca])
1922 foreach f $treediffs([list $diffmergeid $p]) {
1923 if {[lsearch -exact $files $f] < 0
1924 && [lsearch -exact $gcadiffs $f] >= 0} {
1929 set files [lsort $files]
1931 set p [lindex $parents($diffmergeid) 0]
1932 set files $treediffs([list $diffmergeid $p])
1933 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1934 set p [lindex $parents($diffmergeid) $i]
1935 set df $treediffs([list $diffmergeid $p])
1938 if {[lsearch -exact $df $f] >= 0} {
1946 set mergefilelist($diffmergeid) $files
1952 proc showmergediff {} {
1953 global cflist diffmergeid mergefilelist parents
1954 global diffopts diffinhunk currentfile currenthunk filelines
1955 global diffblocked groupfilelast mergefds groupfilenum grouphunks
1957 set files $mergefilelist($diffmergeid)
1959 $cflist insert end $f
1961 set env(GIT_DIFF_OPTS) $diffopts
1963 catch {unset currentfile}
1964 catch {unset currenthunk}
1965 catch {unset filelines}
1966 catch {unset groupfilenum}
1967 catch {unset grouphunks}
1968 set groupfilelast -1
1969 foreach p $parents($diffmergeid) {
1970 set cmd [list | git-diff-tree -p $p $diffmergeid]
1971 set cmd [concat $cmd $mergefilelist($diffmergeid)]
1972 if {[catch {set f [open $cmd r]} err]} {
1973 error_popup "Error getting diffs: $err"
1980 set ids [list $diffmergeid $p]
1981 set mergefds($ids) $f
1982 set diffinhunk($ids) 0
1983 set diffblocked($ids) 0
1984 fconfigure $f -blocking 0
1985 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
1989 proc getmergediffline {f ids id} {
1990 global diffmergeid diffinhunk diffoldlines diffnewlines
1991 global currentfile currenthunk
1992 global diffoldstart diffnewstart diffoldlno diffnewlno
1993 global diffblocked mergefilelist
1994 global noldlines nnewlines difflcounts filelines
1996 set n [gets $f line]
1998 if {![eof $f]} return
2001 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2008 if {$diffinhunk($ids) != 0} {
2009 set fi $currentfile($ids)
2010 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2011 # continuing an existing hunk
2012 set line [string range $line 1 end]
2013 set p [lindex $ids 1]
2014 if {$match eq "-" || $match eq " "} {
2015 set filelines($p,$fi,$diffoldlno($ids)) $line
2016 incr diffoldlno($ids)
2018 if {$match eq "+" || $match eq " "} {
2019 set filelines($id,$fi,$diffnewlno($ids)) $line
2020 incr diffnewlno($ids)
2022 if {$match eq " "} {
2023 if {$diffinhunk($ids) == 2} {
2024 lappend difflcounts($ids) \
2025 [list $noldlines($ids) $nnewlines($ids)]
2026 set noldlines($ids) 0
2027 set diffinhunk($ids) 1
2029 incr noldlines($ids)
2030 } elseif {$match eq "-" || $match eq "+"} {
2031 if {$diffinhunk($ids) == 1} {
2032 lappend difflcounts($ids) [list $noldlines($ids)]
2033 set noldlines($ids) 0
2034 set nnewlines($ids) 0
2035 set diffinhunk($ids) 2
2037 if {$match eq "-"} {
2038 incr noldlines($ids)
2040 incr nnewlines($ids)
2043 # and if it's \ No newline at end of line, then what?
2047 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2048 lappend difflcounts($ids) [list $noldlines($ids)]
2049 } elseif {$diffinhunk($ids) == 2
2050 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2051 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2053 set currenthunk($ids) [list $currentfile($ids) \
2054 $diffoldstart($ids) $diffnewstart($ids) \
2055 $diffoldlno($ids) $diffnewlno($ids) \
2057 set diffinhunk($ids) 0
2058 # -1 = need to block, 0 = unblocked, 1 = is blocked
2059 set diffblocked($ids) -1
2061 if {$diffblocked($ids) == -1} {
2062 fileevent $f readable {}
2063 set diffblocked($ids) 1
2069 if {!$diffblocked($ids)} {
2071 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2072 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2075 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2076 # start of a new file
2077 set currentfile($ids) \
2078 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2079 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2080 $line match f1l f1c f2l f2c rest]} {
2081 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2082 # start of a new hunk
2083 if {$f1l == 0 && $f1c == 0} {
2086 if {$f2l == 0 && $f2c == 0} {
2089 set diffinhunk($ids) 1
2090 set diffoldstart($ids) $f1l
2091 set diffnewstart($ids) $f2l
2092 set diffoldlno($ids) $f1l
2093 set diffnewlno($ids) $f2l
2094 set difflcounts($ids) {}
2095 set noldlines($ids) 0
2096 set nnewlines($ids) 0
2101 proc processhunks {} {
2102 global diffmergeid parents nparents currenthunk
2103 global mergefilelist diffblocked mergefds
2104 global grouphunks grouplinestart grouplineend groupfilenum
2106 set nfiles [llength $mergefilelist($diffmergeid)]
2110 # look for the earliest hunk
2111 foreach p $parents($diffmergeid) {
2112 set ids [list $diffmergeid $p]
2113 if {![info exists currenthunk($ids)]} return
2114 set i [lindex $currenthunk($ids) 0]
2115 set l [lindex $currenthunk($ids) 2]
2116 if {$i < $fi || ($i == $fi && $l < $lno)} {
2123 if {$fi < $nfiles} {
2124 set ids [list $diffmergeid $pi]
2125 set hunk $currenthunk($ids)
2126 unset currenthunk($ids)
2127 if {$diffblocked($ids) > 0} {
2128 fileevent $mergefds($ids) readable \
2129 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2131 set diffblocked($ids) 0
2133 if {[info exists groupfilenum] && $groupfilenum == $fi
2134 && $lno <= $grouplineend} {
2135 # add this hunk to the pending group
2136 lappend grouphunks($pi) $hunk
2137 set endln [lindex $hunk 4]
2138 if {$endln > $grouplineend} {
2139 set grouplineend $endln
2145 # succeeding stuff doesn't belong in this group, so
2146 # process the group now
2147 if {[info exists groupfilenum]} {
2153 if {$fi >= $nfiles} break
2156 set groupfilenum $fi
2157 set grouphunks($pi) [list $hunk]
2158 set grouplinestart $lno
2159 set grouplineend [lindex $hunk 4]
2163 proc processgroup {} {
2164 global groupfilelast groupfilenum difffilestart
2165 global mergefilelist diffmergeid ctext filelines
2166 global parents diffmergeid diffoffset
2167 global grouphunks grouplinestart grouplineend nparents
2170 $ctext conf -state normal
2173 if {$groupfilelast != $f} {
2174 $ctext insert end "\n"
2175 set here [$ctext index "end - 1c"]
2176 set difffilestart($f) $here
2177 set mark fmark.[expr {$f + 1}]
2178 $ctext mark set $mark $here
2179 $ctext mark gravity $mark left
2180 set header [lindex $mergefilelist($id) $f]
2181 set l [expr {(78 - [string length $header]) / 2}]
2182 set pad [string range "----------------------------------------" 1 $l]
2183 $ctext insert end "$pad $header $pad\n" filesep
2184 set groupfilelast $f
2185 foreach p $parents($id) {
2186 set diffoffset($p) 0
2190 $ctext insert end "@@" msep
2191 set nlines [expr {$grouplineend - $grouplinestart}]
2194 foreach p $parents($id) {
2195 set startline [expr {$grouplinestart + $diffoffset($p)}]
2197 set nl $grouplinestart
2198 if {[info exists grouphunks($p)]} {
2199 foreach h $grouphunks($p) {
2202 for {} {$nl < $l} {incr nl} {
2203 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2207 foreach chunk [lindex $h 5] {
2208 if {[llength $chunk] == 2} {
2209 set olc [lindex $chunk 0]
2210 set nlc [lindex $chunk 1]
2211 set nnl [expr {$nl + $nlc}]
2212 lappend events [list $nl $nnl $pnum $olc $nlc]
2216 incr ol [lindex $chunk 0]
2217 incr nl [lindex $chunk 0]
2222 if {$nl < $grouplineend} {
2223 for {} {$nl < $grouplineend} {incr nl} {
2224 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2228 set nlines [expr {$ol - $startline}]
2229 $ctext insert end " -$startline,$nlines" msep
2233 set nlines [expr {$grouplineend - $grouplinestart}]
2234 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2236 set events [lsort -integer -index 0 $events]
2237 set nevents [llength $events]
2238 set nmerge $nparents($diffmergeid)
2239 set l $grouplinestart
2240 for {set i 0} {$i < $nevents} {set i $j} {
2241 set nl [lindex $events $i 0]
2243 $ctext insert end " $filelines($id,$f,$l)\n"
2246 set e [lindex $events $i]
2247 set enl [lindex $e 1]
2251 set pnum [lindex $e 2]
2252 set olc [lindex $e 3]
2253 set nlc [lindex $e 4]
2254 if {![info exists delta($pnum)]} {
2255 set delta($pnum) [expr {$olc - $nlc}]
2256 lappend active $pnum
2258 incr delta($pnum) [expr {$olc - $nlc}]
2260 if {[incr j] >= $nevents} break
2261 set e [lindex $events $j]
2262 if {[lindex $e 0] >= $enl} break
2263 if {[lindex $e 1] > $enl} {
2264 set enl [lindex $e 1]
2267 set nlc [expr {$enl - $l}]
2270 if {[llength $active] == $nmerge - 1} {
2271 # no diff for one of the parents, i.e. it's identical
2272 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2273 if {![info exists delta($pnum)]} {
2274 if {$pnum < $mergemax} {
2282 } elseif {[llength $active] == $nmerge} {
2283 # all parents are different, see if one is very similar
2285 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2286 set sim [similarity $pnum $l $nlc $f \
2287 [lrange $events $i [expr {$j-1}]]]
2288 if {$sim > $bestsim} {
2294 lappend ncol m$bestpn
2298 foreach p $parents($id) {
2300 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2301 set olc [expr {$nlc + $delta($pnum)}]
2302 set ol [expr {$l + $diffoffset($p)}]
2303 incr diffoffset($p) $delta($pnum)
2305 for {} {$olc > 0} {incr olc -1} {
2306 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2310 set endl [expr {$l + $nlc}]
2312 # show this pretty much as a normal diff
2313 set p [lindex $parents($id) $bestpn]
2314 set ol [expr {$l + $diffoffset($p)}]
2315 incr diffoffset($p) $delta($bestpn)
2316 unset delta($bestpn)
2317 for {set k $i} {$k < $j} {incr k} {
2318 set e [lindex $events $k]
2319 if {[lindex $e 2] != $bestpn} continue
2320 set nl [lindex $e 0]
2321 set ol [expr {$ol + $nl - $l}]
2322 for {} {$l < $nl} {incr l} {
2323 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2326 for {} {$c > 0} {incr c -1} {
2327 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2330 set nl [lindex $e 1]
2331 for {} {$l < $nl} {incr l} {
2332 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2336 for {} {$l < $endl} {incr l} {
2337 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2340 while {$l < $grouplineend} {
2341 $ctext insert end " $filelines($id,$f,$l)\n"
2344 $ctext conf -state disabled
2347 proc similarity {pnum l nlc f events} {
2348 global diffmergeid parents diffoffset filelines
2351 set p [lindex $parents($id) $pnum]
2352 set ol [expr {$l + $diffoffset($p)}]
2353 set endl [expr {$l + $nlc}]
2357 if {[lindex $e 2] != $pnum} continue
2358 set nl [lindex $e 0]
2359 set ol [expr {$ol + $nl - $l}]
2360 for {} {$l < $nl} {incr l} {
2361 incr same [string length $filelines($id,$f,$l)]
2364 set oc [lindex $e 3]
2365 for {} {$oc > 0} {incr oc -1} {
2366 incr diff [string length $filelines($p,$f,$ol)]
2370 set nl [lindex $e 1]
2371 for {} {$l < $nl} {incr l} {
2372 incr diff [string length $filelines($id,$f,$l)]
2376 for {} {$l < $endl} {incr l} {
2377 incr same [string length $filelines($id,$f,$l)]
2383 return [expr {200 * $same / (2 * $same + $diff)}]
2386 proc startdiff {ids} {
2387 global treediffs diffids treepending diffmergeid
2390 catch {unset diffmergeid}
2391 if {![info exists treediffs($ids)]} {
2392 if {![info exists treepending]} {
2400 proc addtocflist {ids} {
2401 global treediffs cflist
2402 foreach f $treediffs($ids) {
2403 $cflist insert end $f
2408 proc gettreediffs {ids} {
2409 global treediff parents treepending
2410 set treepending $ids
2412 set id [lindex $ids 0]
2413 set p [lindex $ids 1]
2414 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2415 fconfigure $gdtf -blocking 0
2416 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2419 proc gettreediffline {gdtf ids} {
2420 global treediff treediffs treepending diffids diffmergeid
2422 set n [gets $gdtf line]
2424 if {![eof $gdtf]} return
2426 set treediffs($ids) $treediff
2428 if {$ids != $diffids} {
2429 gettreediffs $diffids
2431 if {[info exists diffmergeid]} {
2439 set file [lindex $line 5]
2440 lappend treediff $file
2443 proc getblobdiffs {ids} {
2444 global diffopts blobdifffd diffids env curdifftag curtagstart
2445 global difffilestart nextupdate diffinhdr treediffs
2447 set id [lindex $ids 0]
2448 set p [lindex $ids 1]
2449 set env(GIT_DIFF_OPTS) $diffopts
2450 set cmd [list | git-diff-tree -r -p -C $p $id]
2451 if {[catch {set bdf [open $cmd r]} err]} {
2452 puts "error getting diffs: $err"
2456 fconfigure $bdf -blocking 0
2457 set blobdifffd($ids) $bdf
2458 set curdifftag Comments
2460 catch {unset difffilestart}
2461 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2462 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2465 proc getblobdiffline {bdf ids} {
2466 global diffids blobdifffd ctext curdifftag curtagstart
2467 global diffnexthead diffnextnote difffilestart
2468 global nextupdate diffinhdr treediffs
2471 set n [gets $bdf line]
2475 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2476 $ctext tag add $curdifftag $curtagstart end
2481 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2484 $ctext conf -state normal
2485 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2486 # start of a new file
2487 $ctext insert end "\n"
2488 $ctext tag add $curdifftag $curtagstart end
2489 set curtagstart [$ctext index "end - 1c"]
2491 set here [$ctext index "end - 1c"]
2492 set i [lsearch -exact $treediffs($diffids) $fname]
2494 set difffilestart($i) $here
2496 $ctext mark set fmark.$i $here
2497 $ctext mark gravity fmark.$i left
2499 if {$newname != $fname} {
2500 set i [lsearch -exact $treediffs($diffids) $newname]
2502 set difffilestart($i) $here
2504 $ctext mark set fmark.$i $here
2505 $ctext mark gravity fmark.$i left
2508 set curdifftag "f:$fname"
2509 $ctext tag delete $curdifftag
2510 set l [expr {(78 - [string length $header]) / 2}]
2511 set pad [string range "----------------------------------------" 1 $l]
2512 $ctext insert end "$pad $header $pad\n" filesep
2514 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2516 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2517 $line match f1l f1c f2l f2c rest]} {
2519 $ctext insert end "\t" hunksep
2520 $ctext insert end " $f1l " d0 " $f2l " d1
2521 $ctext insert end " $rest \n" hunksep
2523 $ctext insert end "$line\n" hunksep
2527 set x [string range $line 0 0]
2528 if {$x == "-" || $x == "+"} {
2529 set tag [expr {$x == "+"}]
2531 set line [string range $line 1 end]
2533 $ctext insert end "$line\n" d$tag
2534 } elseif {$x == " "} {
2536 set line [string range $line 1 end]
2538 $ctext insert end "$line\n"
2539 } elseif {$diffinhdr || $x == "\\"} {
2540 # e.g. "\ No newline at end of file"
2541 $ctext insert end "$line\n" filesep
2543 # Something else we don't recognize
2544 if {$curdifftag != "Comments"} {
2545 $ctext insert end "\n"
2546 $ctext tag add $curdifftag $curtagstart end
2547 set curtagstart [$ctext index "end - 1c"]
2548 set curdifftag Comments
2550 $ctext insert end "$line\n" filesep
2553 $ctext conf -state disabled
2554 if {[clock clicks -milliseconds] >= $nextupdate} {
2556 fileevent $bdf readable {}
2558 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2563 global difffilestart ctext
2564 set here [$ctext index @0,0]
2565 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2566 if {[$ctext compare $difffilestart($i) > $here]} {
2567 if {![info exists pos]
2568 || [$ctext compare $difffilestart($i) < $pos]} {
2569 set pos $difffilestart($i)
2573 if {[info exists pos]} {
2578 proc listboxsel {} {
2579 global ctext cflist currentid
2580 if {![info exists currentid]} return
2581 set sel [lsort [$cflist curselection]]
2582 if {$sel eq {}} return
2583 set first [lindex $sel 0]
2584 catch {$ctext yview fmark.$first}
2588 global linespc charspc canvx0 canvy0 mainfont
2591 set linespc [font metrics $mainfont -linespace]
2592 set charspc [font measure $mainfont "m"]
2593 set canvy0 [expr 3 + 0.5 * $linespc]
2594 set canvx0 [expr 3 + 0.5 * $linespc]
2595 set xspc1(0) $linespc
2600 global selectedline stopped redisplaying phase
2601 if {$stopped > 1} return
2602 if {$phase == "getcommits"} return
2604 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2611 proc incrfont {inc} {
2612 global mainfont namefont textfont selectedline ctext canv phase
2613 global stopped entries
2615 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2616 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2617 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2619 $ctext conf -font $textfont
2620 $ctext tag conf filesep -font [concat $textfont bold]
2621 foreach e $entries {
2622 $e conf -font $mainfont
2624 if {$phase == "getcommits"} {
2625 $canv itemconf textitems -font $mainfont
2631 global sha1entry sha1string
2632 if {[string length $sha1string] == 40} {
2633 $sha1entry delete 0 end
2637 proc sha1change {n1 n2 op} {
2638 global sha1string currentid sha1but
2639 if {$sha1string == {}
2640 || ([info exists currentid] && $sha1string == $currentid)} {
2645 if {[$sha1but cget -state] == $state} return
2646 if {$state == "normal"} {
2647 $sha1but conf -state normal -relief raised -text "Goto: "
2649 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2653 proc gotocommit {} {
2654 global sha1string currentid idline tagids
2655 global lineid numcommits
2657 if {$sha1string == {}
2658 || ([info exists currentid] && $sha1string == $currentid)} return
2659 if {[info exists tagids($sha1string)]} {
2660 set id $tagids($sha1string)
2662 set id [string tolower $sha1string]
2663 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2665 for {set l 0} {$l < $numcommits} {incr l} {
2666 if {[string match $id* $lineid($l)]} {
2667 lappend matches $lineid($l)
2670 if {$matches ne {}} {
2671 if {[llength $matches] > 1} {
2672 error_popup "Short SHA1 id $id is ambiguous"
2675 set id [lindex $matches 0]
2679 if {[info exists idline($id)]} {
2680 selectline $idline($id) 1
2683 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2688 error_popup "$type $sha1string is not known"
2691 proc lineenter {x y id} {
2692 global hoverx hovery hoverid hovertimer
2693 global commitinfo canv
2695 if {![info exists commitinfo($id)]} return
2699 if {[info exists hovertimer]} {
2700 after cancel $hovertimer
2702 set hovertimer [after 500 linehover]
2706 proc linemotion {x y id} {
2707 global hoverx hovery hoverid hovertimer
2709 if {[info exists hoverid] && $id == $hoverid} {
2712 if {[info exists hovertimer]} {
2713 after cancel $hovertimer
2715 set hovertimer [after 500 linehover]
2719 proc lineleave {id} {
2720 global hoverid hovertimer canv
2722 if {[info exists hoverid] && $id == $hoverid} {
2724 if {[info exists hovertimer]} {
2725 after cancel $hovertimer
2733 global hoverx hovery hoverid hovertimer
2734 global canv linespc lthickness
2735 global commitinfo mainfont
2737 set text [lindex $commitinfo($hoverid) 0]
2738 set ymax [lindex [$canv cget -scrollregion] 3]
2739 if {$ymax == {}} return
2740 set yfrac [lindex [$canv yview] 0]
2741 set x [expr {$hoverx + 2 * $linespc}]
2742 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2743 set x0 [expr {$x - 2 * $lthickness}]
2744 set y0 [expr {$y - 2 * $lthickness}]
2745 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2746 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2747 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2748 -fill \#ffff80 -outline black -width 1 -tags hover]
2750 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2754 proc lineclick {x y id} {
2755 global ctext commitinfo children cflist canv
2759 # fill the details pane with info about this line
2760 $ctext conf -state normal
2761 $ctext delete 0.0 end
2762 $ctext insert end "Parent:\n "
2763 catch {destroy $ctext.$id}
2764 button $ctext.$id -text "Go:" -command "selbyid $id" \
2766 $ctext window create end -window $ctext.$id -align center
2767 set info $commitinfo($id)
2768 $ctext insert end "\t[lindex $info 0]\n"
2769 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2770 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2771 $ctext insert end "\tID:\t$id\n"
2772 if {[info exists children($id)]} {
2773 $ctext insert end "\nChildren:"
2774 foreach child $children($id) {
2775 $ctext insert end "\n "
2776 catch {destroy $ctext.$child}
2777 button $ctext.$child -text "Go:" -command "selbyid $child" \
2779 $ctext window create end -window $ctext.$child -align center
2780 set info $commitinfo($child)
2781 $ctext insert end "\t[lindex $info 0]"
2784 $ctext conf -state disabled
2786 $cflist delete 0 end
2791 if {[info exists idline($id)]} {
2792 selectline $idline($id) 1
2798 if {![info exists startmstime]} {
2799 set startmstime [clock clicks -milliseconds]
2801 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2804 proc rowmenu {x y id} {
2805 global rowctxmenu idline selectedline rowmenuid
2807 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2812 $rowctxmenu entryconfigure 0 -state $state
2813 $rowctxmenu entryconfigure 1 -state $state
2814 $rowctxmenu entryconfigure 2 -state $state
2816 tk_popup $rowctxmenu $x $y
2819 proc diffvssel {dirn} {
2820 global rowmenuid selectedline lineid
2824 if {![info exists selectedline]} return
2826 set oldid $lineid($selectedline)
2827 set newid $rowmenuid
2829 set oldid $rowmenuid
2830 set newid $lineid($selectedline)
2832 $ctext conf -state normal
2833 $ctext delete 0.0 end
2834 $ctext mark set fmark.0 0.0
2835 $ctext mark gravity fmark.0 left
2836 $cflist delete 0 end
2837 $cflist insert end "Top"
2838 $ctext insert end "From $oldid\n "
2839 $ctext insert end [lindex $commitinfo($oldid) 0]
2840 $ctext insert end "\n\nTo $newid\n "
2841 $ctext insert end [lindex $commitinfo($newid) 0]
2842 $ctext insert end "\n"
2843 $ctext conf -state disabled
2844 $ctext tag delete Comments
2845 $ctext tag remove found 1.0 end
2846 startdiff [list $newid $oldid]
2850 global rowmenuid currentid commitinfo patchtop patchnum
2852 if {![info exists currentid]} return
2853 set oldid $currentid
2854 set oldhead [lindex $commitinfo($oldid) 0]
2855 set newid $rowmenuid
2856 set newhead [lindex $commitinfo($newid) 0]
2859 catch {destroy $top}
2861 label $top.title -text "Generate patch"
2862 grid $top.title - -pady 10
2863 label $top.from -text "From:"
2864 entry $top.fromsha1 -width 40 -relief flat
2865 $top.fromsha1 insert 0 $oldid
2866 $top.fromsha1 conf -state readonly
2867 grid $top.from $top.fromsha1 -sticky w
2868 entry $top.fromhead -width 60 -relief flat
2869 $top.fromhead insert 0 $oldhead
2870 $top.fromhead conf -state readonly
2871 grid x $top.fromhead -sticky w
2872 label $top.to -text "To:"
2873 entry $top.tosha1 -width 40 -relief flat
2874 $top.tosha1 insert 0 $newid
2875 $top.tosha1 conf -state readonly
2876 grid $top.to $top.tosha1 -sticky w
2877 entry $top.tohead -width 60 -relief flat
2878 $top.tohead insert 0 $newhead
2879 $top.tohead conf -state readonly
2880 grid x $top.tohead -sticky w
2881 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2882 grid $top.rev x -pady 10
2883 label $top.flab -text "Output file:"
2884 entry $top.fname -width 60
2885 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2887 grid $top.flab $top.fname -sticky w
2889 button $top.buts.gen -text "Generate" -command mkpatchgo
2890 button $top.buts.can -text "Cancel" -command mkpatchcan
2891 grid $top.buts.gen $top.buts.can
2892 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2893 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2894 grid $top.buts - -pady 10 -sticky ew
2898 proc mkpatchrev {} {
2901 set oldid [$patchtop.fromsha1 get]
2902 set oldhead [$patchtop.fromhead get]
2903 set newid [$patchtop.tosha1 get]
2904 set newhead [$patchtop.tohead get]
2905 foreach e [list fromsha1 fromhead tosha1 tohead] \
2906 v [list $newid $newhead $oldid $oldhead] {
2907 $patchtop.$e conf -state normal
2908 $patchtop.$e delete 0 end
2909 $patchtop.$e insert 0 $v
2910 $patchtop.$e conf -state readonly
2917 set oldid [$patchtop.fromsha1 get]
2918 set newid [$patchtop.tosha1 get]
2919 set fname [$patchtop.fname get]
2920 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2921 error_popup "Error creating patch: $err"
2923 catch {destroy $patchtop}
2927 proc mkpatchcan {} {
2930 catch {destroy $patchtop}
2935 global rowmenuid mktagtop commitinfo
2939 catch {destroy $top}
2941 label $top.title -text "Create tag"
2942 grid $top.title - -pady 10
2943 label $top.id -text "ID:"
2944 entry $top.sha1 -width 40 -relief flat
2945 $top.sha1 insert 0 $rowmenuid
2946 $top.sha1 conf -state readonly
2947 grid $top.id $top.sha1 -sticky w
2948 entry $top.head -width 60 -relief flat
2949 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2950 $top.head conf -state readonly
2951 grid x $top.head -sticky w
2952 label $top.tlab -text "Tag name:"
2953 entry $top.tag -width 60
2954 grid $top.tlab $top.tag -sticky w
2956 button $top.buts.gen -text "Create" -command mktaggo
2957 button $top.buts.can -text "Cancel" -command mktagcan
2958 grid $top.buts.gen $top.buts.can
2959 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2960 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2961 grid $top.buts - -pady 10 -sticky ew
2966 global mktagtop env tagids idtags
2967 global idpos idline linehtag canv selectedline
2969 set id [$mktagtop.sha1 get]
2970 set tag [$mktagtop.tag get]
2972 error_popup "No tag name specified"
2975 if {[info exists tagids($tag)]} {
2976 error_popup "Tag \"$tag\" already exists"
2981 set fname [file join $dir "refs/tags" $tag]
2982 set f [open $fname w]
2986 error_popup "Error creating tag: $err"
2990 set tagids($tag) $id
2991 lappend idtags($id) $tag
2992 $canv delete tag.$id
2993 set xt [eval drawtags $id $idpos($id)]
2994 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2995 if {[info exists selectedline] && $selectedline == $idline($id)} {
2996 selectline $selectedline 0
3003 catch {destroy $mktagtop}
3012 proc writecommit {} {
3013 global rowmenuid wrcomtop commitinfo wrcomcmd
3015 set top .writecommit
3017 catch {destroy $top}
3019 label $top.title -text "Write commit to file"
3020 grid $top.title - -pady 10
3021 label $top.id -text "ID:"
3022 entry $top.sha1 -width 40 -relief flat
3023 $top.sha1 insert 0 $rowmenuid
3024 $top.sha1 conf -state readonly
3025 grid $top.id $top.sha1 -sticky w
3026 entry $top.head -width 60 -relief flat
3027 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3028 $top.head conf -state readonly
3029 grid x $top.head -sticky w
3030 label $top.clab -text "Command:"
3031 entry $top.cmd -width 60 -textvariable wrcomcmd
3032 grid $top.clab $top.cmd -sticky w -pady 10
3033 label $top.flab -text "Output file:"
3034 entry $top.fname -width 60
3035 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3036 grid $top.flab $top.fname -sticky w
3038 button $top.buts.gen -text "Write" -command wrcomgo
3039 button $top.buts.can -text "Cancel" -command wrcomcan
3040 grid $top.buts.gen $top.buts.can
3041 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3042 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3043 grid $top.buts - -pady 10 -sticky ew
3050 set id [$wrcomtop.sha1 get]
3051 set cmd "echo $id | [$wrcomtop.cmd get]"
3052 set fname [$wrcomtop.fname get]
3053 if {[catch {exec sh -c $cmd >$fname &} err]} {
3054 error_popup "Error writing commit: $err"
3056 catch {destroy $wrcomtop}
3063 catch {destroy $wrcomtop}
3076 set diffopts "-U 5 -p"
3077 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3079 set mainfont {Helvetica 9}
3080 set textfont {Courier 9}
3081 set findmergefiles 0
3085 set colors {green red blue magenta darkgrey brown orange}
3087 catch {source ~/.gitk}
3089 set namefont $mainfont
3091 lappend namefont bold
3096 switch -regexp -- $arg {
3098 "^-b" { set boldnames 1 }
3099 "^-d" { set datemode 1 }
3101 lappend revtreeargs $arg
3116 getcommits $revtreeargs