Calculate the list of interesting files for a merge.
[git/dscho.git] / gitk
blob4cc59becd37bc0f83171905ab05d542dc650f2cc
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc getcommits {rargs} {
11 global commits commfd phase canv mainfont env
12 global startmsecs nextupdate
13 global ctext maincursor textcursor leftover
15 # check that we can find a .git directory somewhere...
16 if {[info exists env(GIT_DIR)]} {
17 set gitdir $env(GIT_DIR)
18 } else {
19 set gitdir ".git"
21 if {![file isdirectory $gitdir]} {
22 error_popup "Cannot find the git directory \"$gitdir\"."
23 exit 1
25 set commits {}
26 set phase getcommits
27 set startmsecs [clock clicks -milliseconds]
28 set nextupdate [expr $startmsecs + 100]
29 if [catch {
30 set parse_args [concat --default HEAD $rargs]
31 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
32 }] {
33 # if git-rev-parse failed for some reason...
34 if {$rargs == {}} {
35 set rargs HEAD
37 set parsed_args $rargs
39 if [catch {
40 set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
41 } err] {
42 puts stderr "Error executing git-rev-list: $err"
43 exit 1
45 set leftover {}
46 fconfigure $commfd -blocking 0 -translation binary
47 fileevent $commfd readable "getcommitlines $commfd"
48 $canv delete all
49 $canv create text 3 3 -anchor nw -text "Reading commits..." \
50 -font $mainfont -tags textitems
51 . config -cursor watch
52 $ctext config -cursor watch
55 proc getcommitlines {commfd} {
56 global commits parents cdate children nchildren
57 global commitlisted phase commitinfo nextupdate
58 global stopped redisplaying leftover
60 set stuff [read $commfd]
61 if {$stuff == {}} {
62 if {![eof $commfd]} return
63 # 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
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 if {$nparents($id) == 1} {
1696 startdiff [concat $id $parents($id)]
1697 } elseif {$nparents($id) > 1} {
1698 mergediff $id
1702 proc selnextline {dir} {
1703 global selectedline
1704 if {![info exists selectedline]} return
1705 set l [expr $selectedline + $dir]
1706 unmarkmatches
1707 selectline $l
1710 proc mergediff {id} {
1711 global parents diffmergeid diffmergegca mergefilelist diffpindex
1713 set diffmergeid $id
1714 set diffpindex -1
1715 set diffmergegca [findgca $parents($id)]
1716 if {[info exists mergefilelist($id)]} {
1717 showmergediff
1718 } else {
1719 contmergediff {}
1723 proc findgca {ids} {
1724 set gca {}
1725 foreach id $ids {
1726 if {$gca eq {}} {
1727 set gca $id
1728 } else {
1729 if {[catch {
1730 set gca [exec git-merge-base $gca $id]
1731 } err]} {
1732 return {}
1736 return $gca
1739 proc contmergediff {ids} {
1740 global diffmergeid diffpindex parents nparents diffmergegca
1741 global treediffs mergefilelist diffids
1743 # diff the child against each of the parents, and diff
1744 # each of the parents against the GCA.
1745 while 1 {
1746 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1747 set ids [list [lindex $ids 1] $diffmergegca]
1748 } else {
1749 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1750 set p [lindex $parents($diffmergeid) $diffpindex]
1751 set ids [list $diffmergeid $p]
1753 if {![info exists treediffs($ids)]} {
1754 set diffids $ids
1755 gettreediffs $ids
1756 return
1760 # If a file in some parent is different from the child and also
1761 # different from the GCA, then it's interesting.
1762 # If we don't have a GCA, then a file is interesting if it is
1763 # different from the child in all the parents.
1764 if {$diffmergegca ne {}} {
1765 set files {}
1766 foreach p $parents($diffmergeid) {
1767 set gcadiffs $treediffs([list $p $diffmergegca])
1768 foreach f $treediffs([list $diffmergeid $p]) {
1769 if {[lsearch -exact $files $f] < 0
1770 && [lsearch -exact $gcadiffs $f] >= 0} {
1771 lappend files $f
1775 set files [lsort $files]
1776 } else {
1777 set p [lindex $parents($diffmergeid) 0]
1778 set files $treediffs([list $diffmergeid $p])
1779 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1780 set p [lindex $parents($diffmergeid) $i]
1781 set df $treediffs([list $diffmergeid $p])
1782 set nf {}
1783 foreach f $files {
1784 if {[lsearch -exact $df $f] >= 0} {
1785 lappend nf $f
1788 set files $nf
1792 set mergefilelist($diffmergeid) $files
1793 showmergediff
1796 proc showmergediff {} {
1797 global cflist diffmergeid mergefilelist
1799 set files $mergefilelist($diffmergeid)
1800 foreach f $files {
1801 $cflist insert end $f
1805 proc startdiff {ids} {
1806 global treediffs diffids treepending diffmergeid
1808 set diffids $ids
1809 catch {unset diffmergeid}
1810 if {![info exists treediffs($ids)]} {
1811 if {![info exists treepending]} {
1812 gettreediffs $ids
1814 } else {
1815 addtocflist $ids
1819 proc addtocflist {ids} {
1820 global treediffs cflist
1821 foreach f $treediffs($ids) {
1822 $cflist insert end $f
1824 getblobdiffs $ids
1827 proc gettreediffs {ids} {
1828 global treediff parents treepending
1829 set treepending $ids
1830 set treediff {}
1831 set id [lindex $ids 0]
1832 set p [lindex $ids 1]
1833 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
1834 fconfigure $gdtf -blocking 0
1835 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
1838 proc gettreediffline {gdtf ids} {
1839 global treediff treediffs treepending diffids diffmergeid
1841 set n [gets $gdtf line]
1842 if {$n < 0} {
1843 if {![eof $gdtf]} return
1844 close $gdtf
1845 set treediffs($ids) $treediff
1846 unset treepending
1847 if {$ids != $diffids} {
1848 gettreediffs $diffids
1849 } else {
1850 if {[info exists diffmergeid]} {
1851 contmergediff $ids
1852 } else {
1853 addtocflist $ids
1856 return
1858 set file [lindex $line 5]
1859 lappend treediff $file
1862 proc getblobdiffs {ids} {
1863 global diffopts blobdifffd diffids env curdifftag curtagstart
1864 global diffindex difffilestart nextupdate diffinhdr
1866 set id [lindex $ids 0]
1867 set p [lindex $ids 1]
1868 set env(GIT_DIFF_OPTS) $diffopts
1869 set cmd [list | git-diff-tree -r -p -C $p $id]
1870 if {[catch {set bdf [open $cmd r]} err]} {
1871 puts "error getting diffs: $err"
1872 return
1874 set diffinhdr 0
1875 fconfigure $bdf -blocking 0
1876 set blobdifffd($ids) $bdf
1877 set curdifftag Comments
1878 set curtagstart 0.0
1879 set diffindex 0
1880 catch {unset difffilestart}
1881 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
1882 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1885 proc getblobdiffline {bdf ids} {
1886 global diffids blobdifffd ctext curdifftag curtagstart
1887 global diffnexthead diffnextnote diffindex difffilestart
1888 global nextupdate diffinhdr
1889 global gaudydiff
1891 set n [gets $bdf line]
1892 if {$n < 0} {
1893 if {[eof $bdf]} {
1894 close $bdf
1895 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1896 $ctext tag add $curdifftag $curtagstart end
1899 return
1901 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1902 return
1904 $ctext conf -state normal
1905 if {[regexp {^diff --git a/(.*) b/} $line match fname]} {
1906 # start of a new file
1907 $ctext insert end "\n"
1908 $ctext tag add $curdifftag $curtagstart end
1909 set curtagstart [$ctext index "end - 1c"]
1910 set header $fname
1911 set here [$ctext index "end - 1c"]
1912 set difffilestart($diffindex) $here
1913 incr diffindex
1914 # start mark names at fmark.1 for first file
1915 $ctext mark set fmark.$diffindex $here
1916 $ctext mark gravity fmark.$diffindex left
1917 set curdifftag "f:$fname"
1918 $ctext tag delete $curdifftag
1919 set l [expr {(78 - [string length $header]) / 2}]
1920 set pad [string range "----------------------------------------" 1 $l]
1921 $ctext insert end "$pad $header $pad\n" filesep
1922 set diffinhdr 1
1923 } elseif {[regexp {^(---|\+\+\+)} $line]} {
1924 set diffinhdr 0
1925 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1926 $line match f1l f1c f2l f2c rest]} {
1927 if {$gaudydiff} {
1928 $ctext insert end "\t" hunksep
1929 $ctext insert end " $f1l " d0 " $f2l " d1
1930 $ctext insert end " $rest \n" hunksep
1931 } else {
1932 $ctext insert end "$line\n" hunksep
1934 set diffinhdr 0
1935 } else {
1936 set x [string range $line 0 0]
1937 if {$x == "-" || $x == "+"} {
1938 set tag [expr {$x == "+"}]
1939 if {$gaudydiff} {
1940 set line [string range $line 1 end]
1942 $ctext insert end "$line\n" d$tag
1943 } elseif {$x == " "} {
1944 if {$gaudydiff} {
1945 set line [string range $line 1 end]
1947 $ctext insert end "$line\n"
1948 } elseif {$diffinhdr || $x == "\\"} {
1949 # e.g. "\ No newline at end of file"
1950 $ctext insert end "$line\n" filesep
1951 } else {
1952 # Something else we don't recognize
1953 if {$curdifftag != "Comments"} {
1954 $ctext insert end "\n"
1955 $ctext tag add $curdifftag $curtagstart end
1956 set curtagstart [$ctext index "end - 1c"]
1957 set curdifftag Comments
1959 $ctext insert end "$line\n" filesep
1962 $ctext conf -state disabled
1963 if {[clock clicks -milliseconds] >= $nextupdate} {
1964 incr nextupdate 100
1965 fileevent $bdf readable {}
1966 update
1967 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1971 proc nextfile {} {
1972 global difffilestart ctext
1973 set here [$ctext index @0,0]
1974 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1975 if {[$ctext compare $difffilestart($i) > $here]} {
1976 $ctext yview $difffilestart($i)
1977 break
1982 proc listboxsel {} {
1983 global ctext cflist currentid treediffs
1984 if {![info exists currentid]} return
1985 set sel [lsort [$cflist curselection]]
1986 if {$sel eq {}} return
1987 set first [lindex $sel 0]
1988 catch {$ctext yview fmark.$first}
1991 proc setcoords {} {
1992 global linespc charspc canvx0 canvy0 mainfont
1993 set linespc [font metrics $mainfont -linespace]
1994 set charspc [font measure $mainfont "m"]
1995 set canvy0 [expr 3 + 0.5 * $linespc]
1996 set canvx0 [expr 3 + 0.5 * $linespc]
1999 proc redisplay {} {
2000 global selectedline stopped redisplaying phase
2001 if {$stopped > 1} return
2002 if {$phase == "getcommits"} return
2003 set redisplaying 1
2004 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2005 set stopped 1
2006 } else {
2007 drawgraph
2011 proc incrfont {inc} {
2012 global mainfont namefont textfont selectedline ctext canv phase
2013 global stopped entries
2014 unmarkmatches
2015 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2016 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2017 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2018 setcoords
2019 $ctext conf -font $textfont
2020 $ctext tag conf filesep -font [concat $textfont bold]
2021 foreach e $entries {
2022 $e conf -font $mainfont
2024 if {$phase == "getcommits"} {
2025 $canv itemconf textitems -font $mainfont
2027 redisplay
2030 proc clearsha1 {} {
2031 global sha1entry sha1string
2032 if {[string length $sha1string] == 40} {
2033 $sha1entry delete 0 end
2037 proc sha1change {n1 n2 op} {
2038 global sha1string currentid sha1but
2039 if {$sha1string == {}
2040 || ([info exists currentid] && $sha1string == $currentid)} {
2041 set state disabled
2042 } else {
2043 set state normal
2045 if {[$sha1but cget -state] == $state} return
2046 if {$state == "normal"} {
2047 $sha1but conf -state normal -relief raised -text "Goto: "
2048 } else {
2049 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2053 proc gotocommit {} {
2054 global sha1string currentid idline tagids
2055 global lineid numcommits
2057 if {$sha1string == {}
2058 || ([info exists currentid] && $sha1string == $currentid)} return
2059 if {[info exists tagids($sha1string)]} {
2060 set id $tagids($sha1string)
2061 } else {
2062 set id [string tolower $sha1string]
2063 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2064 set matches {}
2065 for {set l 0} {$l < $numcommits} {incr l} {
2066 if {[string match $id* $lineid($l)]} {
2067 lappend matches $lineid($l)
2070 if {$matches ne {}} {
2071 if {[llength $matches] > 1} {
2072 error_popup "Short SHA1 id $id is ambiguous"
2073 return
2075 set id [lindex $matches 0]
2079 if {[info exists idline($id)]} {
2080 selectline $idline($id)
2081 return
2083 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2084 set type "SHA1 id"
2085 } else {
2086 set type "Tag"
2088 error_popup "$type $sha1string is not known"
2091 proc lineenter {x y id} {
2092 global hoverx hovery hoverid hovertimer
2093 global commitinfo canv
2095 if {![info exists commitinfo($id)]} return
2096 set hoverx $x
2097 set hovery $y
2098 set hoverid $id
2099 if {[info exists hovertimer]} {
2100 after cancel $hovertimer
2102 set hovertimer [after 500 linehover]
2103 $canv delete hover
2106 proc linemotion {x y id} {
2107 global hoverx hovery hoverid hovertimer
2109 if {[info exists hoverid] && $id == $hoverid} {
2110 set hoverx $x
2111 set hovery $y
2112 if {[info exists hovertimer]} {
2113 after cancel $hovertimer
2115 set hovertimer [after 500 linehover]
2119 proc lineleave {id} {
2120 global hoverid hovertimer canv
2122 if {[info exists hoverid] && $id == $hoverid} {
2123 $canv delete hover
2124 if {[info exists hovertimer]} {
2125 after cancel $hovertimer
2126 unset hovertimer
2128 unset hoverid
2132 proc linehover {} {
2133 global hoverx hovery hoverid hovertimer
2134 global canv linespc lthickness
2135 global commitinfo mainfont
2137 set text [lindex $commitinfo($hoverid) 0]
2138 set ymax [lindex [$canv cget -scrollregion] 3]
2139 if {$ymax == {}} return
2140 set yfrac [lindex [$canv yview] 0]
2141 set x [expr {$hoverx + 2 * $linespc}]
2142 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2143 set x0 [expr {$x - 2 * $lthickness}]
2144 set y0 [expr {$y - 2 * $lthickness}]
2145 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2146 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2147 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2148 -fill \#ffff80 -outline black -width 1 -tags hover]
2149 $canv raise $t
2150 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2151 $canv raise $t
2154 proc lineclick {x y id} {
2155 global ctext commitinfo children cflist canv
2157 unmarkmatches
2158 $canv delete hover
2159 # fill the details pane with info about this line
2160 $ctext conf -state normal
2161 $ctext delete 0.0 end
2162 $ctext insert end "Parent:\n "
2163 catch {destroy $ctext.$id}
2164 button $ctext.$id -text "Go:" -command "selbyid $id" \
2165 -padx 4 -pady 0
2166 $ctext window create end -window $ctext.$id -align center
2167 set info $commitinfo($id)
2168 $ctext insert end "\t[lindex $info 0]\n"
2169 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2170 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2171 $ctext insert end "\tID:\t$id\n"
2172 if {[info exists children($id)]} {
2173 $ctext insert end "\nChildren:"
2174 foreach child $children($id) {
2175 $ctext insert end "\n "
2176 catch {destroy $ctext.$child}
2177 button $ctext.$child -text "Go:" -command "selbyid $child" \
2178 -padx 4 -pady 0
2179 $ctext window create end -window $ctext.$child -align center
2180 set info $commitinfo($child)
2181 $ctext insert end "\t[lindex $info 0]"
2184 $ctext conf -state disabled
2186 $cflist delete 0 end
2189 proc selbyid {id} {
2190 global idline
2191 if {[info exists idline($id)]} {
2192 selectline $idline($id)
2196 proc mstime {} {
2197 global startmstime
2198 if {![info exists startmstime]} {
2199 set startmstime [clock clicks -milliseconds]
2201 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2204 proc rowmenu {x y id} {
2205 global rowctxmenu idline selectedline rowmenuid
2207 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2208 set state disabled
2209 } else {
2210 set state normal
2212 $rowctxmenu entryconfigure 0 -state $state
2213 $rowctxmenu entryconfigure 1 -state $state
2214 $rowctxmenu entryconfigure 2 -state $state
2215 set rowmenuid $id
2216 tk_popup $rowctxmenu $x $y
2219 proc diffvssel {dirn} {
2220 global rowmenuid selectedline lineid
2221 global ctext cflist
2222 global commitinfo
2224 if {![info exists selectedline]} return
2225 if {$dirn} {
2226 set oldid $lineid($selectedline)
2227 set newid $rowmenuid
2228 } else {
2229 set oldid $rowmenuid
2230 set newid $lineid($selectedline)
2232 $ctext conf -state normal
2233 $ctext delete 0.0 end
2234 $ctext mark set fmark.0 0.0
2235 $ctext mark gravity fmark.0 left
2236 $cflist delete 0 end
2237 $cflist insert end "Top"
2238 $ctext insert end "From $oldid\n "
2239 $ctext insert end [lindex $commitinfo($oldid) 0]
2240 $ctext insert end "\n\nTo $newid\n "
2241 $ctext insert end [lindex $commitinfo($newid) 0]
2242 $ctext insert end "\n"
2243 $ctext conf -state disabled
2244 $ctext tag delete Comments
2245 $ctext tag remove found 1.0 end
2246 startdiff $newid [list $oldid]
2249 proc mkpatch {} {
2250 global rowmenuid currentid commitinfo patchtop patchnum
2252 if {![info exists currentid]} return
2253 set oldid $currentid
2254 set oldhead [lindex $commitinfo($oldid) 0]
2255 set newid $rowmenuid
2256 set newhead [lindex $commitinfo($newid) 0]
2257 set top .patch
2258 set patchtop $top
2259 catch {destroy $top}
2260 toplevel $top
2261 label $top.title -text "Generate patch"
2262 grid $top.title - -pady 10
2263 label $top.from -text "From:"
2264 entry $top.fromsha1 -width 40 -relief flat
2265 $top.fromsha1 insert 0 $oldid
2266 $top.fromsha1 conf -state readonly
2267 grid $top.from $top.fromsha1 -sticky w
2268 entry $top.fromhead -width 60 -relief flat
2269 $top.fromhead insert 0 $oldhead
2270 $top.fromhead conf -state readonly
2271 grid x $top.fromhead -sticky w
2272 label $top.to -text "To:"
2273 entry $top.tosha1 -width 40 -relief flat
2274 $top.tosha1 insert 0 $newid
2275 $top.tosha1 conf -state readonly
2276 grid $top.to $top.tosha1 -sticky w
2277 entry $top.tohead -width 60 -relief flat
2278 $top.tohead insert 0 $newhead
2279 $top.tohead conf -state readonly
2280 grid x $top.tohead -sticky w
2281 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2282 grid $top.rev x -pady 10
2283 label $top.flab -text "Output file:"
2284 entry $top.fname -width 60
2285 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2286 incr patchnum
2287 grid $top.flab $top.fname -sticky w
2288 frame $top.buts
2289 button $top.buts.gen -text "Generate" -command mkpatchgo
2290 button $top.buts.can -text "Cancel" -command mkpatchcan
2291 grid $top.buts.gen $top.buts.can
2292 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2293 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2294 grid $top.buts - -pady 10 -sticky ew
2295 focus $top.fname
2298 proc mkpatchrev {} {
2299 global patchtop
2301 set oldid [$patchtop.fromsha1 get]
2302 set oldhead [$patchtop.fromhead get]
2303 set newid [$patchtop.tosha1 get]
2304 set newhead [$patchtop.tohead get]
2305 foreach e [list fromsha1 fromhead tosha1 tohead] \
2306 v [list $newid $newhead $oldid $oldhead] {
2307 $patchtop.$e conf -state normal
2308 $patchtop.$e delete 0 end
2309 $patchtop.$e insert 0 $v
2310 $patchtop.$e conf -state readonly
2314 proc mkpatchgo {} {
2315 global patchtop
2317 set oldid [$patchtop.fromsha1 get]
2318 set newid [$patchtop.tosha1 get]
2319 set fname [$patchtop.fname get]
2320 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2321 error_popup "Error creating patch: $err"
2323 catch {destroy $patchtop}
2324 unset patchtop
2327 proc mkpatchcan {} {
2328 global patchtop
2330 catch {destroy $patchtop}
2331 unset patchtop
2334 proc mktag {} {
2335 global rowmenuid mktagtop commitinfo
2337 set top .maketag
2338 set mktagtop $top
2339 catch {destroy $top}
2340 toplevel $top
2341 label $top.title -text "Create tag"
2342 grid $top.title - -pady 10
2343 label $top.id -text "ID:"
2344 entry $top.sha1 -width 40 -relief flat
2345 $top.sha1 insert 0 $rowmenuid
2346 $top.sha1 conf -state readonly
2347 grid $top.id $top.sha1 -sticky w
2348 entry $top.head -width 60 -relief flat
2349 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2350 $top.head conf -state readonly
2351 grid x $top.head -sticky w
2352 label $top.tlab -text "Tag name:"
2353 entry $top.tag -width 60
2354 grid $top.tlab $top.tag -sticky w
2355 frame $top.buts
2356 button $top.buts.gen -text "Create" -command mktaggo
2357 button $top.buts.can -text "Cancel" -command mktagcan
2358 grid $top.buts.gen $top.buts.can
2359 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2360 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2361 grid $top.buts - -pady 10 -sticky ew
2362 focus $top.tag
2365 proc domktag {} {
2366 global mktagtop env tagids idtags
2367 global idpos idline linehtag canv selectedline
2369 set id [$mktagtop.sha1 get]
2370 set tag [$mktagtop.tag get]
2371 if {$tag == {}} {
2372 error_popup "No tag name specified"
2373 return
2375 if {[info exists tagids($tag)]} {
2376 error_popup "Tag \"$tag\" already exists"
2377 return
2379 if {[catch {
2380 set dir ".git"
2381 if {[info exists env(GIT_DIR)]} {
2382 set dir $env(GIT_DIR)
2384 set fname [file join $dir "refs/tags" $tag]
2385 set f [open $fname w]
2386 puts $f $id
2387 close $f
2388 } err]} {
2389 error_popup "Error creating tag: $err"
2390 return
2393 set tagids($tag) $id
2394 lappend idtags($id) $tag
2395 $canv delete tag.$id
2396 set xt [eval drawtags $id $idpos($id)]
2397 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2398 if {[info exists selectedline] && $selectedline == $idline($id)} {
2399 selectline $selectedline
2403 proc mktagcan {} {
2404 global mktagtop
2406 catch {destroy $mktagtop}
2407 unset mktagtop
2410 proc mktaggo {} {
2411 domktag
2412 mktagcan
2415 proc writecommit {} {
2416 global rowmenuid wrcomtop commitinfo wrcomcmd
2418 set top .writecommit
2419 set wrcomtop $top
2420 catch {destroy $top}
2421 toplevel $top
2422 label $top.title -text "Write commit to file"
2423 grid $top.title - -pady 10
2424 label $top.id -text "ID:"
2425 entry $top.sha1 -width 40 -relief flat
2426 $top.sha1 insert 0 $rowmenuid
2427 $top.sha1 conf -state readonly
2428 grid $top.id $top.sha1 -sticky w
2429 entry $top.head -width 60 -relief flat
2430 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2431 $top.head conf -state readonly
2432 grid x $top.head -sticky w
2433 label $top.clab -text "Command:"
2434 entry $top.cmd -width 60 -textvariable wrcomcmd
2435 grid $top.clab $top.cmd -sticky w -pady 10
2436 label $top.flab -text "Output file:"
2437 entry $top.fname -width 60
2438 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2439 grid $top.flab $top.fname -sticky w
2440 frame $top.buts
2441 button $top.buts.gen -text "Write" -command wrcomgo
2442 button $top.buts.can -text "Cancel" -command wrcomcan
2443 grid $top.buts.gen $top.buts.can
2444 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2445 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2446 grid $top.buts - -pady 10 -sticky ew
2447 focus $top.fname
2450 proc wrcomgo {} {
2451 global wrcomtop
2453 set id [$wrcomtop.sha1 get]
2454 set cmd "echo $id | [$wrcomtop.cmd get]"
2455 set fname [$wrcomtop.fname get]
2456 if {[catch {exec sh -c $cmd >$fname &} err]} {
2457 error_popup "Error writing commit: $err"
2459 catch {destroy $wrcomtop}
2460 unset wrcomtop
2463 proc wrcomcan {} {
2464 global wrcomtop
2466 catch {destroy $wrcomtop}
2467 unset wrcomtop
2470 proc doquit {} {
2471 global stopped
2472 set stopped 100
2473 destroy .
2476 # defaults...
2477 set datemode 0
2478 set boldnames 0
2479 set diffopts "-U 5 -p"
2480 set wrcomcmd "git-diff-tree --stdin -p --pretty"
2482 set mainfont {Helvetica 9}
2483 set textfont {Courier 9}
2484 set findmergefiles 0
2485 set gaudydiff 0
2487 set colors {green red blue magenta darkgrey brown orange}
2489 catch {source ~/.gitk}
2491 set namefont $mainfont
2492 if {$boldnames} {
2493 lappend namefont bold
2496 set revtreeargs {}
2497 foreach arg $argv {
2498 switch -regexp -- $arg {
2499 "^$" { }
2500 "^-b" { set boldnames 1 }
2501 "^-d" { set datemode 1 }
2502 default {
2503 lappend revtreeargs $arg
2508 set stopped 0
2509 set redisplaying 0
2510 set stuffsaved 0
2511 set patchnum 0
2512 setcoords
2513 makewindow
2514 readrefs
2515 getcommits $revtreeargs