Merge master.kernel.org:/pub/scm/gitk/gitk
[git.git] / gitk
blob5ebcf3377ebf0baba09386fde57df153f9415191
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
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 found -back yellow
379 frame .ctop.cdet.right
380 set cflist .ctop.cdet.right.cfiles
381 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
382 -yscrollcommand ".ctop.cdet.right.sb set"
383 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
384 pack .ctop.cdet.right.sb -side right -fill y
385 pack $cflist -side left -fill both -expand 1
386 .ctop.cdet add .ctop.cdet.right
387 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
389 pack .ctop -side top -fill both -expand 1
391 bindall <1> {selcanvline %W %x %y}
392 #bindall <B1-Motion> {selcanvline %W %x %y}
393 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
394 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
395 bindall <2> "allcanvs scan mark 0 %y"
396 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
397 bind . <Key-Up> "selnextline -1"
398 bind . <Key-Down> "selnextline 1"
399 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
400 bind . <Key-Next> "allcanvs yview scroll 1 pages"
401 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
402 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
403 bindkey <Key-space> "$ctext yview scroll 1 pages"
404 bindkey p "selnextline -1"
405 bindkey n "selnextline 1"
406 bindkey b "$ctext yview scroll -1 pages"
407 bindkey d "$ctext yview scroll 18 units"
408 bindkey u "$ctext yview scroll -18 units"
409 bindkey / {findnext 1}
410 bindkey <Key-Return> {findnext 0}
411 bindkey ? findprev
412 bindkey f nextfile
413 bind . <Control-q> doquit
414 bind . <Control-f> dofind
415 bind . <Control-g> {findnext 0}
416 bind . <Control-r> findprev
417 bind . <Control-equal> {incrfont 1}
418 bind . <Control-KP_Add> {incrfont 1}
419 bind . <Control-minus> {incrfont -1}
420 bind . <Control-KP_Subtract> {incrfont -1}
421 bind $cflist <<ListboxSelect>> listboxsel
422 bind . <Destroy> {savestuff %W}
423 bind . <Button-1> "click %W"
424 bind $fstring <Key-Return> dofind
425 bind $sha1entry <Key-Return> gotocommit
426 bind $sha1entry <<PasteSelection>> clearsha1
428 set maincursor [. cget -cursor]
429 set textcursor [$ctext cget -cursor]
431 set rowctxmenu .rowctxmenu
432 menu $rowctxmenu -tearoff 0
433 $rowctxmenu add command -label "Diff this -> selected" \
434 -command {diffvssel 0}
435 $rowctxmenu add command -label "Diff selected -> this" \
436 -command {diffvssel 1}
437 $rowctxmenu add command -label "Make patch" -command mkpatch
438 $rowctxmenu add command -label "Create tag" -command mktag
439 $rowctxmenu add command -label "Write commit to file" -command writecommit
442 # when we make a key binding for the toplevel, make sure
443 # it doesn't get triggered when that key is pressed in the
444 # find string entry widget.
445 proc bindkey {ev script} {
446 global entries
447 bind . $ev $script
448 set escript [bind Entry $ev]
449 if {$escript == {}} {
450 set escript [bind Entry <Key>]
452 foreach e $entries {
453 bind $e $ev "$escript; break"
457 # set the focus back to the toplevel for any click outside
458 # the entry widgets
459 proc click {w} {
460 global entries
461 foreach e $entries {
462 if {$w == $e} return
464 focus .
467 proc savestuff {w} {
468 global canv canv2 canv3 ctext cflist mainfont textfont
469 global stuffsaved
470 if {$stuffsaved} return
471 if {![winfo viewable .]} return
472 catch {
473 set f [open "~/.gitk-new" w]
474 puts $f [list set mainfont $mainfont]
475 puts $f [list set textfont $textfont]
476 puts $f [list set findmergefiles $findmergefiles]
477 puts $f [list set gaudydiff $gaudydiff]
478 puts $f "set geometry(width) [winfo width .ctop]"
479 puts $f "set geometry(height) [winfo height .ctop]"
480 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
481 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
482 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
483 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
484 set wid [expr {([winfo width $ctext] - 8) \
485 / [font measure $textfont "0"]}]
486 puts $f "set geometry(ctextw) $wid"
487 set wid [expr {([winfo width $cflist] - 11) \
488 / [font measure [$cflist cget -font] "0"]}]
489 puts $f "set geometry(cflistw) $wid"
490 close $f
491 file rename -force "~/.gitk-new" "~/.gitk"
493 set stuffsaved 1
496 proc resizeclistpanes {win w} {
497 global oldwidth
498 if [info exists oldwidth($win)] {
499 set s0 [$win sash coord 0]
500 set s1 [$win sash coord 1]
501 if {$w < 60} {
502 set sash0 [expr {int($w/2 - 2)}]
503 set sash1 [expr {int($w*5/6 - 2)}]
504 } else {
505 set factor [expr {1.0 * $w / $oldwidth($win)}]
506 set sash0 [expr {int($factor * [lindex $s0 0])}]
507 set sash1 [expr {int($factor * [lindex $s1 0])}]
508 if {$sash0 < 30} {
509 set sash0 30
511 if {$sash1 < $sash0 + 20} {
512 set sash1 [expr $sash0 + 20]
514 if {$sash1 > $w - 10} {
515 set sash1 [expr $w - 10]
516 if {$sash0 > $sash1 - 20} {
517 set sash0 [expr $sash1 - 20]
521 $win sash place 0 $sash0 [lindex $s0 1]
522 $win sash place 1 $sash1 [lindex $s1 1]
524 set oldwidth($win) $w
527 proc resizecdetpanes {win w} {
528 global oldwidth
529 if [info exists oldwidth($win)] {
530 set s0 [$win sash coord 0]
531 if {$w < 60} {
532 set sash0 [expr {int($w*3/4 - 2)}]
533 } else {
534 set factor [expr {1.0 * $w / $oldwidth($win)}]
535 set sash0 [expr {int($factor * [lindex $s0 0])}]
536 if {$sash0 < 45} {
537 set sash0 45
539 if {$sash0 > $w - 15} {
540 set sash0 [expr $w - 15]
543 $win sash place 0 $sash0 [lindex $s0 1]
545 set oldwidth($win) $w
548 proc allcanvs args {
549 global canv canv2 canv3
550 eval $canv $args
551 eval $canv2 $args
552 eval $canv3 $args
555 proc bindall {event action} {
556 global canv canv2 canv3
557 bind $canv $event $action
558 bind $canv2 $event $action
559 bind $canv3 $event $action
562 proc about {} {
563 set w .about
564 if {[winfo exists $w]} {
565 raise $w
566 return
568 toplevel $w
569 wm title $w "About gitk"
570 message $w.m -text {
571 Gitk version 1.2
573 Copyright © 2005 Paul Mackerras
575 Use and redistribute under the terms of the GNU General Public License} \
576 -justify center -aspect 400
577 pack $w.m -side top -fill x -padx 20 -pady 20
578 button $w.ok -text Close -command "destroy $w"
579 pack $w.ok -side bottom
582 proc assigncolor {id} {
583 global commitinfo colormap commcolors colors nextcolor
584 global parents nparents children nchildren
585 global cornercrossings crossings
587 if [info exists colormap($id)] return
588 set ncolors [llength $colors]
589 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
590 set child [lindex $children($id) 0]
591 if {[info exists colormap($child)]
592 && $nparents($child) == 1} {
593 set colormap($id) $colormap($child)
594 return
597 set badcolors {}
598 if {[info exists cornercrossings($id)]} {
599 foreach x $cornercrossings($id) {
600 if {[info exists colormap($x)]
601 && [lsearch -exact $badcolors $colormap($x)] < 0} {
602 lappend badcolors $colormap($x)
605 if {[llength $badcolors] >= $ncolors} {
606 set badcolors {}
609 set origbad $badcolors
610 if {[llength $badcolors] < $ncolors - 1} {
611 if {[info exists crossings($id)]} {
612 foreach x $crossings($id) {
613 if {[info exists colormap($x)]
614 && [lsearch -exact $badcolors $colormap($x)] < 0} {
615 lappend badcolors $colormap($x)
618 if {[llength $badcolors] >= $ncolors} {
619 set badcolors $origbad
622 set origbad $badcolors
624 if {[llength $badcolors] < $ncolors - 1} {
625 foreach child $children($id) {
626 if {[info exists colormap($child)]
627 && [lsearch -exact $badcolors $colormap($child)] < 0} {
628 lappend badcolors $colormap($child)
630 if {[info exists parents($child)]} {
631 foreach p $parents($child) {
632 if {[info exists colormap($p)]
633 && [lsearch -exact $badcolors $colormap($p)] < 0} {
634 lappend badcolors $colormap($p)
639 if {[llength $badcolors] >= $ncolors} {
640 set badcolors $origbad
643 for {set i 0} {$i <= $ncolors} {incr i} {
644 set c [lindex $colors $nextcolor]
645 if {[incr nextcolor] >= $ncolors} {
646 set nextcolor 0
648 if {[lsearch -exact $badcolors $c]} break
650 set colormap($id) $c
653 proc initgraph {} {
654 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
655 global mainline sidelines
656 global nchildren ncleft
658 allcanvs delete all
659 set nextcolor 0
660 set canvy $canvy0
661 set lineno -1
662 set numcommits 0
663 set lthickness [expr {int($linespc / 9) + 1}]
664 catch {unset mainline}
665 catch {unset sidelines}
666 foreach id [array names nchildren] {
667 set ncleft($id) $nchildren($id)
671 proc bindline {t id} {
672 global canv
674 $canv bind $t <Enter> "lineenter %x %y $id"
675 $canv bind $t <Motion> "linemotion %x %y $id"
676 $canv bind $t <Leave> "lineleave $id"
677 $canv bind $t <Button-1> "lineclick %x %y $id"
680 proc drawcommitline {level} {
681 global parents children nparents nchildren todo
682 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
683 global lineid linehtag linentag linedtag commitinfo
684 global colormap numcommits currentparents dupparents
685 global oldlevel oldnlines oldtodo
686 global idtags idline idheads
687 global lineno lthickness mainline sidelines
688 global commitlisted rowtextx idpos
690 incr numcommits
691 incr lineno
692 set id [lindex $todo $level]
693 set lineid($lineno) $id
694 set idline($id) $lineno
695 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
696 if {![info exists commitinfo($id)]} {
697 readcommit $id
698 if {![info exists commitinfo($id)]} {
699 set commitinfo($id) {"No commit information available"}
700 set nparents($id) 0
703 assigncolor $id
704 set currentparents {}
705 set dupparents {}
706 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
707 foreach p $parents($id) {
708 if {[lsearch -exact $currentparents $p] < 0} {
709 lappend currentparents $p
710 } else {
711 # remember that this parent was listed twice
712 lappend dupparents $p
716 set x [expr $canvx0 + $level * $linespc]
717 set y1 $canvy
718 set canvy [expr $canvy + $linespc]
719 allcanvs conf -scrollregion \
720 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
721 if {[info exists mainline($id)]} {
722 lappend mainline($id) $x $y1
723 set t [$canv create line $mainline($id) \
724 -width $lthickness -fill $colormap($id)]
725 $canv lower $t
726 bindline $t $id
728 if {[info exists sidelines($id)]} {
729 foreach ls $sidelines($id) {
730 set coords [lindex $ls 0]
731 set thick [lindex $ls 1]
732 set t [$canv create line $coords -fill $colormap($id) \
733 -width [expr {$thick * $lthickness}]]
734 $canv lower $t
735 bindline $t $id
738 set orad [expr {$linespc / 3}]
739 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
740 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
741 -fill $ofill -outline black -width 1]
742 $canv raise $t
743 $canv bind $t <1> {selcanvline {} %x %y}
744 set xt [expr $canvx0 + [llength $todo] * $linespc]
745 if {[llength $currentparents] > 2} {
746 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
748 set rowtextx($lineno) $xt
749 set idpos($id) [list $x $xt $y1]
750 if {[info exists idtags($id)] || [info exists idheads($id)]} {
751 set xt [drawtags $id $x $xt $y1]
753 set headline [lindex $commitinfo($id) 0]
754 set name [lindex $commitinfo($id) 1]
755 set date [lindex $commitinfo($id) 2]
756 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
757 -text $headline -font $mainfont ]
758 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
759 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
760 -text $name -font $namefont]
761 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
762 -text $date -font $mainfont]
765 proc drawtags {id x xt y1} {
766 global idtags idheads
767 global linespc lthickness
768 global canv mainfont
770 set marks {}
771 set ntags 0
772 if {[info exists idtags($id)]} {
773 set marks $idtags($id)
774 set ntags [llength $marks]
776 if {[info exists idheads($id)]} {
777 set marks [concat $marks $idheads($id)]
779 if {$marks eq {}} {
780 return $xt
783 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
784 set yt [expr $y1 - 0.5 * $linespc]
785 set yb [expr $yt + $linespc - 1]
786 set xvals {}
787 set wvals {}
788 foreach tag $marks {
789 set wid [font measure $mainfont $tag]
790 lappend xvals $xt
791 lappend wvals $wid
792 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
794 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
795 -width $lthickness -fill black -tags tag.$id]
796 $canv lower $t
797 foreach tag $marks x $xvals wid $wvals {
798 set xl [expr $x + $delta]
799 set xr [expr $x + $delta + $wid + $lthickness]
800 if {[incr ntags -1] >= 0} {
801 # draw a tag
802 $canv create polygon $x [expr $yt + $delta] $xl $yt\
803 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
804 -width 1 -outline black -fill yellow -tags tag.$id
805 } else {
806 # draw a head
807 set xl [expr $xl - $delta/2]
808 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
809 -width 1 -outline black -fill green -tags tag.$id
811 $canv create text $xl $y1 -anchor w -text $tag \
812 -font $mainfont -tags tag.$id
814 return $xt
817 proc updatetodo {level noshortcut} {
818 global currentparents ncleft todo
819 global mainline oldlevel oldtodo oldnlines
820 global canvx0 canvy linespc mainline
821 global commitinfo
823 set oldlevel $level
824 set oldtodo $todo
825 set oldnlines [llength $todo]
826 if {!$noshortcut && [llength $currentparents] == 1} {
827 set p [lindex $currentparents 0]
828 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
829 set ncleft($p) 0
830 set x [expr $canvx0 + $level * $linespc]
831 set y [expr $canvy - $linespc]
832 set mainline($p) [list $x $y]
833 set todo [lreplace $todo $level $level $p]
834 return 0
838 set todo [lreplace $todo $level $level]
839 set i $level
840 foreach p $currentparents {
841 incr ncleft($p) -1
842 set k [lsearch -exact $todo $p]
843 if {$k < 0} {
844 set todo [linsert $todo $i $p]
845 incr i
848 return 1
851 proc notecrossings {id lo hi corner} {
852 global oldtodo crossings cornercrossings
854 for {set i $lo} {[incr i] < $hi} {} {
855 set p [lindex $oldtodo $i]
856 if {$p == {}} continue
857 if {$i == $corner} {
858 if {![info exists cornercrossings($id)]
859 || [lsearch -exact $cornercrossings($id) $p] < 0} {
860 lappend cornercrossings($id) $p
862 if {![info exists cornercrossings($p)]
863 || [lsearch -exact $cornercrossings($p) $id] < 0} {
864 lappend cornercrossings($p) $id
866 } else {
867 if {![info exists crossings($id)]
868 || [lsearch -exact $crossings($id) $p] < 0} {
869 lappend crossings($id) $p
871 if {![info exists crossings($p)]
872 || [lsearch -exact $crossings($p) $id] < 0} {
873 lappend crossings($p) $id
879 proc drawslants {} {
880 global canv mainline sidelines canvx0 canvy linespc
881 global oldlevel oldtodo todo currentparents dupparents
882 global lthickness linespc canvy colormap
884 set y1 [expr $canvy - $linespc]
885 set y2 $canvy
886 set i -1
887 foreach id $oldtodo {
888 incr i
889 if {$id == {}} continue
890 set xi [expr {$canvx0 + $i * $linespc}]
891 if {$i == $oldlevel} {
892 foreach p $currentparents {
893 set j [lsearch -exact $todo $p]
894 set coords [list $xi $y1]
895 set xj [expr {$canvx0 + $j * $linespc}]
896 if {$j < $i - 1} {
897 lappend coords [expr $xj + $linespc] $y1
898 notecrossings $p $j $i [expr {$j + 1}]
899 } elseif {$j > $i + 1} {
900 lappend coords [expr $xj - $linespc] $y1
901 notecrossings $p $i $j [expr {$j - 1}]
903 if {[lsearch -exact $dupparents $p] >= 0} {
904 # draw a double-width line to indicate the doubled parent
905 lappend coords $xj $y2
906 lappend sidelines($p) [list $coords 2]
907 if {![info exists mainline($p)]} {
908 set mainline($p) [list $xj $y2]
910 } else {
911 # normal case, no parent duplicated
912 if {![info exists mainline($p)]} {
913 if {$i != $j} {
914 lappend coords $xj $y2
916 set mainline($p) $coords
917 } else {
918 lappend coords $xj $y2
919 lappend sidelines($p) [list $coords 1]
923 } elseif {[lindex $todo $i] != $id} {
924 set j [lsearch -exact $todo $id]
925 set xj [expr {$canvx0 + $j * $linespc}]
926 lappend mainline($id) $xi $y1 $xj $y2
931 proc decidenext {{noread 0}} {
932 global parents children nchildren ncleft todo
933 global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
934 global datemode cdate
935 global commitinfo
936 global currentparents oldlevel oldnlines oldtodo
937 global lineno lthickness
939 # remove the null entry if present
940 set nullentry [lsearch -exact $todo {}]
941 if {$nullentry >= 0} {
942 set todo [lreplace $todo $nullentry $nullentry]
945 # choose which one to do next time around
946 set todol [llength $todo]
947 set level -1
948 set latest {}
949 for {set k $todol} {[incr k -1] >= 0} {} {
950 set p [lindex $todo $k]
951 if {$ncleft($p) == 0} {
952 if {$datemode} {
953 if {![info exists commitinfo($p)]} {
954 if {$noread} {
955 return {}
957 readcommit $p
959 if {$latest == {} || $cdate($p) > $latest} {
960 set level $k
961 set latest $cdate($p)
963 } else {
964 set level $k
965 break
969 if {$level < 0} {
970 if {$todo != {}} {
971 puts "ERROR: none of the pending commits can be done yet:"
972 foreach p $todo {
973 puts " $p ($ncleft($p))"
976 return -1
979 # If we are reducing, put in a null entry
980 if {$todol < $oldnlines} {
981 if {$nullentry >= 0} {
982 set i $nullentry
983 while {$i < $todol
984 && [lindex $oldtodo $i] == [lindex $todo $i]} {
985 incr i
987 } else {
988 set i $oldlevel
989 if {$level >= $i} {
990 incr i
993 if {$i < $todol} {
994 set todo [linsert $todo $i {}]
995 if {$level >= $i} {
996 incr level
1000 return $level
1003 proc drawcommit {id} {
1004 global phase todo nchildren datemode nextupdate
1005 global startcommits
1007 if {$phase != "incrdraw"} {
1008 set phase incrdraw
1009 set todo $id
1010 set startcommits $id
1011 initgraph
1012 drawcommitline 0
1013 updatetodo 0 $datemode
1014 } else {
1015 if {$nchildren($id) == 0} {
1016 lappend todo $id
1017 lappend startcommits $id
1019 set level [decidenext 1]
1020 if {$level == {} || $id != [lindex $todo $level]} {
1021 return
1023 while 1 {
1024 drawslants
1025 drawcommitline $level
1026 if {[updatetodo $level $datemode]} {
1027 set level [decidenext 1]
1028 if {$level == {}} break
1030 set id [lindex $todo $level]
1031 if {![info exists commitlisted($id)]} {
1032 break
1034 if {[clock clicks -milliseconds] >= $nextupdate} {
1035 doupdate
1036 if {$stopped} break
1042 proc finishcommits {} {
1043 global phase
1044 global startcommits
1045 global canv mainfont ctext maincursor textcursor
1047 if {$phase != "incrdraw"} {
1048 $canv delete all
1049 $canv create text 3 3 -anchor nw -text "No commits selected" \
1050 -font $mainfont -tags textitems
1051 set phase {}
1052 } else {
1053 drawslants
1054 set level [decidenext]
1055 drawrest $level [llength $startcommits]
1057 . config -cursor $maincursor
1058 $ctext config -cursor $textcursor
1061 proc drawgraph {} {
1062 global nextupdate startmsecs startcommits todo
1064 if {$startcommits == {}} return
1065 set startmsecs [clock clicks -milliseconds]
1066 set nextupdate [expr $startmsecs + 100]
1067 initgraph
1068 set todo [lindex $startcommits 0]
1069 drawrest 0 1
1072 proc drawrest {level startix} {
1073 global phase stopped redisplaying selectedline
1074 global datemode currentparents todo
1075 global numcommits
1076 global nextupdate startmsecs startcommits idline
1078 if {$level >= 0} {
1079 set phase drawgraph
1080 set startid [lindex $startcommits $startix]
1081 set startline -1
1082 if {$startid != {}} {
1083 set startline $idline($startid)
1085 while 1 {
1086 if {$stopped} break
1087 drawcommitline $level
1088 set hard [updatetodo $level $datemode]
1089 if {$numcommits == $startline} {
1090 lappend todo $startid
1091 set hard 1
1092 incr startix
1093 set startid [lindex $startcommits $startix]
1094 set startline -1
1095 if {$startid != {}} {
1096 set startline $idline($startid)
1099 if {$hard} {
1100 set level [decidenext]
1101 if {$level < 0} break
1102 drawslants
1104 if {[clock clicks -milliseconds] >= $nextupdate} {
1105 update
1106 incr nextupdate 100
1110 set phase {}
1111 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1112 #puts "overall $drawmsecs ms for $numcommits commits"
1113 if {$redisplaying} {
1114 if {$stopped == 0 && [info exists selectedline]} {
1115 selectline $selectedline
1117 if {$stopped == 1} {
1118 set stopped 0
1119 after idle drawgraph
1120 } else {
1121 set redisplaying 0
1126 proc findmatches {f} {
1127 global findtype foundstring foundstrlen
1128 if {$findtype == "Regexp"} {
1129 set matches [regexp -indices -all -inline $foundstring $f]
1130 } else {
1131 if {$findtype == "IgnCase"} {
1132 set str [string tolower $f]
1133 } else {
1134 set str $f
1136 set matches {}
1137 set i 0
1138 while {[set j [string first $foundstring $str $i]] >= 0} {
1139 lappend matches [list $j [expr $j+$foundstrlen-1]]
1140 set i [expr $j + $foundstrlen]
1143 return $matches
1146 proc dofind {} {
1147 global findtype findloc findstring markedmatches commitinfo
1148 global numcommits lineid linehtag linentag linedtag
1149 global mainfont namefont canv canv2 canv3 selectedline
1150 global matchinglines foundstring foundstrlen
1152 stopfindproc
1153 unmarkmatches
1154 focus .
1155 set matchinglines {}
1156 if {$findloc == "Pickaxe"} {
1157 findpatches
1158 return
1160 if {$findtype == "IgnCase"} {
1161 set foundstring [string tolower $findstring]
1162 } else {
1163 set foundstring $findstring
1165 set foundstrlen [string length $findstring]
1166 if {$foundstrlen == 0} return
1167 if {$findloc == "Files"} {
1168 findfiles
1169 return
1171 if {![info exists selectedline]} {
1172 set oldsel -1
1173 } else {
1174 set oldsel $selectedline
1176 set didsel 0
1177 set fldtypes {Headline Author Date Committer CDate Comment}
1178 for {set l 0} {$l < $numcommits} {incr l} {
1179 set id $lineid($l)
1180 set info $commitinfo($id)
1181 set doesmatch 0
1182 foreach f $info ty $fldtypes {
1183 if {$findloc != "All fields" && $findloc != $ty} {
1184 continue
1186 set matches [findmatches $f]
1187 if {$matches == {}} continue
1188 set doesmatch 1
1189 if {$ty == "Headline"} {
1190 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1191 } elseif {$ty == "Author"} {
1192 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1193 } elseif {$ty == "Date"} {
1194 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1197 if {$doesmatch} {
1198 lappend matchinglines $l
1199 if {!$didsel && $l > $oldsel} {
1200 findselectline $l
1201 set didsel 1
1205 if {$matchinglines == {}} {
1206 bell
1207 } elseif {!$didsel} {
1208 findselectline [lindex $matchinglines 0]
1212 proc findselectline {l} {
1213 global findloc commentend ctext
1214 selectline $l
1215 if {$findloc == "All fields" || $findloc == "Comments"} {
1216 # highlight the matches in the comments
1217 set f [$ctext get 1.0 $commentend]
1218 set matches [findmatches $f]
1219 foreach match $matches {
1220 set start [lindex $match 0]
1221 set end [expr [lindex $match 1] + 1]
1222 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1227 proc findnext {restart} {
1228 global matchinglines selectedline
1229 if {![info exists matchinglines]} {
1230 if {$restart} {
1231 dofind
1233 return
1235 if {![info exists selectedline]} return
1236 foreach l $matchinglines {
1237 if {$l > $selectedline} {
1238 findselectline $l
1239 return
1242 bell
1245 proc findprev {} {
1246 global matchinglines selectedline
1247 if {![info exists matchinglines]} {
1248 dofind
1249 return
1251 if {![info exists selectedline]} return
1252 set prev {}
1253 foreach l $matchinglines {
1254 if {$l >= $selectedline} break
1255 set prev $l
1257 if {$prev != {}} {
1258 findselectline $prev
1259 } else {
1260 bell
1264 proc findlocchange {name ix op} {
1265 global findloc findtype findtypemenu
1266 if {$findloc == "Pickaxe"} {
1267 set findtype Exact
1268 set state disabled
1269 } else {
1270 set state normal
1272 $findtypemenu entryconf 1 -state $state
1273 $findtypemenu entryconf 2 -state $state
1276 proc stopfindproc {{done 0}} {
1277 global findprocpid findprocfile findids
1278 global ctext findoldcursor phase maincursor textcursor
1279 global findinprogress
1281 catch {unset findids}
1282 if {[info exists findprocpid]} {
1283 if {!$done} {
1284 catch {exec kill $findprocpid}
1286 catch {close $findprocfile}
1287 unset findprocpid
1289 if {[info exists findinprogress]} {
1290 unset findinprogress
1291 if {$phase != "incrdraw"} {
1292 . config -cursor $maincursor
1293 $ctext config -cursor $textcursor
1298 proc findpatches {} {
1299 global findstring selectedline numcommits
1300 global findprocpid findprocfile
1301 global finddidsel ctext lineid findinprogress
1302 global findinsertpos
1304 if {$numcommits == 0} return
1306 # make a list of all the ids to search, starting at the one
1307 # after the selected line (if any)
1308 if {[info exists selectedline]} {
1309 set l $selectedline
1310 } else {
1311 set l -1
1313 set inputids {}
1314 for {set i 0} {$i < $numcommits} {incr i} {
1315 if {[incr l] >= $numcommits} {
1316 set l 0
1318 append inputids $lineid($l) "\n"
1321 if {[catch {
1322 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1323 << $inputids] r]
1324 } err]} {
1325 error_popup "Error starting search process: $err"
1326 return
1329 set findinsertpos end
1330 set findprocfile $f
1331 set findprocpid [pid $f]
1332 fconfigure $f -blocking 0
1333 fileevent $f readable readfindproc
1334 set finddidsel 0
1335 . config -cursor watch
1336 $ctext config -cursor watch
1337 set findinprogress 1
1340 proc readfindproc {} {
1341 global findprocfile finddidsel
1342 global idline matchinglines findinsertpos
1344 set n [gets $findprocfile line]
1345 if {$n < 0} {
1346 if {[eof $findprocfile]} {
1347 stopfindproc 1
1348 if {!$finddidsel} {
1349 bell
1352 return
1354 if {![regexp {^[0-9a-f]{40}} $line id]} {
1355 error_popup "Can't parse git-diff-tree output: $line"
1356 stopfindproc
1357 return
1359 if {![info exists idline($id)]} {
1360 puts stderr "spurious id: $id"
1361 return
1363 set l $idline($id)
1364 insertmatch $l $id
1367 proc insertmatch {l id} {
1368 global matchinglines findinsertpos finddidsel
1370 if {$findinsertpos == "end"} {
1371 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1372 set matchinglines [linsert $matchinglines 0 $l]
1373 set findinsertpos 1
1374 } else {
1375 lappend matchinglines $l
1377 } else {
1378 set matchinglines [linsert $matchinglines $findinsertpos $l]
1379 incr findinsertpos
1381 markheadline $l $id
1382 if {!$finddidsel} {
1383 findselectline $l
1384 set finddidsel 1
1388 proc findfiles {} {
1389 global selectedline numcommits lineid ctext
1390 global ffileline finddidsel parents nparents
1391 global findinprogress findstartline findinsertpos
1392 global treediffs fdiffids fdiffsneeded fdiffpos
1393 global findmergefiles
1395 if {$numcommits == 0} return
1397 if {[info exists selectedline]} {
1398 set l [expr {$selectedline + 1}]
1399 } else {
1400 set l 0
1402 set ffileline $l
1403 set findstartline $l
1404 set diffsneeded {}
1405 set fdiffsneeded {}
1406 while 1 {
1407 set id $lineid($l)
1408 if {$findmergefiles || $nparents($id) == 1} {
1409 foreach p $parents($id) {
1410 if {![info exists treediffs([list $id $p])]} {
1411 append diffsneeded "$id $p\n"
1412 lappend fdiffsneeded [list $id $p]
1416 if {[incr l] >= $numcommits} {
1417 set l 0
1419 if {$l == $findstartline} break
1422 # start off a git-diff-tree process if needed
1423 if {$diffsneeded ne {}} {
1424 if {[catch {
1425 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1426 } err ]} {
1427 error_popup "Error starting search process: $err"
1428 return
1430 catch {unset fdiffids}
1431 set fdiffpos 0
1432 fconfigure $df -blocking 0
1433 fileevent $df readable [list readfilediffs $df]
1436 set finddidsel 0
1437 set findinsertpos end
1438 set id $lineid($l)
1439 set p [lindex $parents($id) 0]
1440 . config -cursor watch
1441 $ctext config -cursor watch
1442 set findinprogress 1
1443 findcont [list $id $p]
1444 update
1447 proc readfilediffs {df} {
1448 global findids fdiffids fdiffs
1450 set n [gets $df line]
1451 if {$n < 0} {
1452 if {[eof $df]} {
1453 donefilediff
1454 if {[catch {close $df} err]} {
1455 stopfindproc
1456 bell
1457 error_popup "Error in git-diff-tree: $err"
1458 } elseif {[info exists findids]} {
1459 set ids $findids
1460 stopfindproc
1461 bell
1462 error_popup "Couldn't find diffs for {$ids}"
1465 return
1467 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1468 # start of a new string of diffs
1469 donefilediff
1470 set fdiffids [list $id $p]
1471 set fdiffs {}
1472 } elseif {[string match ":*" $line]} {
1473 lappend fdiffs [lindex $line 5]
1477 proc donefilediff {} {
1478 global fdiffids fdiffs treediffs findids
1479 global fdiffsneeded fdiffpos
1481 if {[info exists fdiffids]} {
1482 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1483 && $fdiffpos < [llength $fdiffsneeded]} {
1484 # git-diff-tree doesn't output anything for a commit
1485 # which doesn't change anything
1486 set nullids [lindex $fdiffsneeded $fdiffpos]
1487 set treediffs($nullids) {}
1488 if {[info exists findids] && $nullids eq $findids} {
1489 unset findids
1490 findcont $nullids
1492 incr fdiffpos
1494 incr fdiffpos
1496 if {![info exists treediffs($fdiffids)]} {
1497 set treediffs($fdiffids) $fdiffs
1499 if {[info exists findids] && $fdiffids eq $findids} {
1500 unset findids
1501 findcont $fdiffids
1506 proc findcont {ids} {
1507 global findids treediffs parents nparents treepending
1508 global ffileline findstartline finddidsel
1509 global lineid numcommits matchinglines findinprogress
1510 global findmergefiles
1512 set id [lindex $ids 0]
1513 set p [lindex $ids 1]
1514 set pi [lsearch -exact $parents($id) $p]
1515 set l $ffileline
1516 while 1 {
1517 if {$findmergefiles || $nparents($id) == 1} {
1518 if {![info exists treediffs($ids)]} {
1519 set findids $ids
1520 set ffileline $l
1521 return
1523 set doesmatch 0
1524 foreach f $treediffs($ids) {
1525 set x [findmatches $f]
1526 if {$x != {}} {
1527 set doesmatch 1
1528 break
1531 if {$doesmatch} {
1532 insertmatch $l $id
1533 set pi $nparents($id)
1535 } else {
1536 set pi $nparents($id)
1538 if {[incr pi] >= $nparents($id)} {
1539 set pi 0
1540 if {[incr l] >= $numcommits} {
1541 set l 0
1543 if {$l == $findstartline} break
1544 set id $lineid($l)
1546 set p [lindex $parents($id) $pi]
1547 set ids [list $id $p]
1549 stopfindproc
1550 if {!$finddidsel} {
1551 bell
1555 # mark a commit as matching by putting a yellow background
1556 # behind the headline
1557 proc markheadline {l id} {
1558 global canv mainfont linehtag commitinfo
1560 set bbox [$canv bbox $linehtag($l)]
1561 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1562 $canv lower $t
1565 # mark the bits of a headline, author or date that match a find string
1566 proc markmatches {canv l str tag matches font} {
1567 set bbox [$canv bbox $tag]
1568 set x0 [lindex $bbox 0]
1569 set y0 [lindex $bbox 1]
1570 set y1 [lindex $bbox 3]
1571 foreach match $matches {
1572 set start [lindex $match 0]
1573 set end [lindex $match 1]
1574 if {$start > $end} continue
1575 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1576 set xlen [font measure $font [string range $str 0 [expr $end]]]
1577 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1578 -outline {} -tags matches -fill yellow]
1579 $canv lower $t
1583 proc unmarkmatches {} {
1584 global matchinglines findids
1585 allcanvs delete matches
1586 catch {unset matchinglines}
1587 catch {unset findids}
1590 proc selcanvline {w x y} {
1591 global canv canvy0 ctext linespc selectedline
1592 global lineid linehtag linentag linedtag rowtextx
1593 set ymax [lindex [$canv cget -scrollregion] 3]
1594 if {$ymax == {}} return
1595 set yfrac [lindex [$canv yview] 0]
1596 set y [expr {$y + $yfrac * $ymax}]
1597 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1598 if {$l < 0} {
1599 set l 0
1601 if {$w eq $canv} {
1602 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1604 unmarkmatches
1605 selectline $l
1608 proc selectline {l} {
1609 global canv canv2 canv3 ctext commitinfo selectedline
1610 global lineid linehtag linentag linedtag
1611 global canvy0 linespc parents nparents
1612 global cflist currentid sha1entry
1613 global commentend idtags
1614 $canv delete hover
1615 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1616 $canv delete secsel
1617 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1618 -tags secsel -fill [$canv cget -selectbackground]]
1619 $canv lower $t
1620 $canv2 delete secsel
1621 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1622 -tags secsel -fill [$canv2 cget -selectbackground]]
1623 $canv2 lower $t
1624 $canv3 delete secsel
1625 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1626 -tags secsel -fill [$canv3 cget -selectbackground]]
1627 $canv3 lower $t
1628 set y [expr {$canvy0 + $l * $linespc}]
1629 set ymax [lindex [$canv cget -scrollregion] 3]
1630 set ytop [expr {$y - $linespc - 1}]
1631 set ybot [expr {$y + $linespc + 1}]
1632 set wnow [$canv yview]
1633 set wtop [expr [lindex $wnow 0] * $ymax]
1634 set wbot [expr [lindex $wnow 1] * $ymax]
1635 set wh [expr {$wbot - $wtop}]
1636 set newtop $wtop
1637 if {$ytop < $wtop} {
1638 if {$ybot < $wtop} {
1639 set newtop [expr {$y - $wh / 2.0}]
1640 } else {
1641 set newtop $ytop
1642 if {$newtop > $wtop - $linespc} {
1643 set newtop [expr {$wtop - $linespc}]
1646 } elseif {$ybot > $wbot} {
1647 if {$ytop > $wbot} {
1648 set newtop [expr {$y - $wh / 2.0}]
1649 } else {
1650 set newtop [expr {$ybot - $wh}]
1651 if {$newtop < $wtop + $linespc} {
1652 set newtop [expr {$wtop + $linespc}]
1656 if {$newtop != $wtop} {
1657 if {$newtop < 0} {
1658 set newtop 0
1660 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1662 set selectedline $l
1664 set id $lineid($l)
1665 set currentid $id
1666 $sha1entry delete 0 end
1667 $sha1entry insert 0 $id
1668 $sha1entry selection from 0
1669 $sha1entry selection to end
1671 $ctext conf -state normal
1672 $ctext delete 0.0 end
1673 $ctext mark set fmark.0 0.0
1674 $ctext mark gravity fmark.0 left
1675 set info $commitinfo($id)
1676 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1677 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1678 if {[info exists idtags($id)]} {
1679 $ctext insert end "Tags:"
1680 foreach tag $idtags($id) {
1681 $ctext insert end " $tag"
1683 $ctext insert end "\n"
1685 $ctext insert end "\n"
1686 $ctext insert end [lindex $info 5]
1687 $ctext insert end "\n"
1688 $ctext tag delete Comments
1689 $ctext tag remove found 1.0 end
1690 $ctext conf -state disabled
1691 set commentend [$ctext index "end - 1c"]
1693 $cflist delete 0 end
1694 $cflist insert end "Comments"
1695 startdiff $id $parents($id)
1698 proc startdiff {id vs} {
1699 global diffpending diffpindex
1700 global diffindex difffilestart
1701 global curdifftag curtagstart
1703 set diffpending $vs
1704 set diffpindex 0
1705 set diffindex 0
1706 catch {unset difffilestart}
1707 set curdifftag Comments
1708 set curtagstart 0.0
1709 contdiff [list $id [lindex $vs 0]]
1712 proc contdiff {ids} {
1713 global treediffs diffids treepending
1715 set diffids $ids
1716 if {![info exists treediffs($ids)]} {
1717 if {![info exists treepending]} {
1718 gettreediffs $ids
1720 } else {
1721 addtocflist $ids
1725 proc selnextline {dir} {
1726 global selectedline
1727 if {![info exists selectedline]} return
1728 set l [expr $selectedline + $dir]
1729 unmarkmatches
1730 selectline $l
1733 proc addtocflist {ids} {
1734 global treediffs cflist diffpindex
1736 set colors {black blue green red cyan magenta}
1737 set color [lindex $colors [expr {$diffpindex % [llength $colors]}]]
1738 foreach f $treediffs($ids) {
1739 $cflist insert end $f
1740 $cflist itemconf end -foreground $color
1742 getblobdiffs $ids
1745 proc gettreediffs {ids} {
1746 global treediffs parents treepending
1747 set treepending $ids
1748 set treediffs($ids) {}
1749 set id [lindex $ids 0]
1750 set p [lindex $ids 1]
1751 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1752 fconfigure $gdtf -blocking 0
1753 fileevent $gdtf readable "gettreediffline $gdtf {$ids}"
1756 proc gettreediffline {gdtf ids} {
1757 global treediffs treepending diffids
1758 set n [gets $gdtf line]
1759 if {$n < 0} {
1760 if {![eof $gdtf]} return
1761 close $gdtf
1762 unset treepending
1763 if {[info exists diffids]} {
1764 if {$ids != $diffids} {
1765 gettreediffs $diffids
1766 } else {
1767 addtocflist $ids
1770 return
1772 set file [lindex $line 5]
1773 lappend treediffs($ids) $file
1776 proc getblobdiffs {ids} {
1777 global diffopts blobdifffd diffids env
1778 global nextupdate diffinhdr
1780 set id [lindex $ids 0]
1781 set p [lindex $ids 1]
1782 set env(GIT_DIFF_OPTS) $diffopts
1783 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
1784 puts "error getting diffs: $err"
1785 return
1787 set diffinhdr 0
1788 fconfigure $bdf -blocking 0
1789 set blobdifffd($ids) $bdf
1790 fileevent $bdf readable [list getblobdiffline $bdf $ids]
1791 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1794 proc getblobdiffline {bdf ids} {
1795 global diffids blobdifffd ctext curdifftag curtagstart
1796 global diffnexthead diffnextnote diffindex difffilestart
1797 global nextupdate diffpending diffpindex diffinhdr
1798 global gaudydiff
1800 set n [gets $bdf line]
1801 if {$n < 0} {
1802 if {[eof $bdf]} {
1803 close $bdf
1804 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1805 $ctext tag add $curdifftag $curtagstart end
1806 if {[incr diffpindex] < [llength $diffpending]} {
1807 set id [lindex $ids 0]
1808 set p [lindex $diffpending $diffpindex]
1809 contdiff [list $id $p]
1813 return
1815 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1816 return
1818 $ctext conf -state normal
1819 if {[regexp {^diff --git a/(.*) b/} $line match fname]} {
1820 # start of a new file
1821 $ctext insert end "\n"
1822 $ctext tag add $curdifftag $curtagstart end
1823 set curtagstart [$ctext index "end - 1c"]
1824 set header $fname
1825 set here [$ctext index "end - 1c"]
1826 set difffilestart($diffindex) $here
1827 incr diffindex
1828 # start mark names at fmark.1 for first file
1829 $ctext mark set fmark.$diffindex $here
1830 $ctext mark gravity fmark.$diffindex left
1831 set curdifftag "f:$fname"
1832 $ctext tag delete $curdifftag
1833 set l [expr {(78 - [string length $header]) / 2}]
1834 set pad [string range "----------------------------------------" 1 $l]
1835 $ctext insert end "$pad $header $pad\n" filesep
1836 set diffinhdr 1
1837 } elseif {[regexp {^(---|\+\+\+)} $line]} {
1838 set diffinhdr 0
1839 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1840 $line match f1l f1c f2l f2c rest]} {
1841 if {$gaudydiff} {
1842 $ctext insert end "\t" hunksep
1843 $ctext insert end " $f1l " d0 " $f2l " d1
1844 $ctext insert end " $rest \n" hunksep
1845 } else {
1846 $ctext insert end "$line\n" hunksep
1848 set diffinhdr 0
1849 } else {
1850 set x [string range $line 0 0]
1851 if {$x == "-" || $x == "+"} {
1852 set tag [expr {$x == "+"}]
1853 if {$gaudydiff} {
1854 set line [string range $line 1 end]
1856 $ctext insert end "$line\n" d$tag
1857 } elseif {$x == " "} {
1858 if {$gaudydiff} {
1859 set line [string range $line 1 end]
1861 $ctext insert end "$line\n"
1862 } elseif {$diffinhdr || $x == "\\"} {
1863 # e.g. "\ No newline at end of file"
1864 $ctext insert end "$line\n" filesep
1865 } else {
1866 # Something else we don't recognize
1867 if {$curdifftag != "Comments"} {
1868 $ctext insert end "\n"
1869 $ctext tag add $curdifftag $curtagstart end
1870 set curtagstart [$ctext index "end - 1c"]
1871 set curdifftag Comments
1873 $ctext insert end "$line\n" filesep
1876 $ctext conf -state disabled
1877 if {[clock clicks -milliseconds] >= $nextupdate} {
1878 incr nextupdate 100
1879 fileevent $bdf readable {}
1880 update
1881 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1885 proc nextfile {} {
1886 global difffilestart ctext
1887 set here [$ctext index @0,0]
1888 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1889 if {[$ctext compare $difffilestart($i) > $here]} {
1890 $ctext yview $difffilestart($i)
1891 break
1896 proc listboxsel {} {
1897 global ctext cflist currentid treediffs
1898 if {![info exists currentid]} return
1899 set sel [lsort [$cflist curselection]]
1900 if {$sel eq {}} return
1901 set first [lindex $sel 0]
1902 catch {$ctext yview fmark.$first}
1905 proc setcoords {} {
1906 global linespc charspc canvx0 canvy0 mainfont
1907 set linespc [font metrics $mainfont -linespace]
1908 set charspc [font measure $mainfont "m"]
1909 set canvy0 [expr 3 + 0.5 * $linespc]
1910 set canvx0 [expr 3 + 0.5 * $linespc]
1913 proc redisplay {} {
1914 global selectedline stopped redisplaying phase
1915 if {$stopped > 1} return
1916 if {$phase == "getcommits"} return
1917 set redisplaying 1
1918 if {$phase == "drawgraph" || $phase == "incrdraw"} {
1919 set stopped 1
1920 } else {
1921 drawgraph
1925 proc incrfont {inc} {
1926 global mainfont namefont textfont selectedline ctext canv phase
1927 global stopped entries
1928 unmarkmatches
1929 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1930 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1931 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1932 setcoords
1933 $ctext conf -font $textfont
1934 $ctext tag conf filesep -font [concat $textfont bold]
1935 foreach e $entries {
1936 $e conf -font $mainfont
1938 if {$phase == "getcommits"} {
1939 $canv itemconf textitems -font $mainfont
1941 redisplay
1944 proc clearsha1 {} {
1945 global sha1entry sha1string
1946 if {[string length $sha1string] == 40} {
1947 $sha1entry delete 0 end
1951 proc sha1change {n1 n2 op} {
1952 global sha1string currentid sha1but
1953 if {$sha1string == {}
1954 || ([info exists currentid] && $sha1string == $currentid)} {
1955 set state disabled
1956 } else {
1957 set state normal
1959 if {[$sha1but cget -state] == $state} return
1960 if {$state == "normal"} {
1961 $sha1but conf -state normal -relief raised -text "Goto: "
1962 } else {
1963 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
1967 proc gotocommit {} {
1968 global sha1string currentid idline tagids
1969 global lineid numcommits
1971 if {$sha1string == {}
1972 || ([info exists currentid] && $sha1string == $currentid)} return
1973 if {[info exists tagids($sha1string)]} {
1974 set id $tagids($sha1string)
1975 } else {
1976 set id [string tolower $sha1string]
1977 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
1978 set matches {}
1979 for {set l 0} {$l < $numcommits} {incr l} {
1980 if {[string match $id* $lineid($l)]} {
1981 lappend matches $lineid($l)
1984 if {$matches ne {}} {
1985 if {[llength $matches] > 1} {
1986 error_popup "Short SHA1 id $id is ambiguous"
1987 return
1989 set id [lindex $matches 0]
1993 if {[info exists idline($id)]} {
1994 selectline $idline($id)
1995 return
1997 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
1998 set type "SHA1 id"
1999 } else {
2000 set type "Tag"
2002 error_popup "$type $sha1string is not known"
2005 proc lineenter {x y id} {
2006 global hoverx hovery hoverid hovertimer
2007 global commitinfo canv
2009 if {![info exists commitinfo($id)]} return
2010 set hoverx $x
2011 set hovery $y
2012 set hoverid $id
2013 if {[info exists hovertimer]} {
2014 after cancel $hovertimer
2016 set hovertimer [after 500 linehover]
2017 $canv delete hover
2020 proc linemotion {x y id} {
2021 global hoverx hovery hoverid hovertimer
2023 if {[info exists hoverid] && $id == $hoverid} {
2024 set hoverx $x
2025 set hovery $y
2026 if {[info exists hovertimer]} {
2027 after cancel $hovertimer
2029 set hovertimer [after 500 linehover]
2033 proc lineleave {id} {
2034 global hoverid hovertimer canv
2036 if {[info exists hoverid] && $id == $hoverid} {
2037 $canv delete hover
2038 if {[info exists hovertimer]} {
2039 after cancel $hovertimer
2040 unset hovertimer
2042 unset hoverid
2046 proc linehover {} {
2047 global hoverx hovery hoverid hovertimer
2048 global canv linespc lthickness
2049 global commitinfo mainfont
2051 set text [lindex $commitinfo($hoverid) 0]
2052 set ymax [lindex [$canv cget -scrollregion] 3]
2053 if {$ymax == {}} return
2054 set yfrac [lindex [$canv yview] 0]
2055 set x [expr {$hoverx + 2 * $linespc}]
2056 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2057 set x0 [expr {$x - 2 * $lthickness}]
2058 set y0 [expr {$y - 2 * $lthickness}]
2059 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2060 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2061 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2062 -fill \#ffff80 -outline black -width 1 -tags hover]
2063 $canv raise $t
2064 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2065 $canv raise $t
2068 proc lineclick {x y id} {
2069 global ctext commitinfo children cflist canv
2071 unmarkmatches
2072 $canv delete hover
2073 # fill the details pane with info about this line
2074 $ctext conf -state normal
2075 $ctext delete 0.0 end
2076 $ctext insert end "Parent:\n "
2077 catch {destroy $ctext.$id}
2078 button $ctext.$id -text "Go:" -command "selbyid $id" \
2079 -padx 4 -pady 0
2080 $ctext window create end -window $ctext.$id -align center
2081 set info $commitinfo($id)
2082 $ctext insert end "\t[lindex $info 0]\n"
2083 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2084 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2085 $ctext insert end "\tID:\t$id\n"
2086 if {[info exists children($id)]} {
2087 $ctext insert end "\nChildren:"
2088 foreach child $children($id) {
2089 $ctext insert end "\n "
2090 catch {destroy $ctext.$child}
2091 button $ctext.$child -text "Go:" -command "selbyid $child" \
2092 -padx 4 -pady 0
2093 $ctext window create end -window $ctext.$child -align center
2094 set info $commitinfo($child)
2095 $ctext insert end "\t[lindex $info 0]"
2098 $ctext conf -state disabled
2100 $cflist delete 0 end
2103 proc selbyid {id} {
2104 global idline
2105 if {[info exists idline($id)]} {
2106 selectline $idline($id)
2110 proc mstime {} {
2111 global startmstime
2112 if {![info exists startmstime]} {
2113 set startmstime [clock clicks -milliseconds]
2115 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2118 proc rowmenu {x y id} {
2119 global rowctxmenu idline selectedline rowmenuid
2121 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2122 set state disabled
2123 } else {
2124 set state normal
2126 $rowctxmenu entryconfigure 0 -state $state
2127 $rowctxmenu entryconfigure 1 -state $state
2128 $rowctxmenu entryconfigure 2 -state $state
2129 set rowmenuid $id
2130 tk_popup $rowctxmenu $x $y
2133 proc diffvssel {dirn} {
2134 global rowmenuid selectedline lineid
2135 global ctext cflist
2136 global commitinfo
2138 if {![info exists selectedline]} return
2139 if {$dirn} {
2140 set oldid $lineid($selectedline)
2141 set newid $rowmenuid
2142 } else {
2143 set oldid $rowmenuid
2144 set newid $lineid($selectedline)
2146 $ctext conf -state normal
2147 $ctext delete 0.0 end
2148 $ctext mark set fmark.0 0.0
2149 $ctext mark gravity fmark.0 left
2150 $cflist delete 0 end
2151 $cflist insert end "Top"
2152 $ctext insert end "From $oldid\n "
2153 $ctext insert end [lindex $commitinfo($oldid) 0]
2154 $ctext insert end "\n\nTo $newid\n "
2155 $ctext insert end [lindex $commitinfo($newid) 0]
2156 $ctext insert end "\n"
2157 $ctext conf -state disabled
2158 $ctext tag delete Comments
2159 $ctext tag remove found 1.0 end
2160 startdiff [list $newid $oldid]
2163 proc mkpatch {} {
2164 global rowmenuid currentid commitinfo patchtop patchnum
2166 if {![info exists currentid]} return
2167 set oldid $currentid
2168 set oldhead [lindex $commitinfo($oldid) 0]
2169 set newid $rowmenuid
2170 set newhead [lindex $commitinfo($newid) 0]
2171 set top .patch
2172 set patchtop $top
2173 catch {destroy $top}
2174 toplevel $top
2175 label $top.title -text "Generate patch"
2176 grid $top.title - -pady 10
2177 label $top.from -text "From:"
2178 entry $top.fromsha1 -width 40 -relief flat
2179 $top.fromsha1 insert 0 $oldid
2180 $top.fromsha1 conf -state readonly
2181 grid $top.from $top.fromsha1 -sticky w
2182 entry $top.fromhead -width 60 -relief flat
2183 $top.fromhead insert 0 $oldhead
2184 $top.fromhead conf -state readonly
2185 grid x $top.fromhead -sticky w
2186 label $top.to -text "To:"
2187 entry $top.tosha1 -width 40 -relief flat
2188 $top.tosha1 insert 0 $newid
2189 $top.tosha1 conf -state readonly
2190 grid $top.to $top.tosha1 -sticky w
2191 entry $top.tohead -width 60 -relief flat
2192 $top.tohead insert 0 $newhead
2193 $top.tohead conf -state readonly
2194 grid x $top.tohead -sticky w
2195 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2196 grid $top.rev x -pady 10
2197 label $top.flab -text "Output file:"
2198 entry $top.fname -width 60
2199 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2200 incr patchnum
2201 grid $top.flab $top.fname -sticky w
2202 frame $top.buts
2203 button $top.buts.gen -text "Generate" -command mkpatchgo
2204 button $top.buts.can -text "Cancel" -command mkpatchcan
2205 grid $top.buts.gen $top.buts.can
2206 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2207 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2208 grid $top.buts - -pady 10 -sticky ew
2209 focus $top.fname
2212 proc mkpatchrev {} {
2213 global patchtop
2215 set oldid [$patchtop.fromsha1 get]
2216 set oldhead [$patchtop.fromhead get]
2217 set newid [$patchtop.tosha1 get]
2218 set newhead [$patchtop.tohead get]
2219 foreach e [list fromsha1 fromhead tosha1 tohead] \
2220 v [list $newid $newhead $oldid $oldhead] {
2221 $patchtop.$e conf -state normal
2222 $patchtop.$e delete 0 end
2223 $patchtop.$e insert 0 $v
2224 $patchtop.$e conf -state readonly
2228 proc mkpatchgo {} {
2229 global patchtop
2231 set oldid [$patchtop.fromsha1 get]
2232 set newid [$patchtop.tosha1 get]
2233 set fname [$patchtop.fname get]
2234 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2235 error_popup "Error creating patch: $err"
2237 catch {destroy $patchtop}
2238 unset patchtop
2241 proc mkpatchcan {} {
2242 global patchtop
2244 catch {destroy $patchtop}
2245 unset patchtop
2248 proc mktag {} {
2249 global rowmenuid mktagtop commitinfo
2251 set top .maketag
2252 set mktagtop $top
2253 catch {destroy $top}
2254 toplevel $top
2255 label $top.title -text "Create tag"
2256 grid $top.title - -pady 10
2257 label $top.id -text "ID:"
2258 entry $top.sha1 -width 40 -relief flat
2259 $top.sha1 insert 0 $rowmenuid
2260 $top.sha1 conf -state readonly
2261 grid $top.id $top.sha1 -sticky w
2262 entry $top.head -width 60 -relief flat
2263 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2264 $top.head conf -state readonly
2265 grid x $top.head -sticky w
2266 label $top.tlab -text "Tag name:"
2267 entry $top.tag -width 60
2268 grid $top.tlab $top.tag -sticky w
2269 frame $top.buts
2270 button $top.buts.gen -text "Create" -command mktaggo
2271 button $top.buts.can -text "Cancel" -command mktagcan
2272 grid $top.buts.gen $top.buts.can
2273 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2274 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2275 grid $top.buts - -pady 10 -sticky ew
2276 focus $top.tag
2279 proc domktag {} {
2280 global mktagtop env tagids idtags
2281 global idpos idline linehtag canv selectedline
2283 set id [$mktagtop.sha1 get]
2284 set tag [$mktagtop.tag get]
2285 if {$tag == {}} {
2286 error_popup "No tag name specified"
2287 return
2289 if {[info exists tagids($tag)]} {
2290 error_popup "Tag \"$tag\" already exists"
2291 return
2293 if {[catch {
2294 set dir ".git"
2295 if {[info exists env(GIT_DIR)]} {
2296 set dir $env(GIT_DIR)
2298 set fname [file join $dir "refs/tags" $tag]
2299 set f [open $fname w]
2300 puts $f $id
2301 close $f
2302 } err]} {
2303 error_popup "Error creating tag: $err"
2304 return
2307 set tagids($tag) $id
2308 lappend idtags($id) $tag
2309 $canv delete tag.$id
2310 set xt [eval drawtags $id $idpos($id)]
2311 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2312 if {[info exists selectedline] && $selectedline == $idline($id)} {
2313 selectline $selectedline
2317 proc mktagcan {} {
2318 global mktagtop
2320 catch {destroy $mktagtop}
2321 unset mktagtop
2324 proc mktaggo {} {
2325 domktag
2326 mktagcan
2329 proc writecommit {} {
2330 global rowmenuid wrcomtop commitinfo wrcomcmd
2332 set top .writecommit
2333 set wrcomtop $top
2334 catch {destroy $top}
2335 toplevel $top
2336 label $top.title -text "Write commit to file"
2337 grid $top.title - -pady 10
2338 label $top.id -text "ID:"
2339 entry $top.sha1 -width 40 -relief flat
2340 $top.sha1 insert 0 $rowmenuid
2341 $top.sha1 conf -state readonly
2342 grid $top.id $top.sha1 -sticky w
2343 entry $top.head -width 60 -relief flat
2344 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2345 $top.head conf -state readonly
2346 grid x $top.head -sticky w
2347 label $top.clab -text "Command:"
2348 entry $top.cmd -width 60 -textvariable wrcomcmd
2349 grid $top.clab $top.cmd -sticky w -pady 10
2350 label $top.flab -text "Output file:"
2351 entry $top.fname -width 60
2352 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2353 grid $top.flab $top.fname -sticky w
2354 frame $top.buts
2355 button $top.buts.gen -text "Write" -command wrcomgo
2356 button $top.buts.can -text "Cancel" -command wrcomcan
2357 grid $top.buts.gen $top.buts.can
2358 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2359 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2360 grid $top.buts - -pady 10 -sticky ew
2361 focus $top.fname
2364 proc wrcomgo {} {
2365 global wrcomtop
2367 set id [$wrcomtop.sha1 get]
2368 set cmd "echo $id | [$wrcomtop.cmd get]"
2369 set fname [$wrcomtop.fname get]
2370 if {[catch {exec sh -c $cmd >$fname &} err]} {
2371 error_popup "Error writing commit: $err"
2373 catch {destroy $wrcomtop}
2374 unset wrcomtop
2377 proc wrcomcan {} {
2378 global wrcomtop
2380 catch {destroy $wrcomtop}
2381 unset wrcomtop
2384 proc doquit {} {
2385 global stopped
2386 set stopped 100
2387 destroy .
2390 # defaults...
2391 set datemode 0
2392 set boldnames 0
2393 set diffopts "-U 5 -p"
2394 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2396 set mainfont {Helvetica 9}
2397 set textfont {Courier 9}
2398 set findmergefiles 0
2399 set gaudydiff 0
2401 set colors {green red blue magenta darkgrey brown orange}
2403 catch {source ~/.gitk}
2405 set namefont $mainfont
2406 if {$boldnames} {
2407 lappend namefont bold
2410 set revtreeargs {}
2411 foreach arg $argv {
2412 switch -regexp -- $arg {
2413 "^$" { }
2414 "^-b" { set boldnames 1 }
2415 "^-d" { set datemode 1 }
2416 default {
2417 lappend revtreeargs $arg
2422 set stopped 0
2423 set redisplaying 0
2424 set stuffsaved 0
2425 set patchnum 0
2426 setcoords
2427 makewindow
2428 readrefs
2429 getcommits $revtreeargs