Import the --topo-order change and fix the writing of ~/.gitk.
[git/gitweb.git] / gitk
blob96600b60d693f6cb6c60ad4a4c1f54f11a5468c9
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 --topo-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 # set it blocking so we wait for the process to terminate
64 fconfigure $commfd -blocking 1
65 if {![catch {close $commfd} err]} {
66 after idle finishcommits
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 findtypemenu findloc findstring fstring geometry
274 global entries sha1entry sha1string sha1but
275 global maincursor textcursor
276 global rowctxmenu gaudydiff mergemax
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 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
346 findtype Exact IgnCase Regexp]
347 set findloc "All fields"
348 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
349 Comments Author Committer Files Pickaxe
350 pack .ctop.top.bar.findloc -side right
351 pack .ctop.top.bar.findtype -side right
352 # for making sure type==Exact whenever loc==Pickaxe
353 trace add variable findloc write findlocchange
355 panedwindow .ctop.cdet -orient horizontal
356 .ctop add .ctop.cdet
357 frame .ctop.cdet.left
358 set ctext .ctop.cdet.left.ctext
359 text $ctext -bg white -state disabled -font $textfont \
360 -width $geometry(ctextw) -height $geometry(ctexth) \
361 -yscrollcommand ".ctop.cdet.left.sb set"
362 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
363 pack .ctop.cdet.left.sb -side right -fill y
364 pack $ctext -side left -fill both -expand 1
365 .ctop.cdet add .ctop.cdet.left
367 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
368 if {$gaudydiff} {
369 $ctext tag conf hunksep -back blue -fore white
370 $ctext tag conf d0 -back "#ff8080"
371 $ctext tag conf d1 -back green
372 } else {
373 $ctext tag conf hunksep -fore blue
374 $ctext tag conf d0 -fore red
375 $ctext tag conf d1 -fore "#00a000"
376 $ctext tag conf m0 -fore red
377 $ctext tag conf m1 -fore blue
378 $ctext tag conf m2 -fore green
379 $ctext tag conf m3 -fore purple
380 $ctext tag conf m4 -fore brown
381 $ctext tag conf mmax -fore darkgrey
382 set mergemax 5
383 $ctext tag conf mresult -font [concat $textfont bold]
384 $ctext tag conf msep -font [concat $textfont bold]
385 $ctext tag conf found -back yellow
388 frame .ctop.cdet.right
389 set cflist .ctop.cdet.right.cfiles
390 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
391 -yscrollcommand ".ctop.cdet.right.sb set"
392 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
393 pack .ctop.cdet.right.sb -side right -fill y
394 pack $cflist -side left -fill both -expand 1
395 .ctop.cdet add .ctop.cdet.right
396 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
398 pack .ctop -side top -fill both -expand 1
400 bindall <1> {selcanvline %W %x %y}
401 #bindall <B1-Motion> {selcanvline %W %x %y}
402 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
403 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
404 bindall <2> "allcanvs scan mark 0 %y"
405 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
406 bind . <Key-Up> "selnextline -1"
407 bind . <Key-Down> "selnextline 1"
408 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
409 bind . <Key-Next> "allcanvs yview scroll 1 pages"
410 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
411 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
412 bindkey <Key-space> "$ctext yview scroll 1 pages"
413 bindkey p "selnextline -1"
414 bindkey n "selnextline 1"
415 bindkey b "$ctext yview scroll -1 pages"
416 bindkey d "$ctext yview scroll 18 units"
417 bindkey u "$ctext yview scroll -18 units"
418 bindkey / {findnext 1}
419 bindkey <Key-Return> {findnext 0}
420 bindkey ? findprev
421 bindkey f nextfile
422 bind . <Control-q> doquit
423 bind . <Control-f> dofind
424 bind . <Control-g> {findnext 0}
425 bind . <Control-r> findprev
426 bind . <Control-equal> {incrfont 1}
427 bind . <Control-KP_Add> {incrfont 1}
428 bind . <Control-minus> {incrfont -1}
429 bind . <Control-KP_Subtract> {incrfont -1}
430 bind $cflist <<ListboxSelect>> listboxsel
431 bind . <Destroy> {savestuff %W}
432 bind . <Button-1> "click %W"
433 bind $fstring <Key-Return> dofind
434 bind $sha1entry <Key-Return> gotocommit
435 bind $sha1entry <<PasteSelection>> clearsha1
437 set maincursor [. cget -cursor]
438 set textcursor [$ctext cget -cursor]
440 set rowctxmenu .rowctxmenu
441 menu $rowctxmenu -tearoff 0
442 $rowctxmenu add command -label "Diff this -> selected" \
443 -command {diffvssel 0}
444 $rowctxmenu add command -label "Diff selected -> this" \
445 -command {diffvssel 1}
446 $rowctxmenu add command -label "Make patch" -command mkpatch
447 $rowctxmenu add command -label "Create tag" -command mktag
448 $rowctxmenu add command -label "Write commit to file" -command writecommit
451 # when we make a key binding for the toplevel, make sure
452 # it doesn't get triggered when that key is pressed in the
453 # find string entry widget.
454 proc bindkey {ev script} {
455 global entries
456 bind . $ev $script
457 set escript [bind Entry $ev]
458 if {$escript == {}} {
459 set escript [bind Entry <Key>]
461 foreach e $entries {
462 bind $e $ev "$escript; break"
466 # set the focus back to the toplevel for any click outside
467 # the entry widgets
468 proc click {w} {
469 global entries
470 foreach e $entries {
471 if {$w == $e} return
473 focus .
476 proc savestuff {w} {
477 global canv canv2 canv3 ctext cflist mainfont textfont
478 global stuffsaved findmergefiles gaudydiff
480 if {$stuffsaved} return
481 if {![winfo viewable .]} return
482 catch {
483 set f [open "~/.gitk-new" w]
484 puts $f [list set mainfont $mainfont]
485 puts $f [list set textfont $textfont]
486 puts $f [list set findmergefiles $findmergefiles]
487 puts $f [list set gaudydiff $gaudydiff]
488 puts $f "set geometry(width) [winfo width .ctop]"
489 puts $f "set geometry(height) [winfo height .ctop]"
490 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
491 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
492 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
493 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
494 set wid [expr {([winfo width $ctext] - 8) \
495 / [font measure $textfont "0"]}]
496 puts $f "set geometry(ctextw) $wid"
497 set wid [expr {([winfo width $cflist] - 11) \
498 / [font measure [$cflist cget -font] "0"]}]
499 puts $f "set geometry(cflistw) $wid"
500 close $f
501 file rename -force "~/.gitk-new" "~/.gitk"
503 set stuffsaved 1
506 proc resizeclistpanes {win w} {
507 global oldwidth
508 if [info exists oldwidth($win)] {
509 set s0 [$win sash coord 0]
510 set s1 [$win sash coord 1]
511 if {$w < 60} {
512 set sash0 [expr {int($w/2 - 2)}]
513 set sash1 [expr {int($w*5/6 - 2)}]
514 } else {
515 set factor [expr {1.0 * $w / $oldwidth($win)}]
516 set sash0 [expr {int($factor * [lindex $s0 0])}]
517 set sash1 [expr {int($factor * [lindex $s1 0])}]
518 if {$sash0 < 30} {
519 set sash0 30
521 if {$sash1 < $sash0 + 20} {
522 set sash1 [expr $sash0 + 20]
524 if {$sash1 > $w - 10} {
525 set sash1 [expr $w - 10]
526 if {$sash0 > $sash1 - 20} {
527 set sash0 [expr $sash1 - 20]
531 $win sash place 0 $sash0 [lindex $s0 1]
532 $win sash place 1 $sash1 [lindex $s1 1]
534 set oldwidth($win) $w
537 proc resizecdetpanes {win w} {
538 global oldwidth
539 if [info exists oldwidth($win)] {
540 set s0 [$win sash coord 0]
541 if {$w < 60} {
542 set sash0 [expr {int($w*3/4 - 2)}]
543 } else {
544 set factor [expr {1.0 * $w / $oldwidth($win)}]
545 set sash0 [expr {int($factor * [lindex $s0 0])}]
546 if {$sash0 < 45} {
547 set sash0 45
549 if {$sash0 > $w - 15} {
550 set sash0 [expr $w - 15]
553 $win sash place 0 $sash0 [lindex $s0 1]
555 set oldwidth($win) $w
558 proc allcanvs args {
559 global canv canv2 canv3
560 eval $canv $args
561 eval $canv2 $args
562 eval $canv3 $args
565 proc bindall {event action} {
566 global canv canv2 canv3
567 bind $canv $event $action
568 bind $canv2 $event $action
569 bind $canv3 $event $action
572 proc about {} {
573 set w .about
574 if {[winfo exists $w]} {
575 raise $w
576 return
578 toplevel $w
579 wm title $w "About gitk"
580 message $w.m -text {
581 Gitk version 1.2
583 Copyright © 2005 Paul Mackerras
585 Use and redistribute under the terms of the GNU General Public License} \
586 -justify center -aspect 400
587 pack $w.m -side top -fill x -padx 20 -pady 20
588 button $w.ok -text Close -command "destroy $w"
589 pack $w.ok -side bottom
592 proc assigncolor {id} {
593 global commitinfo colormap commcolors colors nextcolor
594 global parents nparents children nchildren
595 global cornercrossings crossings
597 if [info exists colormap($id)] return
598 set ncolors [llength $colors]
599 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
600 set child [lindex $children($id) 0]
601 if {[info exists colormap($child)]
602 && $nparents($child) == 1} {
603 set colormap($id) $colormap($child)
604 return
607 set badcolors {}
608 if {[info exists cornercrossings($id)]} {
609 foreach x $cornercrossings($id) {
610 if {[info exists colormap($x)]
611 && [lsearch -exact $badcolors $colormap($x)] < 0} {
612 lappend badcolors $colormap($x)
615 if {[llength $badcolors] >= $ncolors} {
616 set badcolors {}
619 set origbad $badcolors
620 if {[llength $badcolors] < $ncolors - 1} {
621 if {[info exists crossings($id)]} {
622 foreach x $crossings($id) {
623 if {[info exists colormap($x)]
624 && [lsearch -exact $badcolors $colormap($x)] < 0} {
625 lappend badcolors $colormap($x)
628 if {[llength $badcolors] >= $ncolors} {
629 set badcolors $origbad
632 set origbad $badcolors
634 if {[llength $badcolors] < $ncolors - 1} {
635 foreach child $children($id) {
636 if {[info exists colormap($child)]
637 && [lsearch -exact $badcolors $colormap($child)] < 0} {
638 lappend badcolors $colormap($child)
640 if {[info exists parents($child)]} {
641 foreach p $parents($child) {
642 if {[info exists colormap($p)]
643 && [lsearch -exact $badcolors $colormap($p)] < 0} {
644 lappend badcolors $colormap($p)
649 if {[llength $badcolors] >= $ncolors} {
650 set badcolors $origbad
653 for {set i 0} {$i <= $ncolors} {incr i} {
654 set c [lindex $colors $nextcolor]
655 if {[incr nextcolor] >= $ncolors} {
656 set nextcolor 0
658 if {[lsearch -exact $badcolors $c]} break
660 set colormap($id) $c
663 proc initgraph {} {
664 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
665 global mainline sidelines
666 global nchildren ncleft
668 allcanvs delete all
669 set nextcolor 0
670 set canvy $canvy0
671 set lineno -1
672 set numcommits 0
673 set lthickness [expr {int($linespc / 9) + 1}]
674 catch {unset mainline}
675 catch {unset sidelines}
676 foreach id [array names nchildren] {
677 set ncleft($id) $nchildren($id)
681 proc bindline {t id} {
682 global canv
684 $canv bind $t <Enter> "lineenter %x %y $id"
685 $canv bind $t <Motion> "linemotion %x %y $id"
686 $canv bind $t <Leave> "lineleave $id"
687 $canv bind $t <Button-1> "lineclick %x %y $id"
690 proc drawcommitline {level} {
691 global parents children nparents nchildren todo
692 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
693 global lineid linehtag linentag linedtag commitinfo
694 global colormap numcommits currentparents dupparents
695 global oldlevel oldnlines oldtodo
696 global idtags idline idheads
697 global lineno lthickness mainline sidelines
698 global commitlisted rowtextx idpos
700 incr numcommits
701 incr lineno
702 set id [lindex $todo $level]
703 set lineid($lineno) $id
704 set idline($id) $lineno
705 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
706 if {![info exists commitinfo($id)]} {
707 readcommit $id
708 if {![info exists commitinfo($id)]} {
709 set commitinfo($id) {"No commit information available"}
710 set nparents($id) 0
713 assigncolor $id
714 set currentparents {}
715 set dupparents {}
716 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
717 foreach p $parents($id) {
718 if {[lsearch -exact $currentparents $p] < 0} {
719 lappend currentparents $p
720 } else {
721 # remember that this parent was listed twice
722 lappend dupparents $p
726 set x [expr $canvx0 + $level * $linespc]
727 set y1 $canvy
728 set canvy [expr $canvy + $linespc]
729 allcanvs conf -scrollregion \
730 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
731 if {[info exists mainline($id)]} {
732 lappend mainline($id) $x $y1
733 set t [$canv create line $mainline($id) \
734 -width $lthickness -fill $colormap($id)]
735 $canv lower $t
736 bindline $t $id
738 if {[info exists sidelines($id)]} {
739 foreach ls $sidelines($id) {
740 set coords [lindex $ls 0]
741 set thick [lindex $ls 1]
742 set t [$canv create line $coords -fill $colormap($id) \
743 -width [expr {$thick * $lthickness}]]
744 $canv lower $t
745 bindline $t $id
748 set orad [expr {$linespc / 3}]
749 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
750 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
751 -fill $ofill -outline black -width 1]
752 $canv raise $t
753 $canv bind $t <1> {selcanvline {} %x %y}
754 set xt [expr $canvx0 + [llength $todo] * $linespc]
755 if {[llength $currentparents] > 2} {
756 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
758 set rowtextx($lineno) $xt
759 set idpos($id) [list $x $xt $y1]
760 if {[info exists idtags($id)] || [info exists idheads($id)]} {
761 set xt [drawtags $id $x $xt $y1]
763 set headline [lindex $commitinfo($id) 0]
764 set name [lindex $commitinfo($id) 1]
765 set date [lindex $commitinfo($id) 2]
766 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
767 -text $headline -font $mainfont ]
768 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
769 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
770 -text $name -font $namefont]
771 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
772 -text $date -font $mainfont]
775 proc drawtags {id x xt y1} {
776 global idtags idheads
777 global linespc lthickness
778 global canv mainfont
780 set marks {}
781 set ntags 0
782 if {[info exists idtags($id)]} {
783 set marks $idtags($id)
784 set ntags [llength $marks]
786 if {[info exists idheads($id)]} {
787 set marks [concat $marks $idheads($id)]
789 if {$marks eq {}} {
790 return $xt
793 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
794 set yt [expr $y1 - 0.5 * $linespc]
795 set yb [expr $yt + $linespc - 1]
796 set xvals {}
797 set wvals {}
798 foreach tag $marks {
799 set wid [font measure $mainfont $tag]
800 lappend xvals $xt
801 lappend wvals $wid
802 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
804 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
805 -width $lthickness -fill black -tags tag.$id]
806 $canv lower $t
807 foreach tag $marks x $xvals wid $wvals {
808 set xl [expr $x + $delta]
809 set xr [expr $x + $delta + $wid + $lthickness]
810 if {[incr ntags -1] >= 0} {
811 # draw a tag
812 $canv create polygon $x [expr $yt + $delta] $xl $yt\
813 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
814 -width 1 -outline black -fill yellow -tags tag.$id
815 } else {
816 # draw a head
817 set xl [expr $xl - $delta/2]
818 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
819 -width 1 -outline black -fill green -tags tag.$id
821 $canv create text $xl $y1 -anchor w -text $tag \
822 -font $mainfont -tags tag.$id
824 return $xt
827 proc updatetodo {level noshortcut} {
828 global currentparents ncleft todo
829 global mainline oldlevel oldtodo oldnlines
830 global canvx0 canvy linespc mainline
831 global commitinfo
833 set oldlevel $level
834 set oldtodo $todo
835 set oldnlines [llength $todo]
836 if {!$noshortcut && [llength $currentparents] == 1} {
837 set p [lindex $currentparents 0]
838 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
839 set ncleft($p) 0
840 set x [expr $canvx0 + $level * $linespc]
841 set y [expr $canvy - $linespc]
842 set mainline($p) [list $x $y]
843 set todo [lreplace $todo $level $level $p]
844 return 0
848 set todo [lreplace $todo $level $level]
849 set i $level
850 foreach p $currentparents {
851 incr ncleft($p) -1
852 set k [lsearch -exact $todo $p]
853 if {$k < 0} {
854 set todo [linsert $todo $i $p]
855 incr i
858 return 1
861 proc notecrossings {id lo hi corner} {
862 global oldtodo crossings cornercrossings
864 for {set i $lo} {[incr i] < $hi} {} {
865 set p [lindex $oldtodo $i]
866 if {$p == {}} continue
867 if {$i == $corner} {
868 if {![info exists cornercrossings($id)]
869 || [lsearch -exact $cornercrossings($id) $p] < 0} {
870 lappend cornercrossings($id) $p
872 if {![info exists cornercrossings($p)]
873 || [lsearch -exact $cornercrossings($p) $id] < 0} {
874 lappend cornercrossings($p) $id
876 } else {
877 if {![info exists crossings($id)]
878 || [lsearch -exact $crossings($id) $p] < 0} {
879 lappend crossings($id) $p
881 if {![info exists crossings($p)]
882 || [lsearch -exact $crossings($p) $id] < 0} {
883 lappend crossings($p) $id
889 proc drawslants {} {
890 global canv mainline sidelines canvx0 canvy linespc
891 global oldlevel oldtodo todo currentparents dupparents
892 global lthickness linespc canvy colormap
894 set y1 [expr $canvy - $linespc]
895 set y2 $canvy
896 set i -1
897 foreach id $oldtodo {
898 incr i
899 if {$id == {}} continue
900 set xi [expr {$canvx0 + $i * $linespc}]
901 if {$i == $oldlevel} {
902 foreach p $currentparents {
903 set j [lsearch -exact $todo $p]
904 set coords [list $xi $y1]
905 set xj [expr {$canvx0 + $j * $linespc}]
906 if {$j < $i - 1} {
907 lappend coords [expr $xj + $linespc] $y1
908 notecrossings $p $j $i [expr {$j + 1}]
909 } elseif {$j > $i + 1} {
910 lappend coords [expr $xj - $linespc] $y1
911 notecrossings $p $i $j [expr {$j - 1}]
913 if {[lsearch -exact $dupparents $p] >= 0} {
914 # draw a double-width line to indicate the doubled parent
915 lappend coords $xj $y2
916 lappend sidelines($p) [list $coords 2]
917 if {![info exists mainline($p)]} {
918 set mainline($p) [list $xj $y2]
920 } else {
921 # normal case, no parent duplicated
922 if {![info exists mainline($p)]} {
923 if {$i != $j} {
924 lappend coords $xj $y2
926 set mainline($p) $coords
927 } else {
928 lappend coords $xj $y2
929 lappend sidelines($p) [list $coords 1]
933 } elseif {[lindex $todo $i] != $id} {
934 set j [lsearch -exact $todo $id]
935 set xj [expr {$canvx0 + $j * $linespc}]
936 lappend mainline($id) $xi $y1 $xj $y2
941 proc decidenext {{noread 0}} {
942 global parents children nchildren ncleft todo
943 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
944 global datemode cdate
945 global commitinfo
946 global currentparents oldlevel oldnlines oldtodo
947 global lineno lthickness
949 # remove the null entry if present
950 set nullentry [lsearch -exact $todo {}]
951 if {$nullentry >= 0} {
952 set todo [lreplace $todo $nullentry $nullentry]
955 # choose which one to do next time around
956 set todol [llength $todo]
957 set level -1
958 set latest {}
959 for {set k $todol} {[incr k -1] >= 0} {} {
960 set p [lindex $todo $k]
961 if {$ncleft($p) == 0} {
962 if {$datemode} {
963 if {![info exists commitinfo($p)]} {
964 if {$noread} {
965 return {}
967 readcommit $p
969 if {$latest == {} || $cdate($p) > $latest} {
970 set level $k
971 set latest $cdate($p)
973 } else {
974 set level $k
975 break
979 if {$level < 0} {
980 if {$todo != {}} {
981 puts "ERROR: none of the pending commits can be done yet:"
982 foreach p $todo {
983 puts " $p ($ncleft($p))"
986 return -1
989 # If we are reducing, put in a null entry
990 if {$todol < $oldnlines} {
991 if {$nullentry >= 0} {
992 set i $nullentry
993 while {$i < $todol
994 && [lindex $oldtodo $i] == [lindex $todo $i]} {
995 incr i
997 } else {
998 set i $oldlevel
999 if {$level >= $i} {
1000 incr i
1003 if {$i < $todol} {
1004 set todo [linsert $todo $i {}]
1005 if {$level >= $i} {
1006 incr level
1010 return $level
1013 proc drawcommit {id} {
1014 global phase todo nchildren datemode nextupdate
1015 global startcommits
1017 if {$phase != "incrdraw"} {
1018 set phase incrdraw
1019 set todo $id
1020 set startcommits $id
1021 initgraph
1022 drawcommitline 0
1023 updatetodo 0 $datemode
1024 } else {
1025 if {$nchildren($id) == 0} {
1026 lappend todo $id
1027 lappend startcommits $id
1029 set level [decidenext 1]
1030 if {$level == {} || $id != [lindex $todo $level]} {
1031 return
1033 while 1 {
1034 drawslants
1035 drawcommitline $level
1036 if {[updatetodo $level $datemode]} {
1037 set level [decidenext 1]
1038 if {$level == {}} break
1040 set id [lindex $todo $level]
1041 if {![info exists commitlisted($id)]} {
1042 break
1044 if {[clock clicks -milliseconds] >= $nextupdate} {
1045 doupdate
1046 if {$stopped} break
1052 proc finishcommits {} {
1053 global phase
1054 global startcommits
1055 global canv mainfont ctext maincursor textcursor
1057 if {$phase != "incrdraw"} {
1058 $canv delete all
1059 $canv create text 3 3 -anchor nw -text "No commits selected" \
1060 -font $mainfont -tags textitems
1061 set phase {}
1062 } else {
1063 drawslants
1064 set level [decidenext]
1065 drawrest $level [llength $startcommits]
1067 . config -cursor $maincursor
1068 $ctext config -cursor $textcursor
1071 proc drawgraph {} {
1072 global nextupdate startmsecs startcommits todo
1074 if {$startcommits == {}} return
1075 set startmsecs [clock clicks -milliseconds]
1076 set nextupdate [expr $startmsecs + 100]
1077 initgraph
1078 set todo [lindex $startcommits 0]
1079 drawrest 0 1
1082 proc drawrest {level startix} {
1083 global phase stopped redisplaying selectedline
1084 global datemode currentparents todo
1085 global numcommits
1086 global nextupdate startmsecs startcommits idline
1088 if {$level >= 0} {
1089 set phase drawgraph
1090 set startid [lindex $startcommits $startix]
1091 set startline -1
1092 if {$startid != {}} {
1093 set startline $idline($startid)
1095 while 1 {
1096 if {$stopped} break
1097 drawcommitline $level
1098 set hard [updatetodo $level $datemode]
1099 if {$numcommits == $startline} {
1100 lappend todo $startid
1101 set hard 1
1102 incr startix
1103 set startid [lindex $startcommits $startix]
1104 set startline -1
1105 if {$startid != {}} {
1106 set startline $idline($startid)
1109 if {$hard} {
1110 set level [decidenext]
1111 if {$level < 0} break
1112 drawslants
1114 if {[clock clicks -milliseconds] >= $nextupdate} {
1115 update
1116 incr nextupdate 100
1120 set phase {}
1121 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1122 #puts "overall $drawmsecs ms for $numcommits commits"
1123 if {$redisplaying} {
1124 if {$stopped == 0 && [info exists selectedline]} {
1125 selectline $selectedline
1127 if {$stopped == 1} {
1128 set stopped 0
1129 after idle drawgraph
1130 } else {
1131 set redisplaying 0
1136 proc findmatches {f} {
1137 global findtype foundstring foundstrlen
1138 if {$findtype == "Regexp"} {
1139 set matches [regexp -indices -all -inline $foundstring $f]
1140 } else {
1141 if {$findtype == "IgnCase"} {
1142 set str [string tolower $f]
1143 } else {
1144 set str $f
1146 set matches {}
1147 set i 0
1148 while {[set j [string first $foundstring $str $i]] >= 0} {
1149 lappend matches [list $j [expr $j+$foundstrlen-1]]
1150 set i [expr $j + $foundstrlen]
1153 return $matches
1156 proc dofind {} {
1157 global findtype findloc findstring markedmatches commitinfo
1158 global numcommits lineid linehtag linentag linedtag
1159 global mainfont namefont canv canv2 canv3 selectedline
1160 global matchinglines foundstring foundstrlen
1162 stopfindproc
1163 unmarkmatches
1164 focus .
1165 set matchinglines {}
1166 if {$findloc == "Pickaxe"} {
1167 findpatches
1168 return
1170 if {$findtype == "IgnCase"} {
1171 set foundstring [string tolower $findstring]
1172 } else {
1173 set foundstring $findstring
1175 set foundstrlen [string length $findstring]
1176 if {$foundstrlen == 0} return
1177 if {$findloc == "Files"} {
1178 findfiles
1179 return
1181 if {![info exists selectedline]} {
1182 set oldsel -1
1183 } else {
1184 set oldsel $selectedline
1186 set didsel 0
1187 set fldtypes {Headline Author Date Committer CDate Comment}
1188 for {set l 0} {$l < $numcommits} {incr l} {
1189 set id $lineid($l)
1190 set info $commitinfo($id)
1191 set doesmatch 0
1192 foreach f $info ty $fldtypes {
1193 if {$findloc != "All fields" && $findloc != $ty} {
1194 continue
1196 set matches [findmatches $f]
1197 if {$matches == {}} continue
1198 set doesmatch 1
1199 if {$ty == "Headline"} {
1200 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1201 } elseif {$ty == "Author"} {
1202 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1203 } elseif {$ty == "Date"} {
1204 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1207 if {$doesmatch} {
1208 lappend matchinglines $l
1209 if {!$didsel && $l > $oldsel} {
1210 findselectline $l
1211 set didsel 1
1215 if {$matchinglines == {}} {
1216 bell
1217 } elseif {!$didsel} {
1218 findselectline [lindex $matchinglines 0]
1222 proc findselectline {l} {
1223 global findloc commentend ctext
1224 selectline $l
1225 if {$findloc == "All fields" || $findloc == "Comments"} {
1226 # highlight the matches in the comments
1227 set f [$ctext get 1.0 $commentend]
1228 set matches [findmatches $f]
1229 foreach match $matches {
1230 set start [lindex $match 0]
1231 set end [expr [lindex $match 1] + 1]
1232 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1237 proc findnext {restart} {
1238 global matchinglines selectedline
1239 if {![info exists matchinglines]} {
1240 if {$restart} {
1241 dofind
1243 return
1245 if {![info exists selectedline]} return
1246 foreach l $matchinglines {
1247 if {$l > $selectedline} {
1248 findselectline $l
1249 return
1252 bell
1255 proc findprev {} {
1256 global matchinglines selectedline
1257 if {![info exists matchinglines]} {
1258 dofind
1259 return
1261 if {![info exists selectedline]} return
1262 set prev {}
1263 foreach l $matchinglines {
1264 if {$l >= $selectedline} break
1265 set prev $l
1267 if {$prev != {}} {
1268 findselectline $prev
1269 } else {
1270 bell
1274 proc findlocchange {name ix op} {
1275 global findloc findtype findtypemenu
1276 if {$findloc == "Pickaxe"} {
1277 set findtype Exact
1278 set state disabled
1279 } else {
1280 set state normal
1282 $findtypemenu entryconf 1 -state $state
1283 $findtypemenu entryconf 2 -state $state
1286 proc stopfindproc {{done 0}} {
1287 global findprocpid findprocfile findids
1288 global ctext findoldcursor phase maincursor textcursor
1289 global findinprogress
1291 catch {unset findids}
1292 if {[info exists findprocpid]} {
1293 if {!$done} {
1294 catch {exec kill $findprocpid}
1296 catch {close $findprocfile}
1297 unset findprocpid
1299 if {[info exists findinprogress]} {
1300 unset findinprogress
1301 if {$phase != "incrdraw"} {
1302 . config -cursor $maincursor
1303 $ctext config -cursor $textcursor
1308 proc findpatches {} {
1309 global findstring selectedline numcommits
1310 global findprocpid findprocfile
1311 global finddidsel ctext lineid findinprogress
1312 global findinsertpos
1314 if {$numcommits == 0} return
1316 # make a list of all the ids to search, starting at the one
1317 # after the selected line (if any)
1318 if {[info exists selectedline]} {
1319 set l $selectedline
1320 } else {
1321 set l -1
1323 set inputids {}
1324 for {set i 0} {$i < $numcommits} {incr i} {
1325 if {[incr l] >= $numcommits} {
1326 set l 0
1328 append inputids $lineid($l) "\n"
1331 if {[catch {
1332 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1333 << $inputids] r]
1334 } err]} {
1335 error_popup "Error starting search process: $err"
1336 return
1339 set findinsertpos end
1340 set findprocfile $f
1341 set findprocpid [pid $f]
1342 fconfigure $f -blocking 0
1343 fileevent $f readable readfindproc
1344 set finddidsel 0
1345 . config -cursor watch
1346 $ctext config -cursor watch
1347 set findinprogress 1
1350 proc readfindproc {} {
1351 global findprocfile finddidsel
1352 global idline matchinglines findinsertpos
1354 set n [gets $findprocfile line]
1355 if {$n < 0} {
1356 if {[eof $findprocfile]} {
1357 stopfindproc 1
1358 if {!$finddidsel} {
1359 bell
1362 return
1364 if {![regexp {^[0-9a-f]{40}} $line id]} {
1365 error_popup "Can't parse git-diff-tree output: $line"
1366 stopfindproc
1367 return
1369 if {![info exists idline($id)]} {
1370 puts stderr "spurious id: $id"
1371 return
1373 set l $idline($id)
1374 insertmatch $l $id
1377 proc insertmatch {l id} {
1378 global matchinglines findinsertpos finddidsel
1380 if {$findinsertpos == "end"} {
1381 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1382 set matchinglines [linsert $matchinglines 0 $l]
1383 set findinsertpos 1
1384 } else {
1385 lappend matchinglines $l
1387 } else {
1388 set matchinglines [linsert $matchinglines $findinsertpos $l]
1389 incr findinsertpos
1391 markheadline $l $id
1392 if {!$finddidsel} {
1393 findselectline $l
1394 set finddidsel 1
1398 proc findfiles {} {
1399 global selectedline numcommits lineid ctext
1400 global ffileline finddidsel parents nparents
1401 global findinprogress findstartline findinsertpos
1402 global treediffs fdiffids fdiffsneeded fdiffpos
1403 global findmergefiles
1405 if {$numcommits == 0} return
1407 if {[info exists selectedline]} {
1408 set l [expr {$selectedline + 1}]
1409 } else {
1410 set l 0
1412 set ffileline $l
1413 set findstartline $l
1414 set diffsneeded {}
1415 set fdiffsneeded {}
1416 while 1 {
1417 set id $lineid($l)
1418 if {$findmergefiles || $nparents($id) == 1} {
1419 foreach p $parents($id) {
1420 if {![info exists treediffs([list $id $p])]} {
1421 append diffsneeded "$id $p\n"
1422 lappend fdiffsneeded [list $id $p]
1426 if {[incr l] >= $numcommits} {
1427 set l 0
1429 if {$l == $findstartline} break
1432 # start off a git-diff-tree process if needed
1433 if {$diffsneeded ne {}} {
1434 if {[catch {
1435 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1436 } err ]} {
1437 error_popup "Error starting search process: $err"
1438 return
1440 catch {unset fdiffids}
1441 set fdiffpos 0
1442 fconfigure $df -blocking 0
1443 fileevent $df readable [list readfilediffs $df]
1446 set finddidsel 0
1447 set findinsertpos end
1448 set id $lineid($l)
1449 set p [lindex $parents($id) 0]
1450 . config -cursor watch
1451 $ctext config -cursor watch
1452 set findinprogress 1
1453 findcont [list $id $p]
1454 update
1457 proc readfilediffs {df} {
1458 global findids fdiffids fdiffs
1460 set n [gets $df line]
1461 if {$n < 0} {
1462 if {[eof $df]} {
1463 donefilediff
1464 if {[catch {close $df} err]} {
1465 stopfindproc
1466 bell
1467 error_popup "Error in git-diff-tree: $err"
1468 } elseif {[info exists findids]} {
1469 set ids $findids
1470 stopfindproc
1471 bell
1472 error_popup "Couldn't find diffs for {$ids}"
1475 return
1477 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1478 # start of a new string of diffs
1479 donefilediff
1480 set fdiffids [list $id $p]
1481 set fdiffs {}
1482 } elseif {[string match ":*" $line]} {
1483 lappend fdiffs [lindex $line 5]
1487 proc donefilediff {} {
1488 global fdiffids fdiffs treediffs findids
1489 global fdiffsneeded fdiffpos
1491 if {[info exists fdiffids]} {
1492 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1493 && $fdiffpos < [llength $fdiffsneeded]} {
1494 # git-diff-tree doesn't output anything for a commit
1495 # which doesn't change anything
1496 set nullids [lindex $fdiffsneeded $fdiffpos]
1497 set treediffs($nullids) {}
1498 if {[info exists findids] && $nullids eq $findids} {
1499 unset findids
1500 findcont $nullids
1502 incr fdiffpos
1504 incr fdiffpos
1506 if {![info exists treediffs($fdiffids)]} {
1507 set treediffs($fdiffids) $fdiffs
1509 if {[info exists findids] && $fdiffids eq $findids} {
1510 unset findids
1511 findcont $fdiffids
1516 proc findcont {ids} {
1517 global findids treediffs parents nparents
1518 global ffileline findstartline finddidsel
1519 global lineid numcommits matchinglines findinprogress
1520 global findmergefiles
1522 set id [lindex $ids 0]
1523 set p [lindex $ids 1]
1524 set pi [lsearch -exact $parents($id) $p]
1525 set l $ffileline
1526 while 1 {
1527 if {$findmergefiles || $nparents($id) == 1} {
1528 if {![info exists treediffs($ids)]} {
1529 set findids $ids
1530 set ffileline $l
1531 return
1533 set doesmatch 0
1534 foreach f $treediffs($ids) {
1535 set x [findmatches $f]
1536 if {$x != {}} {
1537 set doesmatch 1
1538 break
1541 if {$doesmatch} {
1542 insertmatch $l $id
1543 set pi $nparents($id)
1545 } else {
1546 set pi $nparents($id)
1548 if {[incr pi] >= $nparents($id)} {
1549 set pi 0
1550 if {[incr l] >= $numcommits} {
1551 set l 0
1553 if {$l == $findstartline} break
1554 set id $lineid($l)
1556 set p [lindex $parents($id) $pi]
1557 set ids [list $id $p]
1559 stopfindproc
1560 if {!$finddidsel} {
1561 bell
1565 # mark a commit as matching by putting a yellow background
1566 # behind the headline
1567 proc markheadline {l id} {
1568 global canv mainfont linehtag commitinfo
1570 set bbox [$canv bbox $linehtag($l)]
1571 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1572 $canv lower $t
1575 # mark the bits of a headline, author or date that match a find string
1576 proc markmatches {canv l str tag matches font} {
1577 set bbox [$canv bbox $tag]
1578 set x0 [lindex $bbox 0]
1579 set y0 [lindex $bbox 1]
1580 set y1 [lindex $bbox 3]
1581 foreach match $matches {
1582 set start [lindex $match 0]
1583 set end [lindex $match 1]
1584 if {$start > $end} continue
1585 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1586 set xlen [font measure $font [string range $str 0 [expr $end]]]
1587 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1588 -outline {} -tags matches -fill yellow]
1589 $canv lower $t
1593 proc unmarkmatches {} {
1594 global matchinglines findids
1595 allcanvs delete matches
1596 catch {unset matchinglines}
1597 catch {unset findids}
1600 proc selcanvline {w x y} {
1601 global canv canvy0 ctext linespc selectedline
1602 global lineid linehtag linentag linedtag rowtextx
1603 set ymax [lindex [$canv cget -scrollregion] 3]
1604 if {$ymax == {}} return
1605 set yfrac [lindex [$canv yview] 0]
1606 set y [expr {$y + $yfrac * $ymax}]
1607 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1608 if {$l < 0} {
1609 set l 0
1611 if {$w eq $canv} {
1612 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1614 unmarkmatches
1615 selectline $l
1618 proc selectline {l} {
1619 global canv canv2 canv3 ctext commitinfo selectedline
1620 global lineid linehtag linentag linedtag
1621 global canvy0 linespc parents nparents
1622 global cflist currentid sha1entry
1623 global commentend idtags
1624 $canv delete hover
1625 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1626 $canv delete secsel
1627 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1628 -tags secsel -fill [$canv cget -selectbackground]]
1629 $canv lower $t
1630 $canv2 delete secsel
1631 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1632 -tags secsel -fill [$canv2 cget -selectbackground]]
1633 $canv2 lower $t
1634 $canv3 delete secsel
1635 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1636 -tags secsel -fill [$canv3 cget -selectbackground]]
1637 $canv3 lower $t
1638 set y [expr {$canvy0 + $l * $linespc}]
1639 set ymax [lindex [$canv cget -scrollregion] 3]
1640 set ytop [expr {$y - $linespc - 1}]
1641 set ybot [expr {$y + $linespc + 1}]
1642 set wnow [$canv yview]
1643 set wtop [expr [lindex $wnow 0] * $ymax]
1644 set wbot [expr [lindex $wnow 1] * $ymax]
1645 set wh [expr {$wbot - $wtop}]
1646 set newtop $wtop
1647 if {$ytop < $wtop} {
1648 if {$ybot < $wtop} {
1649 set newtop [expr {$y - $wh / 2.0}]
1650 } else {
1651 set newtop $ytop
1652 if {$newtop > $wtop - $linespc} {
1653 set newtop [expr {$wtop - $linespc}]
1656 } elseif {$ybot > $wbot} {
1657 if {$ytop > $wbot} {
1658 set newtop [expr {$y - $wh / 2.0}]
1659 } else {
1660 set newtop [expr {$ybot - $wh}]
1661 if {$newtop < $wtop + $linespc} {
1662 set newtop [expr {$wtop + $linespc}]
1666 if {$newtop != $wtop} {
1667 if {$newtop < 0} {
1668 set newtop 0
1670 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1672 set selectedline $l
1674 set id $lineid($l)
1675 set currentid $id
1676 $sha1entry delete 0 end
1677 $sha1entry insert 0 $id
1678 $sha1entry selection from 0
1679 $sha1entry selection to end
1681 $ctext conf -state normal
1682 $ctext delete 0.0 end
1683 $ctext mark set fmark.0 0.0
1684 $ctext mark gravity fmark.0 left
1685 set info $commitinfo($id)
1686 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1687 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1688 if {[info exists idtags($id)]} {
1689 $ctext insert end "Tags:"
1690 foreach tag $idtags($id) {
1691 $ctext insert end " $tag"
1693 $ctext insert end "\n"
1695 $ctext insert end "\n"
1696 $ctext insert end [lindex $info 5]
1697 $ctext insert end "\n"
1698 $ctext tag delete Comments
1699 $ctext tag remove found 1.0 end
1700 $ctext conf -state disabled
1701 set commentend [$ctext index "end - 1c"]
1703 $cflist delete 0 end
1704 $cflist insert end "Comments"
1705 if {$nparents($id) == 1} {
1706 startdiff [concat $id $parents($id)]
1707 } elseif {$nparents($id) > 1} {
1708 mergediff $id
1712 proc selnextline {dir} {
1713 global selectedline
1714 if {![info exists selectedline]} return
1715 set l [expr $selectedline + $dir]
1716 unmarkmatches
1717 selectline $l
1720 proc mergediff {id} {
1721 global parents diffmergeid diffmergegca mergefilelist diffpindex
1723 set diffmergeid $id
1724 set diffpindex -1
1725 set diffmergegca [findgca $parents($id)]
1726 if {[info exists mergefilelist($id)]} {
1727 showmergediff
1728 } else {
1729 contmergediff {}
1733 proc findgca {ids} {
1734 set gca {}
1735 foreach id $ids {
1736 if {$gca eq {}} {
1737 set gca $id
1738 } else {
1739 if {[catch {
1740 set gca [exec git-merge-base $gca $id]
1741 } err]} {
1742 return {}
1746 return $gca
1749 proc contmergediff {ids} {
1750 global diffmergeid diffpindex parents nparents diffmergegca
1751 global treediffs mergefilelist diffids
1753 # diff the child against each of the parents, and diff
1754 # each of the parents against the GCA.
1755 while 1 {
1756 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1757 set ids [list [lindex $ids 1] $diffmergegca]
1758 } else {
1759 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1760 set p [lindex $parents($diffmergeid) $diffpindex]
1761 set ids [list $diffmergeid $p]
1763 if {![info exists treediffs($ids)]} {
1764 set diffids $ids
1765 if {![info exists treepending]} {
1766 gettreediffs $ids
1768 return
1772 # If a file in some parent is different from the child and also
1773 # different from the GCA, then it's interesting.
1774 # If we don't have a GCA, then a file is interesting if it is
1775 # different from the child in all the parents.
1776 if {$diffmergegca ne {}} {
1777 set files {}
1778 foreach p $parents($diffmergeid) {
1779 set gcadiffs $treediffs([list $p $diffmergegca])
1780 foreach f $treediffs([list $diffmergeid $p]) {
1781 if {[lsearch -exact $files $f] < 0
1782 && [lsearch -exact $gcadiffs $f] >= 0} {
1783 lappend files $f
1787 set files [lsort $files]
1788 } else {
1789 set p [lindex $parents($diffmergeid) 0]
1790 set files $treediffs([list $diffmergeid $p])
1791 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1792 set p [lindex $parents($diffmergeid) $i]
1793 set df $treediffs([list $diffmergeid $p])
1794 set nf {}
1795 foreach f $files {
1796 if {[lsearch -exact $df $f] >= 0} {
1797 lappend nf $f
1800 set files $nf
1804 set mergefilelist($diffmergeid) $files
1805 if {$files ne {}} {
1806 showmergediff
1810 proc showmergediff {} {
1811 global cflist diffmergeid mergefilelist parents
1812 global diffopts diffinhunk currentfile diffblocked
1813 global groupfilelast mergefds
1815 set files $mergefilelist($diffmergeid)
1816 foreach f $files {
1817 $cflist insert end $f
1819 set env(GIT_DIFF_OPTS) $diffopts
1820 set flist {}
1821 catch {unset currentfile}
1822 catch {unset currenthunk}
1823 catch {unset filelines}
1824 set groupfilelast -1
1825 foreach p $parents($diffmergeid) {
1826 set cmd [list | git-diff-tree -p $p $diffmergeid]
1827 set cmd [concat $cmd $mergefilelist($diffmergeid)]
1828 if {[catch {set f [open $cmd r]} err]} {
1829 error_popup "Error getting diffs: $err"
1830 foreach f $flist {
1831 catch {close $f}
1833 return
1835 lappend flist $f
1836 set ids [list $diffmergeid $p]
1837 set mergefds($ids) $f
1838 set diffinhunk($ids) 0
1839 set diffblocked($ids) 0
1840 fconfigure $f -blocking 0
1841 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
1845 proc getmergediffline {f ids id} {
1846 global diffmergeid diffinhunk diffoldlines diffnewlines
1847 global currentfile currenthunk
1848 global diffoldstart diffnewstart diffoldlno diffnewlno
1849 global diffblocked mergefilelist
1850 global noldlines nnewlines difflcounts filelines
1852 set n [gets $f line]
1853 if {$n < 0} {
1854 if {![eof $f]} return
1857 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
1858 if {$n < 0} {
1859 close $f
1861 return
1864 if {$diffinhunk($ids) != 0} {
1865 set fi $currentfile($ids)
1866 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
1867 # continuing an existing hunk
1868 set line [string range $line 1 end]
1869 set p [lindex $ids 1]
1870 if {$match eq "-" || $match eq " "} {
1871 set filelines($p,$fi,$diffoldlno($ids)) $line
1872 incr diffoldlno($ids)
1874 if {$match eq "+" || $match eq " "} {
1875 set filelines($id,$fi,$diffnewlno($ids)) $line
1876 incr diffnewlno($ids)
1878 if {$match eq " "} {
1879 if {$diffinhunk($ids) == 2} {
1880 lappend difflcounts($ids) \
1881 [list $noldlines($ids) $nnewlines($ids)]
1882 set noldlines($ids) 0
1883 set diffinhunk($ids) 1
1885 incr noldlines($ids)
1886 } elseif {$match eq "-" || $match eq "+"} {
1887 if {$diffinhunk($ids) == 1} {
1888 lappend difflcounts($ids) [list $noldlines($ids)]
1889 set noldlines($ids) 0
1890 set nnewlines($ids) 0
1891 set diffinhunk($ids) 2
1893 if {$match eq "-"} {
1894 incr noldlines($ids)
1895 } else {
1896 incr nnewlines($ids)
1899 # and if it's \ No newline at end of line, then what?
1900 return
1902 # end of a hunk
1903 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
1904 lappend difflcounts($ids) [list $noldlines($ids)]
1905 } elseif {$diffinhunk($ids) == 2
1906 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
1907 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
1909 set currenthunk($ids) [list $currentfile($ids) \
1910 $diffoldstart($ids) $diffnewstart($ids) \
1911 $diffoldlno($ids) $diffnewlno($ids) \
1912 $difflcounts($ids)]
1913 set diffinhunk($ids) 0
1914 # -1 = need to block, 0 = unblocked, 1 = is blocked
1915 set diffblocked($ids) -1
1916 processhunks
1917 if {$diffblocked($ids) == -1} {
1918 fileevent $f readable {}
1919 set diffblocked($ids) 1
1923 if {$n < 0} {
1924 # eof
1925 if {!$diffblocked($ids)} {
1926 close $f
1927 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
1928 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
1929 processhunks
1931 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
1932 # start of a new file
1933 set currentfile($ids) \
1934 [lsearch -exact $mergefilelist($diffmergeid) $fname]
1935 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1936 $line match f1l f1c f2l f2c rest]} {
1937 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
1938 # start of a new hunk
1939 if {$f1l == 0 && $f1c == 0} {
1940 set f1l 1
1942 if {$f2l == 0 && $f2c == 0} {
1943 set f2l 1
1945 set diffinhunk($ids) 1
1946 set diffoldstart($ids) $f1l
1947 set diffnewstart($ids) $f2l
1948 set diffoldlno($ids) $f1l
1949 set diffnewlno($ids) $f2l
1950 set difflcounts($ids) {}
1951 set noldlines($ids) 0
1952 set nnewlines($ids) 0
1957 proc processhunks {} {
1958 global diffmergeid parents nparents currenthunk
1959 global mergefilelist diffblocked mergefds
1960 global grouphunks grouplinestart grouplineend groupfilenum
1962 set nfiles [llength $mergefilelist($diffmergeid)]
1963 while 1 {
1964 set fi $nfiles
1965 set lno 0
1966 # look for the earliest hunk
1967 foreach p $parents($diffmergeid) {
1968 set ids [list $diffmergeid $p]
1969 if {![info exists currenthunk($ids)]} return
1970 set i [lindex $currenthunk($ids) 0]
1971 set l [lindex $currenthunk($ids) 2]
1972 if {$i < $fi || ($i == $fi && $l < $lno)} {
1973 set fi $i
1974 set lno $l
1975 set pi $p
1979 if {$fi < $nfiles} {
1980 set ids [list $diffmergeid $pi]
1981 set hunk $currenthunk($ids)
1982 unset currenthunk($ids)
1983 if {$diffblocked($ids) > 0} {
1984 fileevent $mergefds($ids) readable \
1985 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
1987 set diffblocked($ids) 0
1989 if {[info exists groupfilenum] && $groupfilenum == $fi
1990 && $lno <= $grouplineend} {
1991 # add this hunk to the pending group
1992 lappend grouphunks($pi) $hunk
1993 set endln [lindex $hunk 4]
1994 if {$endln > $grouplineend} {
1995 set grouplineend $endln
1997 continue
2001 # succeeding stuff doesn't belong in this group, so
2002 # process the group now
2003 if {[info exists groupfilenum]} {
2004 processgroup
2005 unset groupfilenum
2006 unset grouphunks
2009 if {$fi >= $nfiles} break
2011 # start a new group
2012 set groupfilenum $fi
2013 set grouphunks($pi) [list $hunk]
2014 set grouplinestart $lno
2015 set grouplineend [lindex $hunk 4]
2019 proc processgroup {} {
2020 global groupfilelast groupfilenum difffilestart
2021 global mergefilelist diffmergeid ctext filelines
2022 global parents diffmergeid diffoffset
2023 global grouphunks grouplinestart grouplineend nparents
2024 global mergemax
2026 $ctext conf -state normal
2027 set id $diffmergeid
2028 set f $groupfilenum
2029 if {$groupfilelast != $f} {
2030 $ctext insert end "\n"
2031 set here [$ctext index "end - 1c"]
2032 set difffilestart($f) $here
2033 set mark fmark.[expr {$f + 1}]
2034 $ctext mark set $mark $here
2035 $ctext mark gravity $mark left
2036 set header [lindex $mergefilelist($id) $f]
2037 set l [expr {(78 - [string length $header]) / 2}]
2038 set pad [string range "----------------------------------------" 1 $l]
2039 $ctext insert end "$pad $header $pad\n" filesep
2040 set groupfilelast $f
2041 foreach p $parents($id) {
2042 set diffoffset($p) 0
2046 $ctext insert end "@@" msep
2047 set nlines [expr {$grouplineend - $grouplinestart}]
2048 set events {}
2049 set pnum 0
2050 foreach p $parents($id) {
2051 set startline [expr {$grouplinestart + $diffoffset($p)}]
2052 set offset($p) $diffoffset($p)
2053 set ol $startline
2054 set nl $grouplinestart
2055 if {[info exists grouphunks($p)]} {
2056 foreach h $grouphunks($p) {
2057 set l [lindex $h 2]
2058 if {$nl < $l} {
2059 for {} {$nl < $l} {incr nl} {
2060 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2061 incr ol
2064 foreach chunk [lindex $h 5] {
2065 if {[llength $chunk] == 2} {
2066 set olc [lindex $chunk 0]
2067 set nlc [lindex $chunk 1]
2068 set nnl [expr {$nl + $nlc}]
2069 lappend events [list $nl $nnl $pnum $olc $nlc]
2070 incr ol $olc
2071 set nl $nnl
2072 } else {
2073 incr ol [lindex $chunk 0]
2074 incr nl [lindex $chunk 0]
2079 if {$nl < $grouplineend} {
2080 for {} {$nl < $grouplineend} {incr nl} {
2081 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2082 incr ol
2085 set nlines [expr {$ol - $startline}]
2086 $ctext insert end " -$startline,$nlines" msep
2087 incr pnum
2090 set nlines [expr {$grouplineend - $grouplinestart}]
2091 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2093 set events [lsort -integer -index 0 $events]
2094 set nevents [llength $events]
2095 set nmerge $nparents($diffmergeid)
2096 set i 0
2097 set l $grouplinestart
2098 while {$i < $nevents} {
2099 set nl [lindex $events $i 0]
2100 while {$l < $nl} {
2101 $ctext insert end " $filelines($id,$f,$l)\n"
2102 incr l
2104 set e [lindex $events $i]
2105 set enl [lindex $e 1]
2106 set j $i
2107 set active {}
2108 while 1 {
2109 set pnum [lindex $e 2]
2110 set olc [lindex $e 3]
2111 set nlc [lindex $e 4]
2112 if {![info exists delta($pnum)]} {
2113 set delta($pnum) [expr {$olc - $nlc}]
2114 lappend active $pnum
2115 } else {
2116 incr delta($pnum) [expr {$olc - $nlc}]
2118 if {[incr j] >= $nevents} break
2119 set e [lindex $events $j]
2120 if {[lindex $e 0] >= $enl} break
2121 if {[lindex $e 1] > $enl} {
2122 set enl [lindex $e 1]
2125 set nlc [expr {$enl - $l}]
2126 set ncol mresult
2127 if {[llength $active] == $nmerge - 1} {
2128 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2129 if {![info exists delta($pnum)]} {
2130 if {$pnum < $mergemax} {
2131 lappend ncol m$pnum
2132 } else {
2133 lappend ncol mmax
2135 break
2139 set pnum -1
2140 foreach p $parents($id) {
2141 incr pnum
2142 if {![info exists delta($pnum)]} continue
2143 set olc [expr {$nlc + $delta($pnum)}]
2144 set ol [expr {$l + $diffoffset($p)}]
2145 incr diffoffset($p) $delta($pnum)
2146 unset delta($pnum)
2147 for {} {$olc > 0} {incr olc -1} {
2148 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2149 incr ol
2152 for {} {$nlc > 0} {incr nlc -1} {
2153 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2154 incr l
2156 set i $j
2158 while {$l < $grouplineend} {
2159 $ctext insert end " $filelines($id,$f,$l)\n"
2160 incr l
2162 $ctext conf -state disabled
2165 proc startdiff {ids} {
2166 global treediffs diffids treepending diffmergeid
2168 set diffids $ids
2169 catch {unset diffmergeid}
2170 if {![info exists treediffs($ids)]} {
2171 if {![info exists treepending]} {
2172 gettreediffs $ids
2174 } else {
2175 addtocflist $ids
2179 proc addtocflist {ids} {
2180 global treediffs cflist
2181 foreach f $treediffs($ids) {
2182 $cflist insert end $f
2184 getblobdiffs $ids
2187 proc gettreediffs {ids} {
2188 global treediff parents treepending
2189 set treepending $ids
2190 set treediff {}
2191 set id [lindex $ids 0]
2192 set p [lindex $ids 1]
2193 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2194 fconfigure $gdtf -blocking 0
2195 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2198 proc gettreediffline {gdtf ids} {
2199 global treediff treediffs treepending diffids diffmergeid
2201 set n [gets $gdtf line]
2202 if {$n < 0} {
2203 if {![eof $gdtf]} return
2204 close $gdtf
2205 set treediffs($ids) $treediff
2206 unset treepending
2207 if {$ids != $diffids} {
2208 gettreediffs $diffids
2209 } else {
2210 if {[info exists diffmergeid]} {
2211 contmergediff $ids
2212 } else {
2213 addtocflist $ids
2216 return
2218 set file [lindex $line 5]
2219 lappend treediff $file
2222 proc getblobdiffs {ids} {
2223 global diffopts blobdifffd diffids env curdifftag curtagstart
2224 global difffilestart nextupdate diffinhdr treediffs
2226 set id [lindex $ids 0]
2227 set p [lindex $ids 1]
2228 set env(GIT_DIFF_OPTS) $diffopts
2229 set cmd [list | git-diff-tree -r -p -C $p $id]
2230 if {[catch {set bdf [open $cmd r]} err]} {
2231 puts "error getting diffs: $err"
2232 return
2234 set diffinhdr 0
2235 fconfigure $bdf -blocking 0
2236 set blobdifffd($ids) $bdf
2237 set curdifftag Comments
2238 set curtagstart 0.0
2239 catch {unset difffilestart}
2240 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2241 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2244 proc getblobdiffline {bdf ids} {
2245 global diffids blobdifffd ctext curdifftag curtagstart
2246 global diffnexthead diffnextnote difffilestart
2247 global nextupdate diffinhdr treediffs
2248 global gaudydiff
2250 set n [gets $bdf line]
2251 if {$n < 0} {
2252 if {[eof $bdf]} {
2253 close $bdf
2254 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2255 $ctext tag add $curdifftag $curtagstart end
2258 return
2260 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2261 return
2263 $ctext conf -state normal
2264 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2265 # start of a new file
2266 $ctext insert end "\n"
2267 $ctext tag add $curdifftag $curtagstart end
2268 set curtagstart [$ctext index "end - 1c"]
2269 set header $newname
2270 set here [$ctext index "end - 1c"]
2271 set i [lsearch -exact $treediffs($diffids) $fname]
2272 if {$i >= 0} {
2273 set difffilestart($i) $here
2274 incr i
2275 $ctext mark set fmark.$i $here
2276 $ctext mark gravity fmark.$i left
2278 if {$newname != $fname} {
2279 set i [lsearch -exact $treediffs($diffids) $newname]
2280 if {$i >= 0} {
2281 set difffilestart($i) $here
2282 incr i
2283 $ctext mark set fmark.$i $here
2284 $ctext mark gravity fmark.$i left
2287 set curdifftag "f:$fname"
2288 $ctext tag delete $curdifftag
2289 set l [expr {(78 - [string length $header]) / 2}]
2290 set pad [string range "----------------------------------------" 1 $l]
2291 $ctext insert end "$pad $header $pad\n" filesep
2292 set diffinhdr 1
2293 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2294 set diffinhdr 0
2295 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2296 $line match f1l f1c f2l f2c rest]} {
2297 if {$gaudydiff} {
2298 $ctext insert end "\t" hunksep
2299 $ctext insert end " $f1l " d0 " $f2l " d1
2300 $ctext insert end " $rest \n" hunksep
2301 } else {
2302 $ctext insert end "$line\n" hunksep
2304 set diffinhdr 0
2305 } else {
2306 set x [string range $line 0 0]
2307 if {$x == "-" || $x == "+"} {
2308 set tag [expr {$x == "+"}]
2309 if {$gaudydiff} {
2310 set line [string range $line 1 end]
2312 $ctext insert end "$line\n" d$tag
2313 } elseif {$x == " "} {
2314 if {$gaudydiff} {
2315 set line [string range $line 1 end]
2317 $ctext insert end "$line\n"
2318 } elseif {$diffinhdr || $x == "\\"} {
2319 # e.g. "\ No newline at end of file"
2320 $ctext insert end "$line\n" filesep
2321 } else {
2322 # Something else we don't recognize
2323 if {$curdifftag != "Comments"} {
2324 $ctext insert end "\n"
2325 $ctext tag add $curdifftag $curtagstart end
2326 set curtagstart [$ctext index "end - 1c"]
2327 set curdifftag Comments
2329 $ctext insert end "$line\n" filesep
2332 $ctext conf -state disabled
2333 if {[clock clicks -milliseconds] >= $nextupdate} {
2334 incr nextupdate 100
2335 fileevent $bdf readable {}
2336 update
2337 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2341 proc nextfile {} {
2342 global difffilestart ctext
2343 set here [$ctext index @0,0]
2344 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2345 if {[$ctext compare $difffilestart($i) > $here]} {
2346 if {![info exists pos]
2347 || [$ctext compare $difffilestart($i) < $pos]} {
2348 set pos $difffilestart($i)
2352 if {[info exists pos]} {
2353 $ctext yview $pos
2357 proc listboxsel {} {
2358 global ctext cflist currentid
2359 if {![info exists currentid]} return
2360 set sel [lsort [$cflist curselection]]
2361 if {$sel eq {}} return
2362 set first [lindex $sel 0]
2363 catch {$ctext yview fmark.$first}
2366 proc setcoords {} {
2367 global linespc charspc canvx0 canvy0 mainfont
2368 set linespc [font metrics $mainfont -linespace]
2369 set charspc [font measure $mainfont "m"]
2370 set canvy0 [expr 3 + 0.5 * $linespc]
2371 set canvx0 [expr 3 + 0.5 * $linespc]
2374 proc redisplay {} {
2375 global selectedline stopped redisplaying phase
2376 if {$stopped > 1} return
2377 if {$phase == "getcommits"} return
2378 set redisplaying 1
2379 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2380 set stopped 1
2381 } else {
2382 drawgraph
2386 proc incrfont {inc} {
2387 global mainfont namefont textfont selectedline ctext canv phase
2388 global stopped entries
2389 unmarkmatches
2390 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2391 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2392 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2393 setcoords
2394 $ctext conf -font $textfont
2395 $ctext tag conf filesep -font [concat $textfont bold]
2396 foreach e $entries {
2397 $e conf -font $mainfont
2399 if {$phase == "getcommits"} {
2400 $canv itemconf textitems -font $mainfont
2402 redisplay
2405 proc clearsha1 {} {
2406 global sha1entry sha1string
2407 if {[string length $sha1string] == 40} {
2408 $sha1entry delete 0 end
2412 proc sha1change {n1 n2 op} {
2413 global sha1string currentid sha1but
2414 if {$sha1string == {}
2415 || ([info exists currentid] && $sha1string == $currentid)} {
2416 set state disabled
2417 } else {
2418 set state normal
2420 if {[$sha1but cget -state] == $state} return
2421 if {$state == "normal"} {
2422 $sha1but conf -state normal -relief raised -text "Goto: "
2423 } else {
2424 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2428 proc gotocommit {} {
2429 global sha1string currentid idline tagids
2430 global lineid numcommits
2432 if {$sha1string == {}
2433 || ([info exists currentid] && $sha1string == $currentid)} return
2434 if {[info exists tagids($sha1string)]} {
2435 set id $tagids($sha1string)
2436 } else {
2437 set id [string tolower $sha1string]
2438 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2439 set matches {}
2440 for {set l 0} {$l < $numcommits} {incr l} {
2441 if {[string match $id* $lineid($l)]} {
2442 lappend matches $lineid($l)
2445 if {$matches ne {}} {
2446 if {[llength $matches] > 1} {
2447 error_popup "Short SHA1 id $id is ambiguous"
2448 return
2450 set id [lindex $matches 0]
2454 if {[info exists idline($id)]} {
2455 selectline $idline($id)
2456 return
2458 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2459 set type "SHA1 id"
2460 } else {
2461 set type "Tag"
2463 error_popup "$type $sha1string is not known"
2466 proc lineenter {x y id} {
2467 global hoverx hovery hoverid hovertimer
2468 global commitinfo canv
2470 if {![info exists commitinfo($id)]} return
2471 set hoverx $x
2472 set hovery $y
2473 set hoverid $id
2474 if {[info exists hovertimer]} {
2475 after cancel $hovertimer
2477 set hovertimer [after 500 linehover]
2478 $canv delete hover
2481 proc linemotion {x y id} {
2482 global hoverx hovery hoverid hovertimer
2484 if {[info exists hoverid] && $id == $hoverid} {
2485 set hoverx $x
2486 set hovery $y
2487 if {[info exists hovertimer]} {
2488 after cancel $hovertimer
2490 set hovertimer [after 500 linehover]
2494 proc lineleave {id} {
2495 global hoverid hovertimer canv
2497 if {[info exists hoverid] && $id == $hoverid} {
2498 $canv delete hover
2499 if {[info exists hovertimer]} {
2500 after cancel $hovertimer
2501 unset hovertimer
2503 unset hoverid
2507 proc linehover {} {
2508 global hoverx hovery hoverid hovertimer
2509 global canv linespc lthickness
2510 global commitinfo mainfont
2512 set text [lindex $commitinfo($hoverid) 0]
2513 set ymax [lindex [$canv cget -scrollregion] 3]
2514 if {$ymax == {}} return
2515 set yfrac [lindex [$canv yview] 0]
2516 set x [expr {$hoverx + 2 * $linespc}]
2517 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2518 set x0 [expr {$x - 2 * $lthickness}]
2519 set y0 [expr {$y - 2 * $lthickness}]
2520 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2521 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2522 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2523 -fill \#ffff80 -outline black -width 1 -tags hover]
2524 $canv raise $t
2525 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2526 $canv raise $t
2529 proc lineclick {x y id} {
2530 global ctext commitinfo children cflist canv
2532 unmarkmatches
2533 $canv delete hover
2534 # fill the details pane with info about this line
2535 $ctext conf -state normal
2536 $ctext delete 0.0 end
2537 $ctext insert end "Parent:\n "
2538 catch {destroy $ctext.$id}
2539 button $ctext.$id -text "Go:" -command "selbyid $id" \
2540 -padx 4 -pady 0
2541 $ctext window create end -window $ctext.$id -align center
2542 set info $commitinfo($id)
2543 $ctext insert end "\t[lindex $info 0]\n"
2544 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2545 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2546 $ctext insert end "\tID:\t$id\n"
2547 if {[info exists children($id)]} {
2548 $ctext insert end "\nChildren:"
2549 foreach child $children($id) {
2550 $ctext insert end "\n "
2551 catch {destroy $ctext.$child}
2552 button $ctext.$child -text "Go:" -command "selbyid $child" \
2553 -padx 4 -pady 0
2554 $ctext window create end -window $ctext.$child -align center
2555 set info $commitinfo($child)
2556 $ctext insert end "\t[lindex $info 0]"
2559 $ctext conf -state disabled
2561 $cflist delete 0 end
2564 proc selbyid {id} {
2565 global idline
2566 if {[info exists idline($id)]} {
2567 selectline $idline($id)
2571 proc mstime {} {
2572 global startmstime
2573 if {![info exists startmstime]} {
2574 set startmstime [clock clicks -milliseconds]
2576 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2579 proc rowmenu {x y id} {
2580 global rowctxmenu idline selectedline rowmenuid
2582 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2583 set state disabled
2584 } else {
2585 set state normal
2587 $rowctxmenu entryconfigure 0 -state $state
2588 $rowctxmenu entryconfigure 1 -state $state
2589 $rowctxmenu entryconfigure 2 -state $state
2590 set rowmenuid $id
2591 tk_popup $rowctxmenu $x $y
2594 proc diffvssel {dirn} {
2595 global rowmenuid selectedline lineid
2596 global ctext cflist
2597 global commitinfo
2599 if {![info exists selectedline]} return
2600 if {$dirn} {
2601 set oldid $lineid($selectedline)
2602 set newid $rowmenuid
2603 } else {
2604 set oldid $rowmenuid
2605 set newid $lineid($selectedline)
2607 $ctext conf -state normal
2608 $ctext delete 0.0 end
2609 $ctext mark set fmark.0 0.0
2610 $ctext mark gravity fmark.0 left
2611 $cflist delete 0 end
2612 $cflist insert end "Top"
2613 $ctext insert end "From $oldid\n "
2614 $ctext insert end [lindex $commitinfo($oldid) 0]
2615 $ctext insert end "\n\nTo $newid\n "
2616 $ctext insert end [lindex $commitinfo($newid) 0]
2617 $ctext insert end "\n"
2618 $ctext conf -state disabled
2619 $ctext tag delete Comments
2620 $ctext tag remove found 1.0 end
2621 startdiff $newid [list $oldid]
2624 proc mkpatch {} {
2625 global rowmenuid currentid commitinfo patchtop patchnum
2627 if {![info exists currentid]} return
2628 set oldid $currentid
2629 set oldhead [lindex $commitinfo($oldid) 0]
2630 set newid $rowmenuid
2631 set newhead [lindex $commitinfo($newid) 0]
2632 set top .patch
2633 set patchtop $top
2634 catch {destroy $top}
2635 toplevel $top
2636 label $top.title -text "Generate patch"
2637 grid $top.title - -pady 10
2638 label $top.from -text "From:"
2639 entry $top.fromsha1 -width 40 -relief flat
2640 $top.fromsha1 insert 0 $oldid
2641 $top.fromsha1 conf -state readonly
2642 grid $top.from $top.fromsha1 -sticky w
2643 entry $top.fromhead -width 60 -relief flat
2644 $top.fromhead insert 0 $oldhead
2645 $top.fromhead conf -state readonly
2646 grid x $top.fromhead -sticky w
2647 label $top.to -text "To:"
2648 entry $top.tosha1 -width 40 -relief flat
2649 $top.tosha1 insert 0 $newid
2650 $top.tosha1 conf -state readonly
2651 grid $top.to $top.tosha1 -sticky w
2652 entry $top.tohead -width 60 -relief flat
2653 $top.tohead insert 0 $newhead
2654 $top.tohead conf -state readonly
2655 grid x $top.tohead -sticky w
2656 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2657 grid $top.rev x -pady 10
2658 label $top.flab -text "Output file:"
2659 entry $top.fname -width 60
2660 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2661 incr patchnum
2662 grid $top.flab $top.fname -sticky w
2663 frame $top.buts
2664 button $top.buts.gen -text "Generate" -command mkpatchgo
2665 button $top.buts.can -text "Cancel" -command mkpatchcan
2666 grid $top.buts.gen $top.buts.can
2667 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2668 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2669 grid $top.buts - -pady 10 -sticky ew
2670 focus $top.fname
2673 proc mkpatchrev {} {
2674 global patchtop
2676 set oldid [$patchtop.fromsha1 get]
2677 set oldhead [$patchtop.fromhead get]
2678 set newid [$patchtop.tosha1 get]
2679 set newhead [$patchtop.tohead get]
2680 foreach e [list fromsha1 fromhead tosha1 tohead] \
2681 v [list $newid $newhead $oldid $oldhead] {
2682 $patchtop.$e conf -state normal
2683 $patchtop.$e delete 0 end
2684 $patchtop.$e insert 0 $v
2685 $patchtop.$e conf -state readonly
2689 proc mkpatchgo {} {
2690 global patchtop
2692 set oldid [$patchtop.fromsha1 get]
2693 set newid [$patchtop.tosha1 get]
2694 set fname [$patchtop.fname get]
2695 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2696 error_popup "Error creating patch: $err"
2698 catch {destroy $patchtop}
2699 unset patchtop
2702 proc mkpatchcan {} {
2703 global patchtop
2705 catch {destroy $patchtop}
2706 unset patchtop
2709 proc mktag {} {
2710 global rowmenuid mktagtop commitinfo
2712 set top .maketag
2713 set mktagtop $top
2714 catch {destroy $top}
2715 toplevel $top
2716 label $top.title -text "Create tag"
2717 grid $top.title - -pady 10
2718 label $top.id -text "ID:"
2719 entry $top.sha1 -width 40 -relief flat
2720 $top.sha1 insert 0 $rowmenuid
2721 $top.sha1 conf -state readonly
2722 grid $top.id $top.sha1 -sticky w
2723 entry $top.head -width 60 -relief flat
2724 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2725 $top.head conf -state readonly
2726 grid x $top.head -sticky w
2727 label $top.tlab -text "Tag name:"
2728 entry $top.tag -width 60
2729 grid $top.tlab $top.tag -sticky w
2730 frame $top.buts
2731 button $top.buts.gen -text "Create" -command mktaggo
2732 button $top.buts.can -text "Cancel" -command mktagcan
2733 grid $top.buts.gen $top.buts.can
2734 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2735 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2736 grid $top.buts - -pady 10 -sticky ew
2737 focus $top.tag
2740 proc domktag {} {
2741 global mktagtop env tagids idtags
2742 global idpos idline linehtag canv selectedline
2744 set id [$mktagtop.sha1 get]
2745 set tag [$mktagtop.tag get]
2746 if {$tag == {}} {
2747 error_popup "No tag name specified"
2748 return
2750 if {[info exists tagids($tag)]} {
2751 error_popup "Tag \"$tag\" already exists"
2752 return
2754 if {[catch {
2755 set dir ".git"
2756 if {[info exists env(GIT_DIR)]} {
2757 set dir $env(GIT_DIR)
2759 set fname [file join $dir "refs/tags" $tag]
2760 set f [open $fname w]
2761 puts $f $id
2762 close $f
2763 } err]} {
2764 error_popup "Error creating tag: $err"
2765 return
2768 set tagids($tag) $id
2769 lappend idtags($id) $tag
2770 $canv delete tag.$id
2771 set xt [eval drawtags $id $idpos($id)]
2772 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2773 if {[info exists selectedline] && $selectedline == $idline($id)} {
2774 selectline $selectedline
2778 proc mktagcan {} {
2779 global mktagtop
2781 catch {destroy $mktagtop}
2782 unset mktagtop
2785 proc mktaggo {} {
2786 domktag
2787 mktagcan
2790 proc writecommit {} {
2791 global rowmenuid wrcomtop commitinfo wrcomcmd
2793 set top .writecommit
2794 set wrcomtop $top
2795 catch {destroy $top}
2796 toplevel $top
2797 label $top.title -text "Write commit to file"
2798 grid $top.title - -pady 10
2799 label $top.id -text "ID:"
2800 entry $top.sha1 -width 40 -relief flat
2801 $top.sha1 insert 0 $rowmenuid
2802 $top.sha1 conf -state readonly
2803 grid $top.id $top.sha1 -sticky w
2804 entry $top.head -width 60 -relief flat
2805 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2806 $top.head conf -state readonly
2807 grid x $top.head -sticky w
2808 label $top.clab -text "Command:"
2809 entry $top.cmd -width 60 -textvariable wrcomcmd
2810 grid $top.clab $top.cmd -sticky w -pady 10
2811 label $top.flab -text "Output file:"
2812 entry $top.fname -width 60
2813 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2814 grid $top.flab $top.fname -sticky w
2815 frame $top.buts
2816 button $top.buts.gen -text "Write" -command wrcomgo
2817 button $top.buts.can -text "Cancel" -command wrcomcan
2818 grid $top.buts.gen $top.buts.can
2819 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2820 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2821 grid $top.buts - -pady 10 -sticky ew
2822 focus $top.fname
2825 proc wrcomgo {} {
2826 global wrcomtop
2828 set id [$wrcomtop.sha1 get]
2829 set cmd "echo $id | [$wrcomtop.cmd get]"
2830 set fname [$wrcomtop.fname get]
2831 if {[catch {exec sh -c $cmd >$fname &} err]} {
2832 error_popup "Error writing commit: $err"
2834 catch {destroy $wrcomtop}
2835 unset wrcomtop
2838 proc wrcomcan {} {
2839 global wrcomtop
2841 catch {destroy $wrcomtop}
2842 unset wrcomtop
2845 proc doquit {} {
2846 global stopped
2847 set stopped 100
2848 destroy .
2851 # defaults...
2852 set datemode 0
2853 set boldnames 0
2854 set diffopts "-U 5 -p"
2855 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2857 set mainfont {Helvetica 9}
2858 set textfont {Courier 9}
2859 set findmergefiles 0
2860 set gaudydiff 0
2862 set colors {green red blue magenta darkgrey brown orange}
2864 catch {source ~/.gitk}
2866 set namefont $mainfont
2867 if {$boldnames} {
2868 lappend namefont bold
2871 set revtreeargs {}
2872 foreach arg $argv {
2873 switch -regexp -- $arg {
2874 "^$" { }
2875 "^-b" { set boldnames 1 }
2876 "^-d" { set datemode 1 }
2877 default {
2878 lappend revtreeargs $arg
2883 set stopped 0
2884 set redisplaying 0
2885 set stuffsaved 0
2886 set patchnum 0
2887 setcoords
2888 makewindow
2889 readrefs
2890 getcommits $revtreeargs