1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "${1+$@}"
4
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.
9
10proc getcommits {rargs} {
11 global commits commfd phase canv mainfont env
12 global startmsecs nextupdate
13 global ctext maincursor textcursor leftover
14
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"
20 }
21 if {![file isdirectory $gitdir]} {
22 error_popup "Cannot find the git directory \"$gitdir\"."
23 exit 1
24 }
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
36 }
37 set parsed_args $rargs
38 }
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
44 }
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
53}
54
55proc getcommitlines {commfd} {
56 global commits parents cdate children nchildren
57 global commitlisted phase commitinfo nextupdate
58 global stopped redisplaying leftover
59
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
68 }
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
73to allow selection of commits to be displayed.)}
74 } else {
75 set err "Error reading commits: $err"
76 }
77 error_popup $err
78 exit 1
79 }
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
86 }
87 set cmit [string range $stuff $start [expr {$i - 1}]]
88 if {$start == 0} {
89 set cmit "$leftover$cmit"
90 set leftover {}
91 }
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]..."
97 }
98 error_popup "Can't parse git-rev-list output: {$shortcmit}"
99 exit 1
100 }
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
108 }
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
119 }
120 }
121 }
122 }
123 }
124}
125
126proc doupdate {} {
127 global commfd nextupdate
128
129 incr nextupdate 100
130 fileevent $commfd readable {}
131 update
132 fileevent $commfd readable "getcommitlines $commfd"
133}
134
135proc readcommit {id} {
136 if [catch {set contents [exec git-cat-file commit $id]}] return
137 parsecommit $id $contents 0
138}
139
140proc parsecommit {id contents listed} {
141 global commitinfo children nchildren parents nparents cdate ncleft
142
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
154 }
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
169 }
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)
177 }
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}]]
186 }
187 }
188 } else {
189 if {$comment == {}} {
190 set headline [string trim $line]
191 } else {
192 append comment "\n"
193 }
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 " "
198 }
199 append comment $line
200 }
201 }
202 if {$audate != {}} {
203 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
204 }
205 if {$comdate != {}} {
206 set cdate($id) $comdate
207 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
208 }
209 set commitinfo($id) [list $headline $auname $audate \
210 $comname $comdate $comment]
211}
212
213proc 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]}
234 }
235 }
236 if {$obj != {} && $type == "commit" && $tag != {}} {
237 set tagids($tag) $obj
238 lappend idtags($obj) $tag
239 }
240 }
241 close $fd
242 }
243 }
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
253 }
254 close $fd
255 }
256 }
257}
258
259proc 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
269}
270
271proc 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
277
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
286
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
295 }
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]}]
302 }
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}
326
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
354
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
366
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
377 }
378
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}
388
389 pack .ctop -side top -fill both -expand 1
390
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
427
428 set maincursor [. cget -cursor]
429 set textcursor [$ctext cget -cursor]
430
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
440}
441
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.
445proc 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>]
451 }
452 foreach e $entries {
453 bind $e $ev "$escript; break"
454 }
455}
456
457# set the focus back to the toplevel for any click outside
458# the entry widgets
459proc click {w} {
460 global entries
461 foreach e $entries {
462 if {$w == $e} return
463 }
464 focus .
465}
466
467proc 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"
492 }
493 set stuffsaved 1
494}
495
496proc 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
510 }
511 if {$sash1 < $sash0 + 20} {
512 set sash1 [expr $sash0 + 20]
513 }
514 if {$sash1 > $w - 10} {
515 set sash1 [expr $w - 10]
516 if {$sash0 > $sash1 - 20} {
517 set sash0 [expr $sash1 - 20]
518 }
519 }
520 }
521 $win sash place 0 $sash0 [lindex $s0 1]
522 $win sash place 1 $sash1 [lindex $s1 1]
523 }
524 set oldwidth($win) $w
525}
526
527proc 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
538 }
539 if {$sash0 > $w - 15} {
540 set sash0 [expr $w - 15]
541 }
542 }
543 $win sash place 0 $sash0 [lindex $s0 1]
544 }
545 set oldwidth($win) $w
546}
547
548proc allcanvs args {
549 global canv canv2 canv3
550 eval $canv $args
551 eval $canv2 $args
552 eval $canv3 $args
553}
554
555proc bindall {event action} {
556 global canv canv2 canv3
557 bind $canv $event $action
558 bind $canv2 $event $action
559 bind $canv3 $event $action
560}
561
562proc about {} {
563 set w .about
564 if {[winfo exists $w]} {
565 raise $w
566 return
567 }
568 toplevel $w
569 wm title $w "About gitk"
570 message $w.m -text {
571Gitk version 1.2
572
573Copyright © 2005 Paul Mackerras
574
575Use 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
580}
581
582proc assigncolor {id} {
583 global commitinfo colormap commcolors colors nextcolor
584 global parents nparents children nchildren
585 global cornercrossings crossings
586
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
595 }
596 }
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)
603 }
604 }
605 if {[llength $badcolors] >= $ncolors} {
606 set badcolors {}
607 }
608 }
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)
616 }
617 }
618 if {[llength $badcolors] >= $ncolors} {
619 set badcolors $origbad
620 }
621 }
622 set origbad $badcolors
623 }
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)
629 }
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)
635 }
636 }
637 }
638 }
639 if {[llength $badcolors] >= $ncolors} {
640 set badcolors $origbad
641 }
642 }
643 for {set i 0} {$i <= $ncolors} {incr i} {
644 set c [lindex $colors $nextcolor]
645 if {[incr nextcolor] >= $ncolors} {
646 set nextcolor 0
647 }
648 if {[lsearch -exact $badcolors $c]} break
649 }
650 set colormap($id) $c
651}
652
653proc initgraph {} {
654 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
655 global mainline sidelines
656 global nchildren ncleft
657
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)
668 }
669}
670
671proc bindline {t id} {
672 global canv
673
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"
678}
679
680proc 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
689
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
701 }
702 }
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
713 }
714 }
715 }
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
727 }
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
736 }
737 }
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}]
747 }
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]
752 }
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]
763}
764
765proc drawtags {id x xt y1} {
766 global idtags idheads
767 global linespc lthickness
768 global canv mainfont
769
770 set marks {}
771 set ntags 0
772 if {[info exists idtags($id)]} {
773 set marks $idtags($id)
774 set ntags [llength $marks]
775 }
776 if {[info exists idheads($id)]} {
777 set marks [concat $marks $idheads($id)]
778 }
779 if {$marks eq {}} {
780 return $xt
781 }
782
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}]
793 }
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
810 }
811 $canv create text $xl $y1 -anchor w -text $tag \
812 -font $mainfont -tags tag.$id
813 }
814 return $xt
815}
816
817proc updatetodo {level noshortcut} {
818 global currentparents ncleft todo
819 global mainline oldlevel oldtodo oldnlines
820 global canvx0 canvy linespc mainline
821 global commitinfo
822
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
835 }
836 }
837
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
846 }
847 }
848 return 1
849}
850
851proc notecrossings {id lo hi corner} {
852 global oldtodo crossings cornercrossings
853
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
861 }
862 if {![info exists cornercrossings($p)]
863 || [lsearch -exact $cornercrossings($p) $id] < 0} {
864 lappend cornercrossings($p) $id
865 }
866 } else {
867 if {![info exists crossings($id)]
868 || [lsearch -exact $crossings($id) $p] < 0} {
869 lappend crossings($id) $p
870 }
871 if {![info exists crossings($p)]
872 || [lsearch -exact $crossings($p) $id] < 0} {
873 lappend crossings($p) $id
874 }
875 }
876 }
877}
878
879proc drawslants {} {
880 global canv mainline sidelines canvx0 canvy linespc
881 global oldlevel oldtodo todo currentparents dupparents
882 global lthickness linespc canvy colormap
883
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}]
902 }
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]
909 }
910 } else {
911 # normal case, no parent duplicated
912 if {![info exists mainline($p)]} {
913 if {$i != $j} {
914 lappend coords $xj $y2
915 }
916 set mainline($p) $coords
917 } else {
918 lappend coords $xj $y2
919 lappend sidelines($p) [list $coords 1]
920 }
921 }
922 }
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
927 }
928 }
929}
930
931proc 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
938
939 # remove the null entry if present
940 set nullentry [lsearch -exact $todo {}]
941 if {$nullentry >= 0} {
942 set todo [lreplace $todo $nullentry $nullentry]
943 }
944
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 {}
956 }
957 readcommit $p
958 }
959 if {$latest == {} || $cdate($p) > $latest} {
960 set level $k
961 set latest $cdate($p)
962 }
963 } else {
964 set level $k
965 break
966 }
967 }
968 }
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))"
974 }
975 }
976 return -1
977 }
978
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
986 }
987 } else {
988 set i $oldlevel
989 if {$level >= $i} {
990 incr i
991 }
992 }
993 if {$i < $todol} {
994 set todo [linsert $todo $i {}]
995 if {$level >= $i} {
996 incr level
997 }
998 }
999 }
1000 return $level
1001}
1002
1003proc drawcommit {id} {
1004 global phase todo nchildren datemode nextupdate
1005 global startcommits
1006
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
1018 }
1019 set level [decidenext 1]
1020 if {$level == {} || $id != [lindex $todo $level]} {
1021 return
1022 }
1023 while 1 {
1024 drawslants
1025 drawcommitline $level
1026 if {[updatetodo $level $datemode]} {
1027 set level [decidenext 1]
1028 if {$level == {}} break
1029 }
1030 set id [lindex $todo $level]
1031 if {![info exists commitlisted($id)]} {
1032 break
1033 }
1034 if {[clock clicks -milliseconds] >= $nextupdate} {
1035 doupdate
1036 if {$stopped} break
1037 }
1038 }
1039 }
1040}
1041
1042proc finishcommits {} {
1043 global phase
1044 global startcommits
1045 global canv mainfont ctext maincursor textcursor
1046
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]
1056 }
1057 . config -cursor $maincursor
1058 $ctext config -cursor $textcursor
1059}
1060
1061proc drawgraph {} {
1062 global nextupdate startmsecs startcommits todo
1063
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
1070}
1071
1072proc drawrest {level startix} {
1073 global phase stopped redisplaying selectedline
1074 global datemode currentparents todo
1075 global numcommits
1076 global nextupdate startmsecs startcommits idline
1077
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)
1084 }
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)
1097 }
1098 }
1099 if {$hard} {
1100 set level [decidenext]
1101 if {$level < 0} break
1102 drawslants
1103 }
1104 if {[clock clicks -milliseconds] >= $nextupdate} {
1105 update
1106 incr nextupdate 100
1107 }
1108 }
1109 }
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
1116 }
1117 if {$stopped == 1} {
1118 set stopped 0
1119 after idle drawgraph
1120 } else {
1121 set redisplaying 0
1122 }
1123 }
1124}
1125
1126proc 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
1135 }
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]
1141 }
1142 }
1143 return $matches
1144}
1145
1146proc 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
1151
1152 stopfindproc
1153 unmarkmatches
1154 focus .
1155 set matchinglines {}
1156 if {$findloc == "Pickaxe"} {
1157 findpatches
1158 return
1159 }
1160 if {$findtype == "IgnCase"} {
1161 set foundstring [string tolower $findstring]
1162 } else {
1163 set foundstring $findstring
1164 }
1165 set foundstrlen [string length $findstring]
1166 if {$foundstrlen == 0} return
1167 if {$findloc == "Files"} {
1168 findfiles
1169 return
1170 }
1171 if {![info exists selectedline]} {
1172 set oldsel -1
1173 } else {
1174 set oldsel $selectedline
1175 }
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
1185 }
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
1195 }
1196 }
1197 if {$doesmatch} {
1198 lappend matchinglines $l
1199 if {!$didsel && $l > $oldsel} {
1200 findselectline $l
1201 set didsel 1
1202 }
1203 }
1204 }
1205 if {$matchinglines == {}} {
1206 bell
1207 } elseif {!$didsel} {
1208 findselectline [lindex $matchinglines 0]
1209 }
1210}
1211
1212proc 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"
1223 }
1224 }
1225}
1226
1227proc findnext {restart} {
1228 global matchinglines selectedline
1229 if {![info exists matchinglines]} {
1230 if {$restart} {
1231 dofind
1232 }
1233 return
1234 }
1235 if {![info exists selectedline]} return
1236 foreach l $matchinglines {
1237 if {$l > $selectedline} {
1238 findselectline $l
1239 return
1240 }
1241 }
1242 bell
1243}
1244
1245proc findprev {} {
1246 global matchinglines selectedline
1247 if {![info exists matchinglines]} {
1248 dofind
1249 return
1250 }
1251 if {![info exists selectedline]} return
1252 set prev {}
1253 foreach l $matchinglines {
1254 if {$l >= $selectedline} break
1255 set prev $l
1256 }
1257 if {$prev != {}} {
1258 findselectline $prev
1259 } else {
1260 bell
1261 }
1262}
1263
1264proc 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
1271 }
1272 $findtypemenu entryconf 1 -state $state
1273 $findtypemenu entryconf 2 -state $state
1274}
1275
1276proc stopfindproc {{done 0}} {
1277 global findprocpid findprocfile findids
1278 global ctext findoldcursor phase maincursor textcursor
1279 global findinprogress
1280
1281 catch {unset findids}
1282 if {[info exists findprocpid]} {
1283 if {!$done} {
1284 catch {exec kill $findprocpid}
1285 }
1286 catch {close $findprocfile}
1287 unset findprocpid
1288 }
1289 if {[info exists findinprogress]} {
1290 unset findinprogress
1291 if {$phase != "incrdraw"} {
1292 . config -cursor $maincursor
1293 $ctext config -cursor $textcursor
1294 }
1295 }
1296}
1297
1298proc findpatches {} {
1299 global findstring selectedline numcommits
1300 global findprocpid findprocfile
1301 global finddidsel ctext lineid findinprogress
1302 global findinsertpos
1303
1304 if {$numcommits == 0} return
1305
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
1312 }
1313 set inputids {}
1314 for {set i 0} {$i < $numcommits} {incr i} {
1315 if {[incr l] >= $numcommits} {
1316 set l 0
1317 }
1318 append inputids $lineid($l) "\n"
1319 }
1320
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
1327 }
1328
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
1338}
1339
1340proc readfindproc {} {
1341 global findprocfile finddidsel
1342 global idline matchinglines findinsertpos
1343
1344 set n [gets $findprocfile line]
1345 if {$n < 0} {
1346 if {[eof $findprocfile]} {
1347 stopfindproc 1
1348 if {!$finddidsel} {
1349 bell
1350 }
1351 }
1352 return
1353 }
1354 if {![regexp {^[0-9a-f]{40}} $line id]} {
1355 error_popup "Can't parse git-diff-tree output: $line"
1356 stopfindproc
1357 return
1358 }
1359 if {![info exists idline($id)]} {
1360 puts stderr "spurious id: $id"
1361 return
1362 }
1363 set l $idline($id)
1364 insertmatch $l $id
1365}
1366
1367proc insertmatch {l id} {
1368 global matchinglines findinsertpos finddidsel
1369
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
1376 }
1377 } else {
1378 set matchinglines [linsert $matchinglines $findinsertpos $l]
1379 incr findinsertpos
1380 }
1381 markheadline $l $id
1382 if {!$finddidsel} {
1383 findselectline $l
1384 set finddidsel 1
1385 }
1386}
1387
1388proc 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
1394
1395 if {$numcommits == 0} return
1396
1397 if {[info exists selectedline]} {
1398 set l [expr {$selectedline + 1}]
1399 } else {
1400 set l 0
1401 }
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]
1413 }
1414 }
1415 }
1416 if {[incr l] >= $numcommits} {
1417 set l 0
1418 }
1419 if {$l == $findstartline} break
1420 }
1421
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
1429 }
1430 catch {unset fdiffids}
1431 set fdiffpos 0
1432 fconfigure $df -blocking 0
1433 fileevent $df readable [list readfilediffs $df]
1434 }
1435
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
1445}
1446
1447proc readfilediffs {df} {
1448 global findids fdiffids fdiffs
1449
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}"
1463 }
1464 }
1465 return
1466 }
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]
1474 }
1475}
1476
1477proc donefilediff {} {
1478 global fdiffids fdiffs treediffs findids
1479 global fdiffsneeded fdiffpos
1480
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
1491 }
1492 incr fdiffpos
1493 }
1494 incr fdiffpos
1495
1496 if {![info exists treediffs($fdiffids)]} {
1497 set treediffs($fdiffids) $fdiffs
1498 }
1499 if {[info exists findids] && $fdiffids eq $findids} {
1500 unset findids
1501 findcont $fdiffids
1502 }
1503 }
1504}
1505
1506proc findcont {ids} {
1507 global findids treediffs parents nparents
1508 global ffileline findstartline finddidsel
1509 global lineid numcommits matchinglines findinprogress
1510 global findmergefiles
1511
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
1522 }
1523 set doesmatch 0
1524 foreach f $treediffs($ids) {
1525 set x [findmatches $f]
1526 if {$x != {}} {
1527 set doesmatch 1
1528 break
1529 }
1530 }
1531 if {$doesmatch} {
1532 insertmatch $l $id
1533 set pi $nparents($id)
1534 }
1535 } else {
1536 set pi $nparents($id)
1537 }
1538 if {[incr pi] >= $nparents($id)} {
1539 set pi 0
1540 if {[incr l] >= $numcommits} {
1541 set l 0
1542 }
1543 if {$l == $findstartline} break
1544 set id $lineid($l)
1545 }
1546 set p [lindex $parents($id) $pi]
1547 set ids [list $id $p]
1548 }
1549 stopfindproc
1550 if {!$finddidsel} {
1551 bell
1552 }
1553}
1554
1555# mark a commit as matching by putting a yellow background
1556# behind the headline
1557proc markheadline {l id} {
1558 global canv mainfont linehtag commitinfo
1559
1560 set bbox [$canv bbox $linehtag($l)]
1561 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1562 $canv lower $t
1563}
1564
1565# mark the bits of a headline, author or date that match a find string
1566proc 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
1580 }
1581}
1582
1583proc unmarkmatches {} {
1584 global matchinglines findids
1585 allcanvs delete matches
1586 catch {unset matchinglines}
1587 catch {unset findids}
1588}
1589
1590proc 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
1600 }
1601 if {$w eq $canv} {
1602 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1603 }
1604 unmarkmatches
1605 selectline $l
1606}
1607
1608proc 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}]
1644 }
1645 }
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}]
1653 }
1654 }
1655 }
1656 if {$newtop != $wtop} {
1657 if {$newtop < 0} {
1658 set newtop 0
1659 }
1660 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1661 }
1662 set selectedline $l
1663
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
1670
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"
1682 }
1683 $ctext insert end "\n"
1684 }
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"]
1692
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
1699 }
1700}
1701
1702proc selnextline {dir} {
1703 global selectedline
1704 if {![info exists selectedline]} return
1705 set l [expr $selectedline + $dir]
1706 unmarkmatches
1707 selectline $l
1708}
1709
1710proc mergediff {id} {
1711 global parents diffmergeid diffmergegca mergefilelist diffpindex
1712
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 {}
1720 }
1721}
1722
1723proc 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 {}
1733 }
1734 }
1735 }
1736 return $gca
1737}
1738
1739proc contmergediff {ids} {
1740 global diffmergeid diffpindex parents nparents diffmergegca
1741 global treediffs mergefilelist diffids
1742
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]
1752 }
1753 if {![info exists treediffs($ids)]} {
1754 set diffids $ids
1755 gettreediffs $ids
1756 return
1757 }
1758 }
1759
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
1772 }
1773 }
1774 }
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
1786 }
1787 }
1788 set files $nf
1789 }
1790 }
1791
1792 set mergefilelist($diffmergeid) $files
1793 showmergediff
1794}
1795
1796proc showmergediff {} {
1797 global cflist diffmergeid mergefilelist
1798
1799 set files $mergefilelist($diffmergeid)
1800 foreach f $files {
1801 $cflist insert end $f
1802 }
1803}
1804
1805proc startdiff {ids} {
1806 global treediffs diffids treepending diffmergeid
1807
1808 set diffids $ids
1809 catch {unset diffmergeid}
1810 if {![info exists treediffs($ids)]} {
1811 if {![info exists treepending]} {
1812 gettreediffs $ids
1813 }
1814 } else {
1815 addtocflist $ids
1816 }
1817}
1818
1819proc addtocflist {ids} {
1820 global treediffs cflist
1821 foreach f $treediffs($ids) {
1822 $cflist insert end $f
1823 }
1824 getblobdiffs $ids
1825}
1826
1827proc 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]
1836}
1837
1838proc gettreediffline {gdtf ids} {
1839 global treediff treediffs treepending diffids diffmergeid
1840
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
1854 }
1855 }
1856 return
1857 }
1858 set file [lindex $line 5]
1859 lappend treediff $file
1860}
1861
1862proc getblobdiffs {ids} {
1863 global diffopts blobdifffd diffids env curdifftag curtagstart
1864 global difffilestart nextupdate diffinhdr treediffs
1865
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
1873 }
1874 set diffinhdr 0
1875 fconfigure $bdf -blocking 0
1876 set blobdifffd($ids) $bdf
1877 set curdifftag Comments
1878 set curtagstart 0.0
1879 catch {unset difffilestart}
1880 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
1881 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
1882}
1883
1884proc getblobdiffline {bdf ids} {
1885 global diffids blobdifffd ctext curdifftag curtagstart
1886 global diffnexthead diffnextnote difffilestart
1887 global nextupdate diffinhdr treediffs
1888 global gaudydiff
1889
1890 set n [gets $bdf line]
1891 if {$n < 0} {
1892 if {[eof $bdf]} {
1893 close $bdf
1894 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
1895 $ctext tag add $curdifftag $curtagstart end
1896 }
1897 }
1898 return
1899 }
1900 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
1901 return
1902 }
1903 $ctext conf -state normal
1904 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
1905 # start of a new file
1906 $ctext insert end "\n"
1907 $ctext tag add $curdifftag $curtagstart end
1908 set curtagstart [$ctext index "end - 1c"]
1909 set header $newname
1910 set here [$ctext index "end - 1c"]
1911 set i [lsearch -exact $treediffs($diffids) $fname]
1912 if {$i >= 0} {
1913 set difffilestart($i) $here
1914 incr i
1915 $ctext mark set fmark.$i $here
1916 $ctext mark gravity fmark.$i left
1917 }
1918 if {$newname != $fname} {
1919 set i [lsearch -exact $treediffs($diffids) $newname]
1920 if {$i >= 0} {
1921 set difffilestart($i) $here
1922 incr i
1923 $ctext mark set fmark.$i $here
1924 $ctext mark gravity fmark.$i left
1925 }
1926 }
1927 set curdifftag "f:$fname"
1928 $ctext tag delete $curdifftag
1929 set l [expr {(78 - [string length $header]) / 2}]
1930 set pad [string range "----------------------------------------" 1 $l]
1931 $ctext insert end "$pad $header $pad\n" filesep
1932 set diffinhdr 1
1933 } elseif {[regexp {^(---|\+\+\+)} $line]} {
1934 set diffinhdr 0
1935 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
1936 $line match f1l f1c f2l f2c rest]} {
1937 if {$gaudydiff} {
1938 $ctext insert end "\t" hunksep
1939 $ctext insert end " $f1l " d0 " $f2l " d1
1940 $ctext insert end " $rest \n" hunksep
1941 } else {
1942 $ctext insert end "$line\n" hunksep
1943 }
1944 set diffinhdr 0
1945 } else {
1946 set x [string range $line 0 0]
1947 if {$x == "-" || $x == "+"} {
1948 set tag [expr {$x == "+"}]
1949 if {$gaudydiff} {
1950 set line [string range $line 1 end]
1951 }
1952 $ctext insert end "$line\n" d$tag
1953 } elseif {$x == " "} {
1954 if {$gaudydiff} {
1955 set line [string range $line 1 end]
1956 }
1957 $ctext insert end "$line\n"
1958 } elseif {$diffinhdr || $x == "\\"} {
1959 # e.g. "\ No newline at end of file"
1960 $ctext insert end "$line\n" filesep
1961 } else {
1962 # Something else we don't recognize
1963 if {$curdifftag != "Comments"} {
1964 $ctext insert end "\n"
1965 $ctext tag add $curdifftag $curtagstart end
1966 set curtagstart [$ctext index "end - 1c"]
1967 set curdifftag Comments
1968 }
1969 $ctext insert end "$line\n" filesep
1970 }
1971 }
1972 $ctext conf -state disabled
1973 if {[clock clicks -milliseconds] >= $nextupdate} {
1974 incr nextupdate 100
1975 fileevent $bdf readable {}
1976 update
1977 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
1978 }
1979}
1980
1981proc nextfile {} {
1982 global difffilestart ctext
1983 set here [$ctext index @0,0]
1984 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
1985 if {[$ctext compare $difffilestart($i) > $here]} {
1986 if {![info exists pos]
1987 || [$ctext compare $difffilestart($i) < $pos]} {
1988 set pos $difffilestart($i)
1989 }
1990 }
1991 }
1992 if {[info exists pos]} {
1993 $ctext yview $pos
1994 }
1995}
1996
1997proc listboxsel {} {
1998 global ctext cflist currentid
1999 if {![info exists currentid]} return
2000 set sel [lsort [$cflist curselection]]
2001 if {$sel eq {}} return
2002 set first [lindex $sel 0]
2003 catch {$ctext yview fmark.$first}
2004}
2005
2006proc setcoords {} {
2007 global linespc charspc canvx0 canvy0 mainfont
2008 set linespc [font metrics $mainfont -linespace]
2009 set charspc [font measure $mainfont "m"]
2010 set canvy0 [expr 3 + 0.5 * $linespc]
2011 set canvx0 [expr 3 + 0.5 * $linespc]
2012}
2013
2014proc redisplay {} {
2015 global selectedline stopped redisplaying phase
2016 if {$stopped > 1} return
2017 if {$phase == "getcommits"} return
2018 set redisplaying 1
2019 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2020 set stopped 1
2021 } else {
2022 drawgraph
2023 }
2024}
2025
2026proc incrfont {inc} {
2027 global mainfont namefont textfont selectedline ctext canv phase
2028 global stopped entries
2029 unmarkmatches
2030 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2031 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2032 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2033 setcoords
2034 $ctext conf -font $textfont
2035 $ctext tag conf filesep -font [concat $textfont bold]
2036 foreach e $entries {
2037 $e conf -font $mainfont
2038 }
2039 if {$phase == "getcommits"} {
2040 $canv itemconf textitems -font $mainfont
2041 }
2042 redisplay
2043}
2044
2045proc clearsha1 {} {
2046 global sha1entry sha1string
2047 if {[string length $sha1string] == 40} {
2048 $sha1entry delete 0 end
2049 }
2050}
2051
2052proc sha1change {n1 n2 op} {
2053 global sha1string currentid sha1but
2054 if {$sha1string == {}
2055 || ([info exists currentid] && $sha1string == $currentid)} {
2056 set state disabled
2057 } else {
2058 set state normal
2059 }
2060 if {[$sha1but cget -state] == $state} return
2061 if {$state == "normal"} {
2062 $sha1but conf -state normal -relief raised -text "Goto: "
2063 } else {
2064 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2065 }
2066}
2067
2068proc gotocommit {} {
2069 global sha1string currentid idline tagids
2070 global lineid numcommits
2071
2072 if {$sha1string == {}
2073 || ([info exists currentid] && $sha1string == $currentid)} return
2074 if {[info exists tagids($sha1string)]} {
2075 set id $tagids($sha1string)
2076 } else {
2077 set id [string tolower $sha1string]
2078 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2079 set matches {}
2080 for {set l 0} {$l < $numcommits} {incr l} {
2081 if {[string match $id* $lineid($l)]} {
2082 lappend matches $lineid($l)
2083 }
2084 }
2085 if {$matches ne {}} {
2086 if {[llength $matches] > 1} {
2087 error_popup "Short SHA1 id $id is ambiguous"
2088 return
2089 }
2090 set id [lindex $matches 0]
2091 }
2092 }
2093 }
2094 if {[info exists idline($id)]} {
2095 selectline $idline($id)
2096 return
2097 }
2098 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2099 set type "SHA1 id"
2100 } else {
2101 set type "Tag"
2102 }
2103 error_popup "$type $sha1string is not known"
2104}
2105
2106proc lineenter {x y id} {
2107 global hoverx hovery hoverid hovertimer
2108 global commitinfo canv
2109
2110 if {![info exists commitinfo($id)]} return
2111 set hoverx $x
2112 set hovery $y
2113 set hoverid $id
2114 if {[info exists hovertimer]} {
2115 after cancel $hovertimer
2116 }
2117 set hovertimer [after 500 linehover]
2118 $canv delete hover
2119}
2120
2121proc linemotion {x y id} {
2122 global hoverx hovery hoverid hovertimer
2123
2124 if {[info exists hoverid] && $id == $hoverid} {
2125 set hoverx $x
2126 set hovery $y
2127 if {[info exists hovertimer]} {
2128 after cancel $hovertimer
2129 }
2130 set hovertimer [after 500 linehover]
2131 }
2132}
2133
2134proc lineleave {id} {
2135 global hoverid hovertimer canv
2136
2137 if {[info exists hoverid] && $id == $hoverid} {
2138 $canv delete hover
2139 if {[info exists hovertimer]} {
2140 after cancel $hovertimer
2141 unset hovertimer
2142 }
2143 unset hoverid
2144 }
2145}
2146
2147proc linehover {} {
2148 global hoverx hovery hoverid hovertimer
2149 global canv linespc lthickness
2150 global commitinfo mainfont
2151
2152 set text [lindex $commitinfo($hoverid) 0]
2153 set ymax [lindex [$canv cget -scrollregion] 3]
2154 if {$ymax == {}} return
2155 set yfrac [lindex [$canv yview] 0]
2156 set x [expr {$hoverx + 2 * $linespc}]
2157 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2158 set x0 [expr {$x - 2 * $lthickness}]
2159 set y0 [expr {$y - 2 * $lthickness}]
2160 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2161 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2162 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2163 -fill \#ffff80 -outline black -width 1 -tags hover]
2164 $canv raise $t
2165 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2166 $canv raise $t
2167}
2168
2169proc lineclick {x y id} {
2170 global ctext commitinfo children cflist canv
2171
2172 unmarkmatches
2173 $canv delete hover
2174 # fill the details pane with info about this line
2175 $ctext conf -state normal
2176 $ctext delete 0.0 end
2177 $ctext insert end "Parent:\n "
2178 catch {destroy $ctext.$id}
2179 button $ctext.$id -text "Go:" -command "selbyid $id" \
2180 -padx 4 -pady 0
2181 $ctext window create end -window $ctext.$id -align center
2182 set info $commitinfo($id)
2183 $ctext insert end "\t[lindex $info 0]\n"
2184 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2185 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2186 $ctext insert end "\tID:\t$id\n"
2187 if {[info exists children($id)]} {
2188 $ctext insert end "\nChildren:"
2189 foreach child $children($id) {
2190 $ctext insert end "\n "
2191 catch {destroy $ctext.$child}
2192 button $ctext.$child -text "Go:" -command "selbyid $child" \
2193 -padx 4 -pady 0
2194 $ctext window create end -window $ctext.$child -align center
2195 set info $commitinfo($child)
2196 $ctext insert end "\t[lindex $info 0]"
2197 }
2198 }
2199 $ctext conf -state disabled
2200
2201 $cflist delete 0 end
2202}
2203
2204proc selbyid {id} {
2205 global idline
2206 if {[info exists idline($id)]} {
2207 selectline $idline($id)
2208 }
2209}
2210
2211proc mstime {} {
2212 global startmstime
2213 if {![info exists startmstime]} {
2214 set startmstime [clock clicks -milliseconds]
2215 }
2216 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2217}
2218
2219proc rowmenu {x y id} {
2220 global rowctxmenu idline selectedline rowmenuid
2221
2222 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2223 set state disabled
2224 } else {
2225 set state normal
2226 }
2227 $rowctxmenu entryconfigure 0 -state $state
2228 $rowctxmenu entryconfigure 1 -state $state
2229 $rowctxmenu entryconfigure 2 -state $state
2230 set rowmenuid $id
2231 tk_popup $rowctxmenu $x $y
2232}
2233
2234proc diffvssel {dirn} {
2235 global rowmenuid selectedline lineid
2236 global ctext cflist
2237 global commitinfo
2238
2239 if {![info exists selectedline]} return
2240 if {$dirn} {
2241 set oldid $lineid($selectedline)
2242 set newid $rowmenuid
2243 } else {
2244 set oldid $rowmenuid
2245 set newid $lineid($selectedline)
2246 }
2247 $ctext conf -state normal
2248 $ctext delete 0.0 end
2249 $ctext mark set fmark.0 0.0
2250 $ctext mark gravity fmark.0 left
2251 $cflist delete 0 end
2252 $cflist insert end "Top"
2253 $ctext insert end "From $oldid\n "
2254 $ctext insert end [lindex $commitinfo($oldid) 0]
2255 $ctext insert end "\n\nTo $newid\n "
2256 $ctext insert end [lindex $commitinfo($newid) 0]
2257 $ctext insert end "\n"
2258 $ctext conf -state disabled
2259 $ctext tag delete Comments
2260 $ctext tag remove found 1.0 end
2261 startdiff $newid [list $oldid]
2262}
2263
2264proc mkpatch {} {
2265 global rowmenuid currentid commitinfo patchtop patchnum
2266
2267 if {![info exists currentid]} return
2268 set oldid $currentid
2269 set oldhead [lindex $commitinfo($oldid) 0]
2270 set newid $rowmenuid
2271 set newhead [lindex $commitinfo($newid) 0]
2272 set top .patch
2273 set patchtop $top
2274 catch {destroy $top}
2275 toplevel $top
2276 label $top.title -text "Generate patch"
2277 grid $top.title - -pady 10
2278 label $top.from -text "From:"
2279 entry $top.fromsha1 -width 40 -relief flat
2280 $top.fromsha1 insert 0 $oldid
2281 $top.fromsha1 conf -state readonly
2282 grid $top.from $top.fromsha1 -sticky w
2283 entry $top.fromhead -width 60 -relief flat
2284 $top.fromhead insert 0 $oldhead
2285 $top.fromhead conf -state readonly
2286 grid x $top.fromhead -sticky w
2287 label $top.to -text "To:"
2288 entry $top.tosha1 -width 40 -relief flat
2289 $top.tosha1 insert 0 $newid
2290 $top.tosha1 conf -state readonly
2291 grid $top.to $top.tosha1 -sticky w
2292 entry $top.tohead -width 60 -relief flat
2293 $top.tohead insert 0 $newhead
2294 $top.tohead conf -state readonly
2295 grid x $top.tohead -sticky w
2296 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2297 grid $top.rev x -pady 10
2298 label $top.flab -text "Output file:"
2299 entry $top.fname -width 60
2300 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2301 incr patchnum
2302 grid $top.flab $top.fname -sticky w
2303 frame $top.buts
2304 button $top.buts.gen -text "Generate" -command mkpatchgo
2305 button $top.buts.can -text "Cancel" -command mkpatchcan
2306 grid $top.buts.gen $top.buts.can
2307 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2308 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2309 grid $top.buts - -pady 10 -sticky ew
2310 focus $top.fname
2311}
2312
2313proc mkpatchrev {} {
2314 global patchtop
2315
2316 set oldid [$patchtop.fromsha1 get]
2317 set oldhead [$patchtop.fromhead get]
2318 set newid [$patchtop.tosha1 get]
2319 set newhead [$patchtop.tohead get]
2320 foreach e [list fromsha1 fromhead tosha1 tohead] \
2321 v [list $newid $newhead $oldid $oldhead] {
2322 $patchtop.$e conf -state normal
2323 $patchtop.$e delete 0 end
2324 $patchtop.$e insert 0 $v
2325 $patchtop.$e conf -state readonly
2326 }
2327}
2328
2329proc mkpatchgo {} {
2330 global patchtop
2331
2332 set oldid [$patchtop.fromsha1 get]
2333 set newid [$patchtop.tosha1 get]
2334 set fname [$patchtop.fname get]
2335 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2336 error_popup "Error creating patch: $err"
2337 }
2338 catch {destroy $patchtop}
2339 unset patchtop
2340}
2341
2342proc mkpatchcan {} {
2343 global patchtop
2344
2345 catch {destroy $patchtop}
2346 unset patchtop
2347}
2348
2349proc mktag {} {
2350 global rowmenuid mktagtop commitinfo
2351
2352 set top .maketag
2353 set mktagtop $top
2354 catch {destroy $top}
2355 toplevel $top
2356 label $top.title -text "Create tag"
2357 grid $top.title - -pady 10
2358 label $top.id -text "ID:"
2359 entry $top.sha1 -width 40 -relief flat
2360 $top.sha1 insert 0 $rowmenuid
2361 $top.sha1 conf -state readonly
2362 grid $top.id $top.sha1 -sticky w
2363 entry $top.head -width 60 -relief flat
2364 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2365 $top.head conf -state readonly
2366 grid x $top.head -sticky w
2367 label $top.tlab -text "Tag name:"
2368 entry $top.tag -width 60
2369 grid $top.tlab $top.tag -sticky w
2370 frame $top.buts
2371 button $top.buts.gen -text "Create" -command mktaggo
2372 button $top.buts.can -text "Cancel" -command mktagcan
2373 grid $top.buts.gen $top.buts.can
2374 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2375 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2376 grid $top.buts - -pady 10 -sticky ew
2377 focus $top.tag
2378}
2379
2380proc domktag {} {
2381 global mktagtop env tagids idtags
2382 global idpos idline linehtag canv selectedline
2383
2384 set id [$mktagtop.sha1 get]
2385 set tag [$mktagtop.tag get]
2386 if {$tag == {}} {
2387 error_popup "No tag name specified"
2388 return
2389 }
2390 if {[info exists tagids($tag)]} {
2391 error_popup "Tag \"$tag\" already exists"
2392 return
2393 }
2394 if {[catch {
2395 set dir ".git"
2396 if {[info exists env(GIT_DIR)]} {
2397 set dir $env(GIT_DIR)
2398 }
2399 set fname [file join $dir "refs/tags" $tag]
2400 set f [open $fname w]
2401 puts $f $id
2402 close $f
2403 } err]} {
2404 error_popup "Error creating tag: $err"
2405 return
2406 }
2407
2408 set tagids($tag) $id
2409 lappend idtags($id) $tag
2410 $canv delete tag.$id
2411 set xt [eval drawtags $id $idpos($id)]
2412 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2413 if {[info exists selectedline] && $selectedline == $idline($id)} {
2414 selectline $selectedline
2415 }
2416}
2417
2418proc mktagcan {} {
2419 global mktagtop
2420
2421 catch {destroy $mktagtop}
2422 unset mktagtop
2423}
2424
2425proc mktaggo {} {
2426 domktag
2427 mktagcan
2428}
2429
2430proc writecommit {} {
2431 global rowmenuid wrcomtop commitinfo wrcomcmd
2432
2433 set top .writecommit
2434 set wrcomtop $top
2435 catch {destroy $top}
2436 toplevel $top
2437 label $top.title -text "Write commit to file"
2438 grid $top.title - -pady 10
2439 label $top.id -text "ID:"
2440 entry $top.sha1 -width 40 -relief flat
2441 $top.sha1 insert 0 $rowmenuid
2442 $top.sha1 conf -state readonly
2443 grid $top.id $top.sha1 -sticky w
2444 entry $top.head -width 60 -relief flat
2445 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2446 $top.head conf -state readonly
2447 grid x $top.head -sticky w
2448 label $top.clab -text "Command:"
2449 entry $top.cmd -width 60 -textvariable wrcomcmd
2450 grid $top.clab $top.cmd -sticky w -pady 10
2451 label $top.flab -text "Output file:"
2452 entry $top.fname -width 60
2453 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
2454 grid $top.flab $top.fname -sticky w
2455 frame $top.buts
2456 button $top.buts.gen -text "Write" -command wrcomgo
2457 button $top.buts.can -text "Cancel" -command wrcomcan
2458 grid $top.buts.gen $top.buts.can
2459 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2460 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2461 grid $top.buts - -pady 10 -sticky ew
2462 focus $top.fname
2463}
2464
2465proc wrcomgo {} {
2466 global wrcomtop
2467
2468 set id [$wrcomtop.sha1 get]
2469 set cmd "echo $id | [$wrcomtop.cmd get]"
2470 set fname [$wrcomtop.fname get]
2471 if {[catch {exec sh -c $cmd >$fname &} err]} {
2472 error_popup "Error writing commit: $err"
2473 }
2474 catch {destroy $wrcomtop}
2475 unset wrcomtop
2476}
2477
2478proc wrcomcan {} {
2479 global wrcomtop
2480
2481 catch {destroy $wrcomtop}
2482 unset wrcomtop
2483}
2484
2485proc doquit {} {
2486 global stopped
2487 set stopped 100
2488 destroy .
2489}
2490
2491# defaults...
2492set datemode 0
2493set boldnames 0
2494set diffopts "-U 5 -p"
2495set wrcomcmd "git-diff-tree --stdin -p --pretty"
2496
2497set mainfont {Helvetica 9}
2498set textfont {Courier 9}
2499set findmergefiles 0
2500set gaudydiff 0
2501
2502set colors {green red blue magenta darkgrey brown orange}
2503
2504catch {source ~/.gitk}
2505
2506set namefont $mainfont
2507if {$boldnames} {
2508 lappend namefont bold
2509}
2510
2511set revtreeargs {}
2512foreach arg $argv {
2513 switch -regexp -- $arg {
2514 "^$" { }
2515 "^-b" { set boldnames 1 }
2516 "^-d" { set datemode 1 }
2517 default {
2518 lappend revtreeargs $arg
2519 }
2520 }
2521}
2522
2523set stopped 0
2524set redisplaying 0
2525set stuffsaved 0
2526set patchnum 0
2527setcoords
2528makewindow
2529readrefs
2530getcommits $revtreeargs