Fix a bug where we would corrupt the stuff read from git-rev-list.
[git/mingw/4msysgit.git] / gitk
blobe72c9c7bdbed8d316d87d9f2ddda89f57ef94639
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc getcommits {rargs} {
11 global commits commfd phase canv mainfont env
12 global startmsecs nextupdate
13 global ctext maincursor textcursor leftover
15 # check that we can find a .git directory somewhere...
16 if {[info exists env(GIT_DIR)]} {
17 set gitdir $env(GIT_DIR)
18 } else {
19 set gitdir ".git"
21 if {![file isdirectory $gitdir]} {
22 error_popup "Cannot find the git directory \"$gitdir\"."
23 exit 1
25 set commits {}
26 set phase getcommits
27 set startmsecs [clock clicks -milliseconds]
28 set nextupdate [expr $startmsecs + 100]
29 if [catch {
30 set parse_args [concat --default HEAD $rargs]
31 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
32 }] {
33 # if git-rev-parse failed for some reason...
34 if {$rargs == {}} {
35 set rargs HEAD
37 set parsed_args $rargs
39 if [catch {
40 set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
41 } err] {
42 puts stderr "Error executing git-rev-list: $err"
43 exit 1
45 set leftover {}
46 fconfigure $commfd -blocking 0 -translation binary
47 fileevent $commfd readable "getcommitlines $commfd"
48 $canv delete all
49 $canv create text 3 3 -anchor nw -text "Reading commits..." \
50 -font $mainfont -tags textitems
51 . config -cursor watch
52 $ctext config -cursor watch
55 proc getcommitlines {commfd} {
56 global commits parents cdate children nchildren
57 global commitlisted phase commitinfo nextupdate
58 global stopped redisplaying leftover
60 set stuff [read $commfd]
61 if {$stuff == {}} {
62 if {![eof $commfd]} return
63 # this works around what is apparently a bug in Tcl...
64 fconfigure $commfd -blocking 1
65 if {![catch {close $commfd} err]} {
66 after idle finishcommits
67 return
69 if {[string range $err 0 4] == "usage"} {
70 set err \
71 {Gitk: error reading commits: bad arguments to git-rev-list.
72 (Note: arguments to gitk are passed to git-rev-list
73 to allow selection of commits to be displayed.)}
74 } else {
75 set err "Error reading commits: $err"
77 error_popup $err
78 exit 1
80 set start 0
81 while 1 {
82 set i [string first "\0" $stuff $start]
83 if {$i < 0} {
84 append leftover [string range $stuff $start end]
85 return
87 set cmit [string range $stuff $start [expr {$i - 1}]]
88 if {$start == 0} {
89 set cmit "$leftover$cmit"
90 set leftover {}
92 set start [expr {$i + 1}]
93 if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
94 set shortcmit $cmit
95 if {[string length $shortcmit] > 80} {
96 set shortcmit "[string range $shortcmit 0 80]..."
98 error_popup "Can't parse git-rev-list output: {$shortcmit}"
99 exit 1
101 set cmit [string range $cmit 41 end]
102 lappend commits $id
103 set commitlisted($id) 1
104 parsecommit $id $cmit 1
105 drawcommit $id
106 if {[clock clicks -milliseconds] >= $nextupdate} {
107 doupdate
109 while {$redisplaying} {
110 set redisplaying 0
111 if {$stopped == 1} {
112 set stopped 0
113 set phase "getcommits"
114 foreach id $commits {
115 drawcommit $id
116 if {$stopped} break
117 if {[clock clicks -milliseconds] >= $nextupdate} {
118 doupdate
126 proc doupdate {} {
127 global commfd nextupdate
129 incr nextupdate 100
130 fileevent $commfd readable {}
131 update
132 fileevent $commfd readable "getcommitlines $commfd"
135 proc readcommit {id} {
136 if [catch {set contents [exec git-cat-file commit $id]}] return
137 parsecommit $id $contents 0
140 proc parsecommit {id contents listed} {
141 global commitinfo children nchildren parents nparents cdate ncleft
143 set inhdr 1
144 set comment {}
145 set headline {}
146 set auname {}
147 set audate {}
148 set comname {}
149 set comdate {}
150 if {![info exists nchildren($id)]} {
151 set children($id) {}
152 set nchildren($id) 0
153 set ncleft($id) 0
155 set parents($id) {}
156 set nparents($id) 0
157 foreach line [split $contents "\n"] {
158 if {$inhdr} {
159 if {$line == {}} {
160 set inhdr 0
161 } else {
162 set tag [lindex $line 0]
163 if {$tag == "parent"} {
164 set p [lindex $line 1]
165 if {![info exists nchildren($p)]} {
166 set children($p) {}
167 set nchildren($p) 0
168 set ncleft($p) 0
170 lappend parents($id) $p
171 incr nparents($id)
172 # sometimes we get a commit that lists a parent twice...
173 if {$listed && [lsearch -exact $children($p) $id] < 0} {
174 lappend children($p) $id
175 incr nchildren($p)
176 incr ncleft($p)
178 } elseif {$tag == "author"} {
179 set x [expr {[llength $line] - 2}]
180 set audate [lindex $line $x]
181 set auname [lrange $line 1 [expr {$x - 1}]]
182 } elseif {$tag == "committer"} {
183 set x [expr {[llength $line] - 2}]
184 set comdate [lindex $line $x]
185 set comname [lrange $line 1 [expr {$x - 1}]]
188 } else {
189 if {$comment == {}} {
190 set headline [string trim $line]
191 } else {
192 append comment "\n"
194 if {!$listed} {
195 # git-rev-list indents the comment by 4 spaces;
196 # if we got this via git-cat-file, add the indentation
197 append comment " "
199 append comment $line
202 if {$audate != {}} {
203 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
205 if {$comdate != {}} {
206 set cdate($id) $comdate
207 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
209 set commitinfo($id) [list $headline $auname $audate \
210 $comname $comdate $comment]
213 proc readrefs {} {
214 global tagids idtags headids idheads
215 set tags [glob -nocomplain -types f .git/refs/tags/*]
216 foreach f $tags {
217 catch {
218 set fd [open $f r]
219 set line [read $fd]
220 if {[regexp {^[0-9a-f]{40}} $line id]} {
221 set direct [file tail $f]
222 set tagids($direct) $id
223 lappend idtags($id) $direct
224 set contents [split [exec git-cat-file tag $id] "\n"]
225 set obj {}
226 set type {}
227 set tag {}
228 foreach l $contents {
229 if {$l == {}} break
230 switch -- [lindex $l 0] {
231 "object" {set obj [lindex $l 1]}
232 "type" {set type [lindex $l 1]}
233 "tag" {set tag [string range $l 4 end]}
236 if {$obj != {} && $type == "commit" && $tag != {}} {
237 set tagids($tag) $obj
238 lappend idtags($obj) $tag
241 close $fd
244 set heads [glob -nocomplain -types f .git/refs/heads/*]
245 foreach f $heads {
246 catch {
247 set fd [open $f r]
248 set line [read $fd 40]
249 if {[regexp {^[0-9a-f]{40}} $line id]} {
250 set head [file tail $f]
251 set headids($head) $line
252 lappend idheads($line) $head
254 close $fd
259 proc error_popup msg {
260 set w .error
261 toplevel $w
262 wm transient $w .
263 message $w.m -text $msg -justify center -aspect 400
264 pack $w.m -side top -fill x -padx 20 -pady 20
265 button $w.ok -text OK -command "destroy $w"
266 pack $w.ok -side bottom -fill x
267 bind $w <Visibility> "grab $w; focus $w"
268 tkwait window $w
271 proc makewindow {} {
272 global canv canv2 canv3 linespc charspc ctext cflist textfont
273 global findtype findloc findstring fstring geometry
274 global entries sha1entry sha1string sha1but
275 global maincursor textcursor
276 global rowctxmenu
278 menu .bar
279 .bar add cascade -label "File" -menu .bar.file
280 menu .bar.file
281 .bar.file add command -label "Quit" -command doquit
282 menu .bar.help
283 .bar add cascade -label "Help" -menu .bar.help
284 .bar.help add command -label "About gitk" -command about
285 . configure -menu .bar
287 if {![info exists geometry(canv1)]} {
288 set geometry(canv1) [expr 45 * $charspc]
289 set geometry(canv2) [expr 30 * $charspc]
290 set geometry(canv3) [expr 15 * $charspc]
291 set geometry(canvh) [expr 25 * $linespc + 4]
292 set geometry(ctextw) 80
293 set geometry(ctexth) 30
294 set geometry(cflistw) 30
296 panedwindow .ctop -orient vertical
297 if {[info exists geometry(width)]} {
298 .ctop conf -width $geometry(width) -height $geometry(height)
299 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
300 set geometry(ctexth) [expr {($texth - 8) /
301 [font metrics $textfont -linespace]}]
303 frame .ctop.top
304 frame .ctop.top.bar
305 pack .ctop.top.bar -side bottom -fill x
306 set cscroll .ctop.top.csb
307 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
308 pack $cscroll -side right -fill y
309 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
310 pack .ctop.top.clist -side top -fill both -expand 1
311 .ctop add .ctop.top
312 set canv .ctop.top.clist.canv
313 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
314 -bg white -bd 0 \
315 -yscrollincr $linespc -yscrollcommand "$cscroll set"
316 .ctop.top.clist add $canv
317 set canv2 .ctop.top.clist.canv2
318 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
319 -bg white -bd 0 -yscrollincr $linespc
320 .ctop.top.clist add $canv2
321 set canv3 .ctop.top.clist.canv3
322 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
323 -bg white -bd 0 -yscrollincr $linespc
324 .ctop.top.clist add $canv3
325 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
327 set sha1entry .ctop.top.bar.sha1
328 set entries $sha1entry
329 set sha1but .ctop.top.bar.sha1label
330 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
331 -command gotocommit -width 8
332 $sha1but conf -disabledforeground [$sha1but cget -foreground]
333 pack .ctop.top.bar.sha1label -side left
334 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
335 trace add variable sha1string write sha1change
336 pack $sha1entry -side left -pady 2
337 button .ctop.top.bar.findbut -text "Find" -command dofind
338 pack .ctop.top.bar.findbut -side left
339 set findstring {}
340 set fstring .ctop.top.bar.findstring
341 lappend entries $fstring
342 entry $fstring -width 30 -font $textfont -textvariable findstring
343 pack $fstring -side left -expand 1 -fill x
344 set findtype Exact
345 tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
346 set findloc "All fields"
347 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
348 Comments Author Committer
349 pack .ctop.top.bar.findloc -side right
350 pack .ctop.top.bar.findtype -side right
352 panedwindow .ctop.cdet -orient horizontal
353 .ctop add .ctop.cdet
354 frame .ctop.cdet.left
355 set ctext .ctop.cdet.left.ctext
356 text $ctext -bg white -state disabled -font $textfont \
357 -width $geometry(ctextw) -height $geometry(ctexth) \
358 -yscrollcommand ".ctop.cdet.left.sb set"
359 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
360 pack .ctop.cdet.left.sb -side right -fill y
361 pack $ctext -side left -fill both -expand 1
362 .ctop.cdet add .ctop.cdet.left
364 $ctext tag conf filesep -font [concat $textfont bold]
365 $ctext tag conf hunksep -back blue -fore white
366 $ctext tag conf d0 -back "#ff8080"
367 $ctext tag conf d1 -back green
368 $ctext tag conf found -back yellow
370 frame .ctop.cdet.right
371 set cflist .ctop.cdet.right.cfiles
372 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
373 -yscrollcommand ".ctop.cdet.right.sb set"
374 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
375 pack .ctop.cdet.right.sb -side right -fill y
376 pack $cflist -side left -fill both -expand 1
377 .ctop.cdet add .ctop.cdet.right
378 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
380 pack .ctop -side top -fill both -expand 1
382 bindall <1> {selcanvline %W %x %y}
383 #bindall <B1-Motion> {selcanvline %W %x %y}
384 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
385 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
386 bindall <2> "allcanvs scan mark 0 %y"
387 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
388 bind . <Key-Up> "selnextline -1"
389 bind . <Key-Down> "selnextline 1"
390 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
391 bind . <Key-Next> "allcanvs yview scroll 1 pages"
392 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
393 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
394 bindkey <Key-space> "$ctext yview scroll 1 pages"
395 bindkey p "selnextline -1"
396 bindkey n "selnextline 1"
397 bindkey b "$ctext yview scroll -1 pages"
398 bindkey d "$ctext yview scroll 18 units"
399 bindkey u "$ctext yview scroll -18 units"
400 bindkey / findnext
401 bindkey ? findprev
402 bindkey f nextfile
403 bind . <Control-q> doquit
404 bind . <Control-f> dofind
405 bind . <Control-g> findnext
406 bind . <Control-r> findprev
407 bind . <Control-equal> {incrfont 1}
408 bind . <Control-KP_Add> {incrfont 1}
409 bind . <Control-minus> {incrfont -1}
410 bind . <Control-KP_Subtract> {incrfont -1}
411 bind $cflist <<ListboxSelect>> listboxsel
412 bind . <Destroy> {savestuff %W}
413 bind . <Button-1> "click %W"
414 bind $fstring <Key-Return> dofind
415 bind $sha1entry <Key-Return> gotocommit
416 bind $sha1entry <<PasteSelection>> clearsha1
418 set maincursor [. cget -cursor]
419 set textcursor [$ctext cget -cursor]
421 set rowctxmenu .rowctxmenu
422 menu $rowctxmenu -tearoff 0
423 $rowctxmenu add command -label "Diff this -> selected" \
424 -command {diffvssel 0}
425 $rowctxmenu add command -label "Diff selected -> this" \
426 -command {diffvssel 1}
427 $rowctxmenu add command -label "Make patch" -command mkpatch
430 # when we make a key binding for the toplevel, make sure
431 # it doesn't get triggered when that key is pressed in the
432 # find string entry widget.
433 proc bindkey {ev script} {
434 global entries
435 bind . $ev $script
436 set escript [bind Entry $ev]
437 if {$escript == {}} {
438 set escript [bind Entry <Key>]
440 foreach e $entries {
441 bind $e $ev "$escript; break"
445 # set the focus back to the toplevel for any click outside
446 # the entry widgets
447 proc click {w} {
448 global entries
449 foreach e $entries {
450 if {$w == $e} return
452 focus .
455 proc savestuff {w} {
456 global canv canv2 canv3 ctext cflist mainfont textfont
457 global stuffsaved
458 if {$stuffsaved} return
459 if {![winfo viewable .]} return
460 catch {
461 set f [open "~/.gitk-new" w]
462 puts $f "set mainfont {$mainfont}"
463 puts $f "set textfont {$textfont}"
464 puts $f "set geometry(width) [winfo width .ctop]"
465 puts $f "set geometry(height) [winfo height .ctop]"
466 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
467 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
468 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
469 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
470 set wid [expr {([winfo width $ctext] - 8) \
471 / [font measure $textfont "0"]}]
472 puts $f "set geometry(ctextw) $wid"
473 set wid [expr {([winfo width $cflist] - 11) \
474 / [font measure [$cflist cget -font] "0"]}]
475 puts $f "set geometry(cflistw) $wid"
476 close $f
477 file rename -force "~/.gitk-new" "~/.gitk"
479 set stuffsaved 1
482 proc resizeclistpanes {win w} {
483 global oldwidth
484 if [info exists oldwidth($win)] {
485 set s0 [$win sash coord 0]
486 set s1 [$win sash coord 1]
487 if {$w < 60} {
488 set sash0 [expr {int($w/2 - 2)}]
489 set sash1 [expr {int($w*5/6 - 2)}]
490 } else {
491 set factor [expr {1.0 * $w / $oldwidth($win)}]
492 set sash0 [expr {int($factor * [lindex $s0 0])}]
493 set sash1 [expr {int($factor * [lindex $s1 0])}]
494 if {$sash0 < 30} {
495 set sash0 30
497 if {$sash1 < $sash0 + 20} {
498 set sash1 [expr $sash0 + 20]
500 if {$sash1 > $w - 10} {
501 set sash1 [expr $w - 10]
502 if {$sash0 > $sash1 - 20} {
503 set sash0 [expr $sash1 - 20]
507 $win sash place 0 $sash0 [lindex $s0 1]
508 $win sash place 1 $sash1 [lindex $s1 1]
510 set oldwidth($win) $w
513 proc resizecdetpanes {win w} {
514 global oldwidth
515 if [info exists oldwidth($win)] {
516 set s0 [$win sash coord 0]
517 if {$w < 60} {
518 set sash0 [expr {int($w*3/4 - 2)}]
519 } else {
520 set factor [expr {1.0 * $w / $oldwidth($win)}]
521 set sash0 [expr {int($factor * [lindex $s0 0])}]
522 if {$sash0 < 45} {
523 set sash0 45
525 if {$sash0 > $w - 15} {
526 set sash0 [expr $w - 15]
529 $win sash place 0 $sash0 [lindex $s0 1]
531 set oldwidth($win) $w
534 proc allcanvs args {
535 global canv canv2 canv3
536 eval $canv $args
537 eval $canv2 $args
538 eval $canv3 $args
541 proc bindall {event action} {
542 global canv canv2 canv3
543 bind $canv $event $action
544 bind $canv2 $event $action
545 bind $canv3 $event $action
548 proc about {} {
549 set w .about
550 if {[winfo exists $w]} {
551 raise $w
552 return
554 toplevel $w
555 wm title $w "About gitk"
556 message $w.m -text {
557 Gitk version 1.2
559 Copyright © 2005 Paul Mackerras
561 Use and redistribute under the terms of the GNU General Public License} \
562 -justify center -aspect 400
563 pack $w.m -side top -fill x -padx 20 -pady 20
564 button $w.ok -text Close -command "destroy $w"
565 pack $w.ok -side bottom
568 proc assigncolor {id} {
569 global commitinfo colormap commcolors colors nextcolor
570 global parents nparents children nchildren
571 global cornercrossings crossings
573 if [info exists colormap($id)] return
574 set ncolors [llength $colors]
575 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
576 set child [lindex $children($id) 0]
577 if {[info exists colormap($child)]
578 && $nparents($child) == 1} {
579 set colormap($id) $colormap($child)
580 return
583 set badcolors {}
584 if {[info exists cornercrossings($id)]} {
585 foreach x $cornercrossings($id) {
586 if {[info exists colormap($x)]
587 && [lsearch -exact $badcolors $colormap($x)] < 0} {
588 lappend badcolors $colormap($x)
591 if {[llength $badcolors] >= $ncolors} {
592 set badcolors {}
595 set origbad $badcolors
596 if {[llength $badcolors] < $ncolors - 1} {
597 if {[info exists crossings($id)]} {
598 foreach x $crossings($id) {
599 if {[info exists colormap($x)]
600 && [lsearch -exact $badcolors $colormap($x)] < 0} {
601 lappend badcolors $colormap($x)
604 if {[llength $badcolors] >= $ncolors} {
605 set badcolors $origbad
608 set origbad $badcolors
610 if {[llength $badcolors] < $ncolors - 1} {
611 foreach child $children($id) {
612 if {[info exists colormap($child)]
613 && [lsearch -exact $badcolors $colormap($child)] < 0} {
614 lappend badcolors $colormap($child)
616 if {[info exists parents($child)]} {
617 foreach p $parents($child) {
618 if {[info exists colormap($p)]
619 && [lsearch -exact $badcolors $colormap($p)] < 0} {
620 lappend badcolors $colormap($p)
625 if {[llength $badcolors] >= $ncolors} {
626 set badcolors $origbad
629 for {set i 0} {$i <= $ncolors} {incr i} {
630 set c [lindex $colors $nextcolor]
631 if {[incr nextcolor] >= $ncolors} {
632 set nextcolor 0
634 if {[lsearch -exact $badcolors $c]} break
636 set colormap($id) $c
639 proc initgraph {} {
640 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
641 global mainline sidelines
642 global nchildren ncleft
644 allcanvs delete all
645 set nextcolor 0
646 set canvy $canvy0
647 set lineno -1
648 set numcommits 0
649 set lthickness [expr {int($linespc / 9) + 1}]
650 catch {unset mainline}
651 catch {unset sidelines}
652 foreach id [array names nchildren] {
653 set ncleft($id) $nchildren($id)
657 proc bindline {t id} {
658 global canv
660 $canv bind $t <Enter> "lineenter %x %y $id"
661 $canv bind $t <Motion> "linemotion %x %y $id"
662 $canv bind $t <Leave> "lineleave $id"
663 $canv bind $t <Button-1> "lineclick %x %y $id"
666 proc drawcommitline {level} {
667 global parents children nparents nchildren todo
668 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
669 global lineid linehtag linentag linedtag commitinfo
670 global colormap numcommits currentparents dupparents
671 global oldlevel oldnlines oldtodo
672 global idtags idline idheads
673 global lineno lthickness mainline sidelines
674 global commitlisted rowtextx
676 incr numcommits
677 incr lineno
678 set id [lindex $todo $level]
679 set lineid($lineno) $id
680 set idline($id) $lineno
681 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
682 if {![info exists commitinfo($id)]} {
683 readcommit $id
684 if {![info exists commitinfo($id)]} {
685 set commitinfo($id) {"No commit information available"}
686 set nparents($id) 0
689 assigncolor $id
690 set currentparents {}
691 set dupparents {}
692 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
693 foreach p $parents($id) {
694 if {[lsearch -exact $currentparents $p] < 0} {
695 lappend currentparents $p
696 } else {
697 # remember that this parent was listed twice
698 lappend dupparents $p
702 set x [expr $canvx0 + $level * $linespc]
703 set y1 $canvy
704 set canvy [expr $canvy + $linespc]
705 allcanvs conf -scrollregion \
706 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
707 if {[info exists mainline($id)]} {
708 lappend mainline($id) $x $y1
709 set t [$canv create line $mainline($id) \
710 -width $lthickness -fill $colormap($id)]
711 $canv lower $t
712 bindline $t $id
714 if {[info exists sidelines($id)]} {
715 foreach ls $sidelines($id) {
716 set coords [lindex $ls 0]
717 set thick [lindex $ls 1]
718 set t [$canv create line $coords -fill $colormap($id) \
719 -width [expr {$thick * $lthickness}]]
720 $canv lower $t
721 bindline $t $id
724 set orad [expr {$linespc / 3}]
725 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
726 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
727 -fill $ofill -outline black -width 1]
728 $canv raise $t
729 $canv bind $t <1> {selcanvline {} %x %y}
730 set xt [expr $canvx0 + [llength $todo] * $linespc]
731 if {[llength $currentparents] > 2} {
732 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
734 set rowtextx($lineno) $xt
735 set marks {}
736 set ntags 0
737 if {[info exists idtags($id)]} {
738 set marks $idtags($id)
739 set ntags [llength $marks]
741 if {[info exists idheads($id)]} {
742 set marks [concat $marks $idheads($id)]
744 if {$marks != {}} {
745 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
746 set yt [expr $y1 - 0.5 * $linespc]
747 set yb [expr $yt + $linespc - 1]
748 set xvals {}
749 set wvals {}
750 foreach tag $marks {
751 set wid [font measure $mainfont $tag]
752 lappend xvals $xt
753 lappend wvals $wid
754 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
756 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
757 -width $lthickness -fill black]
758 $canv lower $t
759 foreach tag $marks x $xvals wid $wvals {
760 set xl [expr $x + $delta]
761 set xr [expr $x + $delta + $wid + $lthickness]
762 if {[incr ntags -1] >= 0} {
763 # draw a tag
764 $canv create polygon $x [expr $yt + $delta] $xl $yt\
765 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
766 -width 1 -outline black -fill yellow
767 } else {
768 # draw a head
769 set xl [expr $xl - $delta/2]
770 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
771 -width 1 -outline black -fill green
773 $canv create text $xl $y1 -anchor w -text $tag \
774 -font $mainfont
777 set headline [lindex $commitinfo($id) 0]
778 set name [lindex $commitinfo($id) 1]
779 set date [lindex $commitinfo($id) 2]
780 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
781 -text $headline -font $mainfont ]
782 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
783 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
784 -text $name -font $namefont]
785 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
786 -text $date -font $mainfont]
789 proc updatetodo {level noshortcut} {
790 global currentparents ncleft todo
791 global mainline oldlevel oldtodo oldnlines
792 global canvx0 canvy linespc mainline
793 global commitinfo
795 set oldlevel $level
796 set oldtodo $todo
797 set oldnlines [llength $todo]
798 if {!$noshortcut && [llength $currentparents] == 1} {
799 set p [lindex $currentparents 0]
800 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
801 set ncleft($p) 0
802 set x [expr $canvx0 + $level * $linespc]
803 set y [expr $canvy - $linespc]
804 set mainline($p) [list $x $y]
805 set todo [lreplace $todo $level $level $p]
806 return 0
810 set todo [lreplace $todo $level $level]
811 set i $level
812 foreach p $currentparents {
813 incr ncleft($p) -1
814 set k [lsearch -exact $todo $p]
815 if {$k < 0} {
816 set todo [linsert $todo $i $p]
817 incr i
820 return 1
823 proc notecrossings {id lo hi corner} {
824 global oldtodo crossings cornercrossings
826 for {set i $lo} {[incr i] < $hi} {} {
827 set p [lindex $oldtodo $i]
828 if {$p == {}} continue
829 if {$i == $corner} {
830 if {![info exists cornercrossings($id)]
831 || [lsearch -exact $cornercrossings($id) $p] < 0} {
832 lappend cornercrossings($id) $p
834 if {![info exists cornercrossings($p)]
835 || [lsearch -exact $cornercrossings($p) $id] < 0} {
836 lappend cornercrossings($p) $id
838 } else {
839 if {![info exists crossings($id)]
840 || [lsearch -exact $crossings($id) $p] < 0} {
841 lappend crossings($id) $p
843 if {![info exists crossings($p)]
844 || [lsearch -exact $crossings($p) $id] < 0} {
845 lappend crossings($p) $id
851 proc drawslants {} {
852 global canv mainline sidelines canvx0 canvy linespc
853 global oldlevel oldtodo todo currentparents dupparents
854 global lthickness linespc canvy colormap
856 set y1 [expr $canvy - $linespc]
857 set y2 $canvy
858 set i -1
859 foreach id $oldtodo {
860 incr i
861 if {$id == {}} continue
862 set xi [expr {$canvx0 + $i * $linespc}]
863 if {$i == $oldlevel} {
864 foreach p $currentparents {
865 set j [lsearch -exact $todo $p]
866 set coords [list $xi $y1]
867 set xj [expr {$canvx0 + $j * $linespc}]
868 if {$j < $i - 1} {
869 lappend coords [expr $xj + $linespc] $y1
870 notecrossings $p $j $i [expr {$j + 1}]
871 } elseif {$j > $i + 1} {
872 lappend coords [expr $xj - $linespc] $y1
873 notecrossings $p $i $j [expr {$j - 1}]
875 if {[lsearch -exact $dupparents $p] >= 0} {
876 # draw a double-width line to indicate the doubled parent
877 lappend coords $xj $y2
878 lappend sidelines($p) [list $coords 2]
879 if {![info exists mainline($p)]} {
880 set mainline($p) [list $xj $y2]
882 } else {
883 # normal case, no parent duplicated
884 if {![info exists mainline($p)]} {
885 if {$i != $j} {
886 lappend coords $xj $y2
888 set mainline($p) $coords
889 } else {
890 lappend coords $xj $y2
891 lappend sidelines($p) [list $coords 1]
895 } elseif {[lindex $todo $i] != $id} {
896 set j [lsearch -exact $todo $id]
897 set xj [expr {$canvx0 + $j * $linespc}]
898 lappend mainline($id) $xi $y1 $xj $y2
903 proc decidenext {{noread 0}} {
904 global parents children nchildren ncleft todo
905 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
906 global datemode cdate
907 global commitinfo
908 global currentparents oldlevel oldnlines oldtodo
909 global lineno lthickness
911 # remove the null entry if present
912 set nullentry [lsearch -exact $todo {}]
913 if {$nullentry >= 0} {
914 set todo [lreplace $todo $nullentry $nullentry]
917 # choose which one to do next time around
918 set todol [llength $todo]
919 set level -1
920 set latest {}
921 for {set k $todol} {[incr k -1] >= 0} {} {
922 set p [lindex $todo $k]
923 if {$ncleft($p) == 0} {
924 if {$datemode} {
925 if {![info exists commitinfo($p)]} {
926 if {$noread} {
927 return {}
929 readcommit $p
931 if {$latest == {} || $cdate($p) > $latest} {
932 set level $k
933 set latest $cdate($p)
935 } else {
936 set level $k
937 break
941 if {$level < 0} {
942 if {$todo != {}} {
943 puts "ERROR: none of the pending commits can be done yet:"
944 foreach p $todo {
945 puts " $p ($ncleft($p))"
948 return -1
951 # If we are reducing, put in a null entry
952 if {$todol < $oldnlines} {
953 if {$nullentry >= 0} {
954 set i $nullentry
955 while {$i < $todol
956 && [lindex $oldtodo $i] == [lindex $todo $i]} {
957 incr i
959 } else {
960 set i $oldlevel
961 if {$level >= $i} {
962 incr i
965 if {$i < $todol} {
966 set todo [linsert $todo $i {}]
967 if {$level >= $i} {
968 incr level
972 return $level
975 proc drawcommit {id} {
976 global phase todo nchildren datemode nextupdate
977 global startcommits
979 if {$phase != "incrdraw"} {
980 set phase incrdraw
981 set todo $id
982 set startcommits $id
983 initgraph
984 drawcommitline 0
985 updatetodo 0 $datemode
986 } else {
987 if {$nchildren($id) == 0} {
988 lappend todo $id
989 lappend startcommits $id
991 set level [decidenext 1]
992 if {$level == {} || $id != [lindex $todo $level]} {
993 return
995 while 1 {
996 drawslants
997 drawcommitline $level
998 if {[updatetodo $level $datemode]} {
999 set level [decidenext 1]
1000 if {$level == {}} break
1002 set id [lindex $todo $level]
1003 if {![info exists commitlisted($id)]} {
1004 break
1006 if {[clock clicks -milliseconds] >= $nextupdate} {
1007 doupdate
1008 if {$stopped} break
1014 proc finishcommits {} {
1015 global phase
1016 global startcommits
1017 global canv mainfont ctext maincursor textcursor
1019 if {$phase != "incrdraw"} {
1020 $canv delete all
1021 $canv create text 3 3 -anchor nw -text "No commits selected" \
1022 -font $mainfont -tags textitems
1023 set phase {}
1024 } else {
1025 drawslants
1026 set level [decidenext]
1027 drawrest $level [llength $startcommits]
1029 . config -cursor $maincursor
1030 $ctext config -cursor $textcursor
1033 proc drawgraph {} {
1034 global nextupdate startmsecs startcommits todo
1036 if {$startcommits == {}} return
1037 set startmsecs [clock clicks -milliseconds]
1038 set nextupdate [expr $startmsecs + 100]
1039 initgraph
1040 set todo [lindex $startcommits 0]
1041 drawrest 0 1
1044 proc drawrest {level startix} {
1045 global phase stopped redisplaying selectedline
1046 global datemode currentparents todo
1047 global numcommits
1048 global nextupdate startmsecs startcommits idline
1050 if {$level >= 0} {
1051 set phase drawgraph
1052 set startid [lindex $startcommits $startix]
1053 set startline -1
1054 if {$startid != {}} {
1055 set startline $idline($startid)
1057 while 1 {
1058 if {$stopped} break
1059 drawcommitline $level
1060 set hard [updatetodo $level $datemode]
1061 if {$numcommits == $startline} {
1062 lappend todo $startid
1063 set hard 1
1064 incr startix
1065 set startid [lindex $startcommits $startix]
1066 set startline -1
1067 if {$startid != {}} {
1068 set startline $idline($startid)
1071 if {$hard} {
1072 set level [decidenext]
1073 if {$level < 0} break
1074 drawslants
1076 if {[clock clicks -milliseconds] >= $nextupdate} {
1077 update
1078 incr nextupdate 100
1082 set phase {}
1083 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1084 #puts "overall $drawmsecs ms for $numcommits commits"
1085 if {$redisplaying} {
1086 if {$stopped == 0 && [info exists selectedline]} {
1087 selectline $selectedline
1089 if {$stopped == 1} {
1090 set stopped 0
1091 after idle drawgraph
1092 } else {
1093 set redisplaying 0
1098 proc findmatches {f} {
1099 global findtype foundstring foundstrlen
1100 if {$findtype == "Regexp"} {
1101 set matches [regexp -indices -all -inline $foundstring $f]
1102 } else {
1103 if {$findtype == "IgnCase"} {
1104 set str [string tolower $f]
1105 } else {
1106 set str $f
1108 set matches {}
1109 set i 0
1110 while {[set j [string first $foundstring $str $i]] >= 0} {
1111 lappend matches [list $j [expr $j+$foundstrlen-1]]
1112 set i [expr $j + $foundstrlen]
1115 return $matches
1118 proc dofind {} {
1119 global findtype findloc findstring markedmatches commitinfo
1120 global numcommits lineid linehtag linentag linedtag
1121 global mainfont namefont canv canv2 canv3 selectedline
1122 global matchinglines foundstring foundstrlen
1123 unmarkmatches
1124 focus .
1125 set matchinglines {}
1126 set fldtypes {Headline Author Date Committer CDate Comment}
1127 if {$findtype == "IgnCase"} {
1128 set foundstring [string tolower $findstring]
1129 } else {
1130 set foundstring $findstring
1132 set foundstrlen [string length $findstring]
1133 if {$foundstrlen == 0} return
1134 if {![info exists selectedline]} {
1135 set oldsel -1
1136 } else {
1137 set oldsel $selectedline
1139 set didsel 0
1140 for {set l 0} {$l < $numcommits} {incr l} {
1141 set id $lineid($l)
1142 set info $commitinfo($id)
1143 set doesmatch 0
1144 foreach f $info ty $fldtypes {
1145 if {$findloc != "All fields" && $findloc != $ty} {
1146 continue
1148 set matches [findmatches $f]
1149 if {$matches == {}} continue
1150 set doesmatch 1
1151 if {$ty == "Headline"} {
1152 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1153 } elseif {$ty == "Author"} {
1154 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1155 } elseif {$ty == "Date"} {
1156 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1159 if {$doesmatch} {
1160 lappend matchinglines $l
1161 if {!$didsel && $l > $oldsel} {
1162 findselectline $l
1163 set didsel 1
1167 if {$matchinglines == {}} {
1168 bell
1169 } elseif {!$didsel} {
1170 findselectline [lindex $matchinglines 0]
1174 proc findselectline {l} {
1175 global findloc commentend ctext
1176 selectline $l
1177 if {$findloc == "All fields" || $findloc == "Comments"} {
1178 # highlight the matches in the comments
1179 set f [$ctext get 1.0 $commentend]
1180 set matches [findmatches $f]
1181 foreach match $matches {
1182 set start [lindex $match 0]
1183 set end [expr [lindex $match 1] + 1]
1184 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1189 proc findnext {} {
1190 global matchinglines selectedline
1191 if {![info exists matchinglines]} {
1192 dofind
1193 return
1195 if {![info exists selectedline]} return
1196 foreach l $matchinglines {
1197 if {$l > $selectedline} {
1198 findselectline $l
1199 return
1202 bell
1205 proc findprev {} {
1206 global matchinglines selectedline
1207 if {![info exists matchinglines]} {
1208 dofind
1209 return
1211 if {![info exists selectedline]} return
1212 set prev {}
1213 foreach l $matchinglines {
1214 if {$l >= $selectedline} break
1215 set prev $l
1217 if {$prev != {}} {
1218 findselectline $prev
1219 } else {
1220 bell
1224 proc markmatches {canv l str tag matches font} {
1225 set bbox [$canv bbox $tag]
1226 set x0 [lindex $bbox 0]
1227 set y0 [lindex $bbox 1]
1228 set y1 [lindex $bbox 3]
1229 foreach match $matches {
1230 set start [lindex $match 0]
1231 set end [lindex $match 1]
1232 if {$start > $end} continue
1233 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1234 set xlen [font measure $font [string range $str 0 [expr $end]]]
1235 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1236 -outline {} -tags matches -fill yellow]
1237 $canv lower $t
1241 proc unmarkmatches {} {
1242 global matchinglines
1243 allcanvs delete matches
1244 catch {unset matchinglines}
1247 proc selcanvline {w x y} {
1248 global canv canvy0 ctext linespc selectedline
1249 global lineid linehtag linentag linedtag rowtextx
1250 set ymax [lindex [$canv cget -scrollregion] 3]
1251 if {$ymax == {}} return
1252 set yfrac [lindex [$canv yview] 0]
1253 set y [expr {$y + $yfrac * $ymax}]
1254 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1255 if {$l < 0} {
1256 set l 0
1258 if {$w eq $canv} {
1259 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1261 unmarkmatches
1262 selectline $l
1265 proc selectline {l} {
1266 global canv canv2 canv3 ctext commitinfo selectedline
1267 global lineid linehtag linentag linedtag
1268 global canvy0 linespc parents nparents
1269 global cflist currentid sha1entry diffids
1270 global commentend seenfile idtags
1271 $canv delete hover
1272 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1273 $canv delete secsel
1274 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1275 -tags secsel -fill [$canv cget -selectbackground]]
1276 $canv lower $t
1277 $canv2 delete secsel
1278 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1279 -tags secsel -fill [$canv2 cget -selectbackground]]
1280 $canv2 lower $t
1281 $canv3 delete secsel
1282 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1283 -tags secsel -fill [$canv3 cget -selectbackground]]
1284 $canv3 lower $t
1285 set y [expr {$canvy0 + $l * $linespc}]
1286 set ymax [lindex [$canv cget -scrollregion] 3]
1287 set ytop [expr {$y - $linespc - 1}]
1288 set ybot [expr {$y + $linespc + 1}]
1289 set wnow [$canv yview]
1290 set wtop [expr [lindex $wnow 0] * $ymax]
1291 set wbot [expr [lindex $wnow 1] * $ymax]
1292 set wh [expr {$wbot - $wtop}]
1293 set newtop $wtop
1294 if {$ytop < $wtop} {
1295 if {$ybot < $wtop} {
1296 set newtop [expr {$y - $wh / 2.0}]
1297 } else {
1298 set newtop $ytop
1299 if {$newtop > $wtop - $linespc} {
1300 set newtop [expr {$wtop - $linespc}]
1303 } elseif {$ybot > $wbot} {
1304 if {$ytop > $wbot} {
1305 set newtop [expr {$y - $wh / 2.0}]
1306 } else {
1307 set newtop [expr {$ybot - $wh}]
1308 if {$newtop < $wtop + $linespc} {
1309 set newtop [expr {$wtop + $linespc}]
1313 if {$newtop != $wtop} {
1314 if {$newtop < 0} {
1315 set newtop 0
1317 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1319 set selectedline $l
1321 set id $lineid($l)
1322 set currentid $id
1323 set diffids [concat $id $parents($id)]
1324 $sha1entry delete 0 end
1325 $sha1entry insert 0 $id
1326 $sha1entry selection from 0
1327 $sha1entry selection to end
1329 $ctext conf -state normal
1330 $ctext delete 0.0 end
1331 $ctext mark set fmark.0 0.0
1332 $ctext mark gravity fmark.0 left
1333 set info $commitinfo($id)
1334 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1335 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1336 if {[info exists idtags($id)]} {
1337 $ctext insert end "Tags:"
1338 foreach tag $idtags($id) {
1339 $ctext insert end " $tag"
1341 $ctext insert end "\n"
1343 $ctext insert end "\n"
1344 $ctext insert end [lindex $info 5]
1345 $ctext insert end "\n"
1346 $ctext tag delete Comments
1347 $ctext tag remove found 1.0 end
1348 $ctext conf -state disabled
1349 set commentend [$ctext index "end - 1c"]
1351 $cflist delete 0 end
1352 $cflist insert end "Comments"
1353 if {$nparents($id) == 1} {
1354 startdiff
1356 catch {unset seenfile}
1359 proc startdiff {} {
1360 global treediffs diffids treepending
1362 if {![info exists treediffs($diffids)]} {
1363 if {![info exists treepending]} {
1364 gettreediffs $diffids
1366 } else {
1367 addtocflist $diffids
1371 proc selnextline {dir} {
1372 global selectedline
1373 if {![info exists selectedline]} return
1374 set l [expr $selectedline + $dir]
1375 unmarkmatches
1376 selectline $l
1379 proc addtocflist {ids} {
1380 global diffids treediffs cflist
1381 if {$ids != $diffids} {
1382 gettreediffs $diffids
1383 return
1385 foreach f $treediffs($ids) {
1386 $cflist insert end $f
1388 getblobdiffs $ids
1391 proc gettreediffs {ids} {
1392 global treediffs parents treepending
1393 set treepending $ids
1394 set treediffs($ids) {}
1395 set id [lindex $ids 0]
1396 set p [lindex $ids 1]
1397 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1398 fconfigure $gdtf -blocking 0
1399 fileevent $gdtf readable "gettreediffline $gdtf {$ids}"
1402 proc gettreediffline {gdtf ids} {
1403 global treediffs treepending
1404 set n [gets $gdtf line]
1405 if {$n < 0} {
1406 if {![eof $gdtf]} return
1407 close $gdtf
1408 unset treepending
1409 addtocflist $ids
1410 return
1412 set file [lindex $line 5]
1413 lappend treediffs($ids) $file
1416 proc getblobdiffs {ids} {
1417 global diffopts blobdifffd env curdifftag curtagstart
1418 global diffindex difffilestart nextupdate
1420 set id [lindex $ids 0]
1421 set p [lindex $ids 1]
1422 set env(GIT_DIFF_OPTS) $diffopts
1423 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1424 puts "error getting diffs: $err"
1425 return
1427 fconfigure $bdf -blocking 0
1428 set blobdifffd($ids) $bdf
1429 set curdifftag Comments
1430 set curtagstart 0.0
1431 set diffindex 0
1432 catch {unset difffilestart}
1433 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1434 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1437 proc getblobdiffline {bdf ids} {
1438 global diffids blobdifffd ctext curdifftag curtagstart seenfile
1439 global diffnexthead diffnextnote diffindex difffilestart
1440 global nextupdate
1442 set n [gets $bdf line]
1443 if {$n < 0} {
1444 if {[eof $bdf]} {
1445 close $bdf
1446 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1447 $ctext tag add $curdifftag $curtagstart end
1448 set seenfile($curdifftag) 1
1451 return
1453 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1454 return
1456 $ctext conf -state normal
1457 if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
1458 # start of a new file
1459 $ctext insert end "\n"
1460 $ctext tag add $curdifftag $curtagstart end
1461 set seenfile($curdifftag) 1
1462 set curtagstart [$ctext index "end - 1c"]
1463 set header $fname
1464 if {[info exists diffnexthead]} {
1465 set fname $diffnexthead
1466 set header "$diffnexthead ($diffnextnote)"
1467 unset diffnexthead
1469 set here [$ctext index "end - 1c"]
1470 set difffilestart($diffindex) $here
1471 incr diffindex
1472 # start mark names at fmark.1 for first file
1473 $ctext mark set fmark.$diffindex $here
1474 $ctext mark gravity fmark.$diffindex left
1475 set curdifftag "f:$fname"
1476 $ctext tag delete $curdifftag
1477 set l [expr {(78 - [string length $header]) / 2}]
1478 set pad [string range "----------------------------------------" 1 $l]
1479 $ctext insert end "$pad $header $pad\n" filesep
1480 } elseif {[string range $line 0 2] == "+++"} {
1481 # no need to do anything with this
1482 } elseif {[regexp {^Created: (.*) \((mode: *[0-7]*)\)} $line match fn m]} {
1483 set diffnexthead $fn
1484 set diffnextnote "created, mode $m"
1485 } elseif {[string range $line 0 8] == "Deleted: "} {
1486 set diffnexthead [string range $line 9 end]
1487 set diffnextnote "deleted"
1488 } elseif {[regexp {^diff --git a/(.*) b/} $line match fn]} {
1489 # save the filename in case the next thing is "new file mode ..."
1490 set diffnexthead $fn
1491 set diffnextnote "modified"
1492 } elseif {[regexp {^new file mode ([0-7]+)} $line match m]} {
1493 set diffnextnote "new file, mode $m"
1494 } elseif {[string range $line 0 11] == "deleted file"} {
1495 set diffnextnote "deleted"
1496 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1497 $line match f1l f1c f2l f2c rest]} {
1498 $ctext insert end "\t" hunksep
1499 $ctext insert end " $f1l " d0 " $f2l " d1
1500 $ctext insert end " $rest \n" hunksep
1501 } else {
1502 set x [string range $line 0 0]
1503 if {$x == "-" || $x == "+"} {
1504 set tag [expr {$x == "+"}]
1505 set line [string range $line 1 end]
1506 $ctext insert end "$line\n" d$tag
1507 } elseif {$x == " "} {
1508 set line [string range $line 1 end]
1509 $ctext insert end "$line\n"
1510 } elseif {$x == "\\"} {
1511 # e.g. "\ No newline at end of file"
1512 $ctext insert end "$line\n" filesep
1513 } else {
1514 # Something else we don't recognize
1515 if {$curdifftag != "Comments"} {
1516 $ctext insert end "\n"
1517 $ctext tag add $curdifftag $curtagstart end
1518 set seenfile($curdifftag) 1
1519 set curtagstart [$ctext index "end - 1c"]
1520 set curdifftag Comments
1522 $ctext insert end "$line\n" filesep
1525 $ctext conf -state disabled
1526 if {[clock clicks -milliseconds] >= $nextupdate} {
1527 incr nextupdate 100
1528 fileevent $bdf readable {}
1529 update
1530 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1534 proc nextfile {} {
1535 global difffilestart ctext
1536 set here [$ctext index @0,0]
1537 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1538 if {[$ctext compare $difffilestart($i) > $here]} {
1539 $ctext yview $difffilestart($i)
1540 break
1545 proc listboxsel {} {
1546 global ctext cflist currentid treediffs seenfile
1547 if {![info exists currentid]} return
1548 set sel [lsort [$cflist curselection]]
1549 if {$sel eq {}} return
1550 set first [lindex $sel 0]
1551 catch {$ctext yview fmark.$first}
1554 proc setcoords {} {
1555 global linespc charspc canvx0 canvy0 mainfont
1556 set linespc [font metrics $mainfont -linespace]
1557 set charspc [font measure $mainfont "m"]
1558 set canvy0 [expr 3 + 0.5 * $linespc]
1559 set canvx0 [expr 3 + 0.5 * $linespc]
1562 proc redisplay {} {
1563 global selectedline stopped redisplaying phase
1564 if {$stopped > 1} return
1565 if {$phase == "getcommits"} return
1566 set redisplaying 1
1567 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1568 set stopped 1
1569 } else {
1570 drawgraph
1574 proc incrfont {inc} {
1575 global mainfont namefont textfont selectedline ctext canv phase
1576 global stopped entries
1577 unmarkmatches
1578 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1579 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1580 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1581 setcoords
1582 $ctext conf -font $textfont
1583 $ctext tag conf filesep -font [concat $textfont bold]
1584 foreach e $entries {
1585 $e conf -font $mainfont
1587 if {$phase == "getcommits"} {
1588 $canv itemconf textitems -font $mainfont
1590 redisplay
1593 proc clearsha1 {} {
1594 global sha1entry sha1string
1595 if {[string length $sha1string] == 40} {
1596 $sha1entry delete 0 end
1600 proc sha1change {n1 n2 op} {
1601 global sha1string currentid sha1but
1602 if {$sha1string == {}
1603 || ([info exists currentid] && $sha1string == $currentid)} {
1604 set state disabled
1605 } else {
1606 set state normal
1608 if {[$sha1but cget -state] == $state} return
1609 if {$state == "normal"} {
1610 $sha1but conf -state normal -relief raised -text "Goto: "
1611 } else {
1612 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1616 proc gotocommit {} {
1617 global sha1string currentid idline tagids
1618 if {$sha1string == {}
1619 || ([info exists currentid] && $sha1string == $currentid)} return
1620 if {[info exists tagids($sha1string)]} {
1621 set id $tagids($sha1string)
1622 } else {
1623 set id [string tolower $sha1string]
1625 if {[info exists idline($id)]} {
1626 selectline $idline($id)
1627 return
1629 if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
1630 set type "SHA1 id"
1631 } else {
1632 set type "Tag"
1634 error_popup "$type $sha1string is not known"
1637 proc lineenter {x y id} {
1638 global hoverx hovery hoverid hovertimer
1639 global commitinfo canv
1641 if {![info exists commitinfo($id)]} return
1642 set hoverx $x
1643 set hovery $y
1644 set hoverid $id
1645 if {[info exists hovertimer]} {
1646 after cancel $hovertimer
1648 set hovertimer [after 500 linehover]
1649 $canv delete hover
1652 proc linemotion {x y id} {
1653 global hoverx hovery hoverid hovertimer
1655 if {[info exists hoverid] && $id == $hoverid} {
1656 set hoverx $x
1657 set hovery $y
1658 if {[info exists hovertimer]} {
1659 after cancel $hovertimer
1661 set hovertimer [after 500 linehover]
1665 proc lineleave {id} {
1666 global hoverid hovertimer canv
1668 if {[info exists hoverid] && $id == $hoverid} {
1669 $canv delete hover
1670 if {[info exists hovertimer]} {
1671 after cancel $hovertimer
1672 unset hovertimer
1674 unset hoverid
1678 proc linehover {} {
1679 global hoverx hovery hoverid hovertimer
1680 global canv linespc lthickness
1681 global commitinfo mainfont
1683 set text [lindex $commitinfo($hoverid) 0]
1684 set ymax [lindex [$canv cget -scrollregion] 3]
1685 if {$ymax == {}} return
1686 set yfrac [lindex [$canv yview] 0]
1687 set x [expr {$hoverx + 2 * $linespc}]
1688 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
1689 set x0 [expr {$x - 2 * $lthickness}]
1690 set y0 [expr {$y - 2 * $lthickness}]
1691 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
1692 set y1 [expr {$y + $linespc + 2 * $lthickness}]
1693 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
1694 -fill \#ffff80 -outline black -width 1 -tags hover]
1695 $canv raise $t
1696 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
1697 $canv raise $t
1700 proc lineclick {x y id} {
1701 global ctext commitinfo children cflist canv
1703 unmarkmatches
1704 $canv delete hover
1705 # fill the details pane with info about this line
1706 $ctext conf -state normal
1707 $ctext delete 0.0 end
1708 $ctext insert end "Parent:\n "
1709 catch {destroy $ctext.$id}
1710 button $ctext.$id -text "Go:" -command "selbyid $id" \
1711 -padx 4 -pady 0
1712 $ctext window create end -window $ctext.$id -align center
1713 set info $commitinfo($id)
1714 $ctext insert end "\t[lindex $info 0]\n"
1715 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
1716 $ctext insert end "\tDate:\t[lindex $info 2]\n"
1717 $ctext insert end "\tID:\t$id\n"
1718 if {[info exists children($id)]} {
1719 $ctext insert end "\nChildren:"
1720 foreach child $children($id) {
1721 $ctext insert end "\n "
1722 catch {destroy $ctext.$child}
1723 button $ctext.$child -text "Go:" -command "selbyid $child" \
1724 -padx 4 -pady 0
1725 $ctext window create end -window $ctext.$child -align center
1726 set info $commitinfo($child)
1727 $ctext insert end "\t[lindex $info 0]"
1730 $ctext conf -state disabled
1732 $cflist delete 0 end
1735 proc selbyid {id} {
1736 global idline
1737 if {[info exists idline($id)]} {
1738 selectline $idline($id)
1742 proc mstime {} {
1743 global startmstime
1744 if {![info exists startmstime]} {
1745 set startmstime [clock clicks -milliseconds]
1747 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
1750 proc rowmenu {x y id} {
1751 global rowctxmenu idline selectedline rowmenuid
1753 if {![info exists selectedline] || $idline($id) eq $selectedline} {
1754 set state disabled
1755 } else {
1756 set state normal
1758 $rowctxmenu entryconfigure 0 -state $state
1759 $rowctxmenu entryconfigure 1 -state $state
1760 $rowctxmenu entryconfigure 2 -state $state
1761 set rowmenuid $id
1762 tk_popup $rowctxmenu $x $y
1765 proc diffvssel {dirn} {
1766 global rowmenuid selectedline lineid
1767 global ctext cflist
1768 global diffids commitinfo
1770 if {![info exists selectedline]} return
1771 if {$dirn} {
1772 set oldid $lineid($selectedline)
1773 set newid $rowmenuid
1774 } else {
1775 set oldid $rowmenuid
1776 set newid $lineid($selectedline)
1778 $ctext conf -state normal
1779 $ctext delete 0.0 end
1780 $ctext mark set fmark.0 0.0
1781 $ctext mark gravity fmark.0 left
1782 $cflist delete 0 end
1783 $cflist insert end "Top"
1784 $ctext insert end "From $oldid\n "
1785 $ctext insert end [lindex $commitinfo($oldid) 0]
1786 $ctext insert end "\n\nTo $newid\n "
1787 $ctext insert end [lindex $commitinfo($newid) 0]
1788 $ctext insert end "\n"
1789 $ctext conf -state disabled
1790 $ctext tag delete Comments
1791 $ctext tag remove found 1.0 end
1792 set diffids [list $newid $oldid]
1793 startdiff
1796 proc mkpatch {} {
1797 global rowmenuid currentid commitinfo patchtop patchnum
1799 if {![info exists currentid]} return
1800 set oldid $currentid
1801 set oldhead [lindex $commitinfo($oldid) 0]
1802 set newid $rowmenuid
1803 set newhead [lindex $commitinfo($newid) 0]
1804 set top .patch
1805 set patchtop $top
1806 catch {destroy $top}
1807 toplevel $top
1808 label $top.title -text "Generate patch"
1809 grid $top.title -
1810 label $top.from -text "From:"
1811 entry $top.fromsha1 -width 40
1812 $top.fromsha1 insert 0 $oldid
1813 $top.fromsha1 conf -state readonly
1814 grid $top.from $top.fromsha1 -sticky w
1815 entry $top.fromhead -width 60
1816 $top.fromhead insert 0 $oldhead
1817 $top.fromhead conf -state readonly
1818 grid x $top.fromhead -sticky w
1819 label $top.to -text "To:"
1820 entry $top.tosha1 -width 40
1821 $top.tosha1 insert 0 $newid
1822 $top.tosha1 conf -state readonly
1823 grid $top.to $top.tosha1 -sticky w
1824 entry $top.tohead -width 60
1825 $top.tohead insert 0 $newhead
1826 $top.tohead conf -state readonly
1827 grid x $top.tohead -sticky w
1828 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
1829 grid $top.rev x -pady 10
1830 label $top.flab -text "Output file:"
1831 entry $top.fname -width 60
1832 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
1833 incr patchnum
1834 grid $top.flab $top.fname
1835 frame $top.buts
1836 button $top.buts.gen -text "Generate" -command mkpatchgo
1837 button $top.buts.can -text "Cancel" -command mkpatchcan
1838 grid $top.buts.gen $top.buts.can
1839 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1840 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1841 grid $top.buts - -pady 10 -sticky ew
1844 proc mkpatchrev {} {
1845 global patchtop
1847 set oldid [$patchtop.fromsha1 get]
1848 set oldhead [$patchtop.fromhead get]
1849 set newid [$patchtop.tosha1 get]
1850 set newhead [$patchtop.tohead get]
1851 foreach e [list fromsha1 fromhead tosha1 tohead] \
1852 v [list $newid $newhead $oldid $oldhead] {
1853 $patchtop.$e conf -state normal
1854 $patchtop.$e delete 0 end
1855 $patchtop.$e insert 0 $v
1856 $patchtop.$e conf -state readonly
1860 proc mkpatchgo {} {
1861 global patchtop
1863 set oldid [$patchtop.fromsha1 get]
1864 set newid [$patchtop.tosha1 get]
1865 set fname [$patchtop.fname get]
1866 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
1867 error_popup "Error creating patch: $err"
1869 catch {destroy $patchtop}
1870 unset patchtop
1873 proc mkpatchcan {} {
1874 global patchtop
1876 catch {destroy $patchtop}
1877 unset patchtop
1880 proc doquit {} {
1881 global stopped
1882 set stopped 100
1883 destroy .
1886 # defaults...
1887 set datemode 0
1888 set boldnames 0
1889 set diffopts "-U 5 -p"
1891 set mainfont {Helvetica 9}
1892 set textfont {Courier 9}
1894 set colors {green red blue magenta darkgrey brown orange}
1896 catch {source ~/.gitk}
1898 set namefont $mainfont
1899 if {$boldnames} {
1900 lappend namefont bold
1903 set revtreeargs {}
1904 foreach arg $argv {
1905 switch -regexp -- $arg {
1906 "^$" { }
1907 "^-b" { set boldnames 1 }
1908 "^-d" { set datemode 1 }
1909 default {
1910 lappend revtreeargs $arg
1915 set stopped 0
1916 set redisplaying 0
1917 set stuffsaved 0
1918 set patchnum 0
1919 setcoords
1920 makewindow
1921 readrefs
1922 getcommits $revtreeargs